phasegate 0.161.0 → 0.163.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 (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/docs/contracts/requirement-test-matrix.schema.json +1 -1
  3. package/package.json +6 -3
  4. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +91 -23
  5. package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +4 -0
  6. package/scripts/harness/agent-integration/infrastructure/adapters/ci-governance-baseline-grandfather-adapter.ts +25 -2
  7. package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts +17 -2
  8. package/scripts/harness/biome-ast-engine/domain/value-objects/import-graph.ts +28 -1
  9. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +1 -0
  10. package/scripts/harness/config-foundation/domain/harness-config.ts +1 -0
  11. package/scripts/harness/config-foundation/domain/value-objects/l3-config.ts +4 -0
  12. package/scripts/harness/config-foundation/domain/value-objects/layers-config.ts +1 -1
  13. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -1
  14. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -1
  15. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -1
  16. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +14 -9
  17. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +14 -9
  18. package/scripts/harness/harness-api/infrastructure/adapters/phase-dependency-model-query-adapter.ts +18 -4
  19. package/scripts/harness/harness-api/infrastructure/adapters/validator-system-execution-adapter.ts +27 -4
  20. package/scripts/harness/installation/application/ports/model-delegation-port.ts +10 -0
  21. package/scripts/harness/installation/application/usecases/run-install.ts +56 -7
  22. package/scripts/harness/installation/application/usecases/run-reconcile.ts +12 -7
  23. package/scripts/harness/installation/composition-root.ts +4 -2
  24. package/scripts/harness/installation/infrastructure/adapters/skill-deployer-model-delegation-adapter.ts +16 -0
  25. package/scripts/harness/installation/presentation/cli/install-handler.ts +2 -1
  26. package/scripts/harness/main.ts +68 -12
  27. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +9 -7
  28. package/scripts/harness/nyquist-validation/domain/services/matrix-validation-service.ts +27 -2
  29. package/scripts/harness/nyquist-validation/domain/services/requirement-intent-coverage-service.ts +4 -4
  30. package/scripts/harness/nyquist-validation/domain/value-objects/intent-coverage.ts +33 -0
  31. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +3 -1
  32. package/scripts/harness/nyquist-validation/infrastructure/adapters/type-script-test-reference-source-adapter.ts +4 -1
  33. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +8 -3
  34. package/scripts/harness/quick-mode/composition-root.ts +3 -0
  35. package/scripts/harness/quick-mode/infrastructure/adapters/validator-system-quick-mode-execution-adapter.ts +18 -0
  36. package/scripts/harness/quick-mode/presentation/handlers/ci-check-quick-mode-handler.ts +51 -5
  37. package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +12 -2
  38. package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +12 -2
  39. package/scripts/harness/traceability-model/application/usecases/apply-work-item-status-usecase.ts +6 -1
  40. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +28 -9
  41. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +65 -27
  42. package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +6 -7
  43. package/scripts/harness/validator-system/composition-root.ts +8 -1
  44. package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +16 -6
  45. package/scripts/harness/validator-system/infrastructure/adapters/file-system-work-item-reflection-adapter.ts +10 -1
  46. package/scripts/harness/validator-system/infrastructure/adapters/import-graph-source-analysis-adapter.ts +35 -3
  47. package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +86 -14
  48. package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +29 -3
  49. package/templates/.claude/scripts/deny-check.sh +73 -12
@@ -3,39 +3,111 @@
3
3
  * @unit validator-system
4
4
  *
5
5
  * NyquistAcCoveragePolicyAdapter — AcCoveragePolicyPort実装
6
+ *
7
+ * FAIL-CLOSED: nyquist-validation モジュールの実行時に例外が発生した場合は
8
+ * passed=false(不合格)として扱い、エラーを結果に含めて上位へ通知する。
9
+ * かつては catch 節で passed=true を返す fail-open だったが、AC 網羅ゲート
10
+ * (L3-004)を無効化する重大な品質防御の抜け穴だったため fail-closed へ是正した。
11
+ *
12
+ * REAL REGISTRY: storyId 整合性検査に必要な有効 storyId 一覧を
13
+ * traceability-model の StoryCatalog から取得して nyquist モジュールへ配線する。
14
+ * かつては `getStoryIds: async () => []`(空スタブ)だったため、正当かつ完全網羅の
15
+ * マトリクスでも全 storyId が「未登録」と判定され L3-004 が構造的に PASS 不能だった。
16
+ *
17
+ * MATRIX PATH: requirement-test-matrix.json のパスは config
18
+ * (layers.L3.requirementMatrixPath)から供給される。未指定時は既定値
19
+ * `.harness/requirement-test-matrix.json` を用いる。マトリクスが不在の場合は
20
+ * fail-closed で「設定されているが不在」という実行可能なメッセージを返す。
6
21
  */
22
+ import { access } from 'node:fs/promises';
23
+ import { isAbsolute, join } from 'node:path';
7
24
  import type { AcCoveragePolicyPort } from '../../domain/ports/ac-coverage-policy-port.js';
8
25
  import type { HarnessErrorLike } from '../../domain/value-objects/validation-result.js';
9
26
 
27
+ const DEFAULT_MATRIX_PATH = '.harness/requirement-test-matrix.json';
28
+
29
+ function toL3004Error(message: string, suggestion: string): HarnessErrorLike {
30
+ return {
31
+ code: { value: 'L3-004', toString: () => 'L3-004' },
32
+ severity: { value: 'error', toString: () => 'error' },
33
+ message,
34
+ suggestion,
35
+ };
36
+ }
37
+
10
38
  export class NyquistAcCoveragePolicyAdapter implements AcCoveragePolicyPort {
11
39
  async checkCoverage(context: { matrixFilePath?: string }): Promise<{
12
40
  passed: boolean;
13
41
  errors: readonly HarnessErrorLike[];
14
42
  }> {
43
+ const rootDir = process.cwd();
44
+ 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);
51
+
52
+ // マトリクス不在は threshold-configured-but-absent と同様に fail-closed で
53
+ // 明確なメッセージを返す(ENOENT の生メッセージで落とさない)。
54
+ try {
55
+ await access(matrixFilePath);
56
+ } catch {
57
+ return {
58
+ passed: false,
59
+ errors: [
60
+ toL3004Error(
61
+ `AC網羅マトリクスが見つかりません: ${relativeOrAbsolute}(L3-004 は fail-closed)`,
62
+ `${relativeOrAbsolute} を生成してください(phasegate:generate-matrix)。パスは config の layers.L3.requirementMatrixPath で変更できます`,
63
+ ),
64
+ ],
65
+ };
66
+ }
67
+
15
68
  try {
69
+ // 有効 storyId 一覧を traceability-model の StoryCatalog から取得する(REAL registry)
70
+ const storyIds = await this.loadValidStoryIds(rootDir);
71
+
16
72
  const { createNyquistValidationModule } = await import('../../../nyquist-validation/composition-root.js');
17
73
  const mod = createNyquistValidationModule({
18
- rootDir: process.cwd(),
19
- getStoryIds: async () => [],
20
- } as {
21
- rootDir: string;
22
- getStoryIds: () => Promise<readonly string[]>;
74
+ getStoryIds: async () => storyIds,
23
75
  });
24
76
  const output = await mod.checkAcCoverageGateUseCase.execute({
25
- matrixFilePath: context.matrixFilePath ?? '',
77
+ matrixFilePath,
26
78
  });
27
79
 
28
80
  return {
29
81
  passed: output.passed,
30
- errors: output.errors.map((err) => ({
31
- code: { value: 'L3-004', toString: () => 'L3-004' },
32
- severity: { value: 'error', toString: () => 'error' },
33
- message: err.message,
34
- suggestion: '',
35
- })),
82
+ errors: output.errors.map((err) =>
83
+ toL3004Error(err.message, ''),
84
+ ),
85
+ };
86
+ } catch (error) {
87
+ const message = error instanceof Error ? error.message : String(error);
88
+ // FAIL-CLOSED: 例外を握り潰して合格扱いにせず、不合格として通知する
89
+ return {
90
+ passed: false,
91
+ errors: [
92
+ toL3004Error(
93
+ `AC網羅ゲートの検査に失敗しました(fail-closed): ${message}`,
94
+ 'requirement-test-matrix.json の存在・形式・スキーマ整合性を確認してください',
95
+ ),
96
+ ],
36
97
  };
37
- } catch {
38
- return { passed: true, errors: [] };
39
98
  }
40
99
  }
100
+
101
+ private async loadValidStoryIds(rootDir: string): Promise<readonly string[]> {
102
+ const { createConfigFoundationModule } = await import('../../../config-foundation/composition-root.js');
103
+ const configModule = createConfigFoundationModule();
104
+ const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
105
+
106
+ const { createTraceabilityModelModule } = await import('../../../traceability-model/composition-root.js');
107
+ const traceModule = createTraceabilityModelModule(rootDir, {
108
+ pathRoots: { designDocsRoot: resolvedConfig.config.paths.designDocs },
109
+ });
110
+ const storyIds = await traceModule.storyCatalog.getAllStoryIds();
111
+ return storyIds.map((s) => s.value);
112
+ }
41
113
  }
@@ -51,9 +51,35 @@ export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPor
51
51
  };
52
52
  }
53
53
 
54
- return { satisfied: true, violations: [] };
55
- } catch {
56
- return { satisfied: true, violations: [] };
54
+ // Fail-closed: an unexpected exit code means prerequisites could not be confirmed.
55
+ console.error(`[validator-system] phase gate check returned unexpected exit code ${result.exitCode}`);
56
+ return {
57
+ satisfied: false,
58
+ violations: [
59
+ {
60
+ code: { value: 'L2-002', toString: () => 'L2-002' },
61
+ severity: { value: 'error', toString: () => 'error' },
62
+ 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)',
64
+ },
65
+ ],
66
+ };
67
+ } catch (err) {
68
+ // Fail-closed: if the phase-dependency-model could not be loaded/executed we cannot
69
+ // confirm prerequisites are met, so treat them as NOT satisfied rather than opening the gate.
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ console.error(`[validator-system] phase gate prerequisite check failed to run: ${message}`);
72
+ return {
73
+ satisfied: false,
74
+ violations: [
75
+ {
76
+ code: { value: 'L2-002', toString: () => 'L2-002' },
77
+ severity: { value: 'error', toString: () => 'error' },
78
+ 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)',
80
+ },
81
+ ],
82
+ };
57
83
  }
58
84
  }
59
85
  }
@@ -4,14 +4,33 @@
4
4
  # Reads JSON from stdin, exits with code 2 to block dangerous commands.
5
5
  # Deny patterns are read from .claude/settings.json permissions.deny.
6
6
 
7
- DEBUG_LOG="/tmp/claude_deny_check_debug.log"
8
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] deny-check.sh started" >> "$DEBUG_LOG"
7
+ # Debug logging is OFF by default to avoid leaking command contents to a
8
+ # world-readable file. Set CLAUDE_DENY_CHECK_DEBUG=1 to enable. When enabled,
9
+ # the log is created with restrictive (owner-only) permissions.
10
+ DEBUG_LOG="${CLAUDE_DENY_CHECK_DEBUG_LOG:-$HOME/.cache/claude/deny-check-debug.log}"
11
+
12
+ debug_log() {
13
+ if [[ "$CLAUDE_DENY_CHECK_DEBUG" == "1" ]]; then
14
+ local dir
15
+ dir=$(dirname "$DEBUG_LOG")
16
+ mkdir -p "$dir" 2>/dev/null || return 0
17
+ # Restrict permissions before writing anything sensitive.
18
+ ( umask 077; touch "$DEBUG_LOG" 2>/dev/null ) || return 0
19
+ chmod 600 "$DEBUG_LOG" 2>/dev/null || true
20
+ printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$DEBUG_LOG" 2>/dev/null || true
21
+ fi
22
+ }
23
+
24
+ debug_log "deny-check.sh started"
9
25
 
10
26
  INPUT_JSON=$(cat)
11
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Input JSON: $INPUT_JSON" >> "$DEBUG_LOG"
27
+ debug_log "input received (${#INPUT_JSON} bytes)"
12
28
 
29
+ # Fail CLOSED: without jq we cannot parse the deny policy, so deny the command
30
+ # rather than silently allowing it.
13
31
  if ! command -v jq &> /dev/null; then
14
- exit 0
32
+ echo "Security policy: jq is required to evaluate the deny policy but was not found; command blocked." >&2
33
+ exit 2
15
34
  fi
16
35
 
17
36
  TOOL_NAME=$(echo "$INPUT_JSON" | jq -r '.tool_name // empty')
@@ -43,13 +62,55 @@ if [[ ${#DENY_PATTERNS[@]} -eq 0 ]]; then
43
62
  exit 0
44
63
  fi
45
64
 
46
- for pattern in "${DENY_PATTERNS[@]}"; do
47
- regex_pattern=$(echo "$pattern" | sed 's/\*/.*/')
48
- if echo "$COMMAND" | grep -E "^$regex_pattern" &>/dev/null; then
49
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] BLOCKED: '$COMMAND' matches '$pattern'" >> "$DEBUG_LOG"
50
- echo "Security policy violation: command '$COMMAND' is denied by pattern '$pattern'" >&2
51
- exit 2
52
- fi
53
- done
65
+ # Split a shell command line into individual command segments so that denied
66
+ # commands smuggled behind chaining/grouping/substitution operators are still
67
+ # inspected. Operators handled: ; && || | & newline $( ) ` { } ( )
68
+ # We replace each operator with a newline, then treat each line as a segment.
69
+ split_command_segments() {
70
+ local cmd="$1"
71
+ # Order matters: multi-char operators first.
72
+ cmd=${cmd//&&/$'\n'}
73
+ cmd=${cmd//||/$'\n'}
74
+ cmd=${cmd//|/$'\n'}
75
+ cmd=${cmd//;/$'\n'}
76
+ cmd=${cmd//&/$'\n'}
77
+ cmd=${cmd//\$(/$'\n'}
78
+ cmd=${cmd//\`/$'\n'}
79
+ cmd=${cmd//(/$'\n'}
80
+ cmd=${cmd//)/$'\n'}
81
+ cmd=${cmd//\{/$'\n'}
82
+ cmd=${cmd//\}/$'\n'}
83
+ # Trailing newline ensures the final segment is emitted as a complete line
84
+ # so that `while read` does not drop an unterminated last segment.
85
+ printf '%s\n' "$cmd"
86
+ }
87
+
88
+ # Convert a settings glob pattern to an anchored regex. ALL '*' become '.*'.
89
+ glob_to_regex() {
90
+ printf '%s' "$1" | sed 's/\*/.*/g'
91
+ }
92
+
93
+ check_segment() {
94
+ local segment="$1"
95
+ # Strip leading whitespace so "^pattern" anchors match after operators.
96
+ segment="${segment#"${segment%%[![:space:]]*}"}"
97
+ [[ -z "$segment" ]] && return 0
98
+ for pattern in "${DENY_PATTERNS[@]}"; do
99
+ local regex_pattern
100
+ regex_pattern=$(glob_to_regex "$pattern")
101
+ if printf '%s' "$segment" | grep -E "^$regex_pattern" &>/dev/null; then
102
+ debug_log "BLOCKED segment '$segment' matches '$pattern'"
103
+ echo "Security policy violation: command segment '$segment' is denied by pattern '$pattern'" >&2
104
+ exit 2
105
+ fi
106
+ done
107
+ return 0
108
+ }
109
+
110
+ # Inspect the whole command AND each chained/substituted segment.
111
+ check_segment "$COMMAND"
112
+ while IFS= read -r segment; do
113
+ check_segment "$segment"
114
+ done < <(split_command_segments "$COMMAND")
54
115
 
55
116
  exit 0