phasegate 0.16.0 → 0.32.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/README.ja.md +74 -42
- package/README.md +69 -9
- package/docs/ADR/ADR-013-story-reflection-gate.md +100 -0
- package/docs/guide/configuration.md +244 -7
- package/package.json +7 -5
- package/scripts/harness/adr-foundation/domain/ports/adr-document-parser-port.ts +1 -0
- package/scripts/harness/adr-foundation/domain/ports/adr-frontmatter-parser-port.ts +1 -0
- package/scripts/harness/adr-foundation/domain/ports/adr-repository-port.ts +1 -0
- package/scripts/harness/adr-foundation/domain/value-objects/adr-status.ts +6 -6
- package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +3 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +108 -2
- package/scripts/harness/agent-integration/domain/ports/config-query-port.ts +2 -0
- package/scripts/harness/agent-integration/domain/ports/phase-gate-query-port.ts +1 -1
- package/scripts/harness/agent-integration/domain/ports/story-reflection-query-port.ts +8 -0
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +258 -0
- package/scripts/harness/agent-integration/domain/services/hook-to-cli-translator.ts +21 -4
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +20 -0
- package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts +63 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +122 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +20 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts +17 -3
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +29 -2
- package/scripts/harness/biome-ast-engine/domain/services/lint-runner.ts +24 -0
- package/scripts/harness/biome-ast-engine/domain/services/rule-definition-registry.ts +1 -1
- package/scripts/harness/biome-ast-engine/domain/value-objects/source-module-snapshot.ts +5 -2
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/node-workspace-file-adapter.ts +1 -1
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +1 -1
- package/scripts/harness/biome-ast-engine/infrastructure/parsers/unit-comment-parser.ts +12 -7
- package/scripts/harness/ci-governance/composition-root.ts +1 -0
- package/scripts/harness/config-foundation/application/mappers/phase-config-section-mapper.ts +44 -0
- package/scripts/harness/config-foundation/application/usecases/load-resolved-config-use-case.ts +20 -1
- package/scripts/harness/config-foundation/domain/harness-config.ts +2 -1
- package/scripts/harness/config-foundation/domain/value-objects/phase-dependencies-config.ts +18 -4
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +185 -10
- package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +2 -1
- package/scripts/harness/harness-api/application/dto/command-dispatch-input.ts +1 -0
- package/scripts/harness/harness-api/application/dto/command-dispatch-output.ts +1 -0
- package/scripts/harness/harness-api/application/dto/exit-code-decision-input.ts +1 -0
- package/scripts/harness/harness-api/application/dto/exit-code-decision-output.ts +1 -0
- package/scripts/harness/harness-api/application/dto/harness-api-response-contract.ts +1 -0
- package/scripts/harness/harness-api/application/dto/initialize-registry-input.ts +1 -0
- package/scripts/harness/harness-api/application/dto/registry-summary-output.ts +1 -0
- package/scripts/harness/harness-api/application/dto/status-derivation-input.ts +1 -0
- package/scripts/harness/harness-api/application/mappers/harness-api-response-mapper.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/decide-exit-code-usecase.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/derive-harness-status-usecase.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/dispatch-command-usecase.ts +1 -0
- package/scripts/harness/harness-api/application/usecases/initialize-command-registry-usecase.ts +1 -0
- package/scripts/harness/harness-api/composition-root.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/artifact-scanner-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/biome-lint-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/config-query-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/impact-analysis-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/phase-gate-query-port.ts +1 -0
- package/scripts/harness/harness-api/domain/ports/validator-execution-port.ts +1 -0
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +1 -0
- package/scripts/harness/harness-api/domain/services/command-registry.ts +1 -0
- package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/artifact-scan-result.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/check-ready-result.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/cli-command-definition.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/command-input-spec.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/drift-report-summary.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/exit-code-spec.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/harness-api-response.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/layer-health.ts +1 -0
- package/scripts/harness/harness-api/domain/value-objects/phase-info.ts +1 -0
- package/scripts/harness/harness-api/index.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/file-system-artifact-scanner-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/nyquist-validation-impact-analysis-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/phase-dependency-model-query-adapter.ts +1 -0
- package/scripts/harness/harness-api/infrastructure/adapters/validator-system-execution-adapter.ts +1 -0
- package/scripts/harness/harness-api/presentation/dto/cli-output-options.ts +1 -0
- package/scripts/harness/harness-api/presentation/formatters/harness-api-json-formatter.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/check-phase-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/check-ready-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/ci-check-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/complete-check-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/detect-drift-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/impact-analysis-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/lint-handler.ts +1 -0
- package/scripts/harness/harness-api/presentation/handlers/status-handler.ts +1 -0
- package/scripts/harness/harness-error/infrastructure/adapters/legacy-error-reporter-adapter.ts +7 -1
- package/scripts/harness/integrations/pre-commit.ts +1 -0
- package/scripts/harness/main.ts +154 -34
- package/scripts/harness/phase-dependency-model/application/dto/resolve-gate-result-dto.ts +14 -0
- package/scripts/harness/phase-dependency-model/application/services/phase-gate-result-mapper.ts +20 -0
- package/scripts/harness/phase-dependency-model/application/usecases/check-story-reflection-usecase.ts +42 -0
- package/scripts/harness/phase-dependency-model/application/usecases/resolve-gate-usecase.ts +161 -0
- package/scripts/harness/phase-dependency-model/composition-root.ts +57 -1
- package/scripts/harness/phase-dependency-model/domain/definitions/{default-phase-dependencies.ts → full-phase-dependencies.ts} +3 -3
- package/scripts/harness/phase-dependency-model/domain/definitions/{default-phase-nodes.ts → full-phase-nodes.ts} +1 -1
- package/scripts/harness/phase-dependency-model/domain/definitions/full-story-reflection-defaults.ts +28 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/minimal-phase-dependencies.ts +32 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/minimal-phase-nodes.ts +50 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/minimal-story-reflection-defaults.ts +8 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/standard-phase-dependencies.ts +39 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/standard-phase-nodes.ts +118 -0
- package/scripts/harness/phase-dependency-model/domain/definitions/standard-story-reflection-defaults.ts +23 -0
- package/scripts/harness/phase-dependency-model/domain/models/phase-structure.ts +95 -23
- package/scripts/harness/phase-dependency-model/domain/ports/glob-matcher-port.ts +8 -0
- package/scripts/harness/phase-dependency-model/domain/ports/phase-config-provider-port.ts +2 -0
- package/scripts/harness/phase-dependency-model/domain/ports/story-annotation-verifier-port.ts +14 -0
- package/scripts/harness/phase-dependency-model/domain/ports/story-reflection-file-system-port.ts +16 -0
- package/scripts/harness/phase-dependency-model/domain/services/gate-graph.ts +223 -0
- package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +66 -0
- package/scripts/harness/phase-dependency-model/domain/values/gate-definition.ts +127 -0
- package/scripts/harness/phase-dependency-model/domain/values/gate-name.ts +40 -0
- package/scripts/harness/phase-dependency-model/domain/values/gate-story-annotation.ts +39 -0
- package/scripts/harness/phase-dependency-model/domain/values/phase-customization-policy.ts +16 -4
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-config.ts +44 -0
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-mapping.ts +91 -0
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-result.ts +55 -0
- package/scripts/harness/phase-dependency-model/infrastructure/adapters/picomatch-glob-matcher.ts +17 -0
- package/scripts/harness/phase-dependency-model/infrastructure/adapters/story-annotation-verifier-adapter.ts +46 -0
- package/scripts/harness/phase-dependency-model/infrastructure/config/custom-gates-config-parser.ts +22 -0
- package/scripts/harness/phase-dependency-model/infrastructure/config/harness-config-phase-config-provider.ts +78 -2
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +94 -0
- package/scripts/harness/phase-dependency-model/presentation/cli/check-phase-gate-command-handler.ts +12 -4
- package/scripts/harness/phase-dependency-model/presentation/cli/story-reflection-status-presenter.ts +87 -0
- package/scripts/harness/regression-suite/application/dto/analyze-migration-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/analyze-migration-output.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/configure-ci-gate-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/configure-ci-gate-output.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/migrate-v0-tests-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/migrate-v0-tests-output.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/run-regression-suite-input.ts +1 -0
- package/scripts/harness/regression-suite/application/dto/run-regression-suite-output.ts +1 -0
- package/scripts/harness/regression-suite/application/mappers/migration-mapping-mapper.ts +1 -0
- package/scripts/harness/regression-suite/application/mappers/test-execution-summary-mapper.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/analyze-v0-migration-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/configure-ci-gate-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/migrate-v0-tests-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-agent-independence-guard-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-gng-gate-regression-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-k-requirements-regression-usecase.ts +1 -0
- package/scripts/harness/regression-suite/application/usecases/run-k14-k15-regression-usecase.ts +1 -0
- package/scripts/harness/regression-suite/composition-root.ts +1 -0
- package/scripts/harness/regression-suite/domain/aggregates/v0-test-migration.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/ci-gate-result-writer-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/config-query-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/import-analyzer-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/migration-mapping-repository-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/suite-registry-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/test-runner-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/ports/v0-spec-reader-port.ts +1 -0
- package/scripts/harness/regression-suite/domain/services/import-guard-service.ts +1 -0
- package/scripts/harness/regression-suite/domain/services/migration-analyzer.ts +1 -0
- package/scripts/harness/regression-suite/domain/services/regression-runner.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/agent-independence-test.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/biome-modification-spec.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/ci-gate-config.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/coverage-rate.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/gng-condition-test.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/import-violation.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/k-requirement-test.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/migration-mapping.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/regression-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/suite-id.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/test-execution-summary.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/test-failure-detail.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/v0-test-id.ts +1 -0
- package/scripts/harness/regression-suite/domain/value-objects/v1-test-path.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/biome-ast-import-analyzer-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/file-system-v0-spec-reader-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/harness-config-query-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/json-ci-gate-result-writer-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/markdown-migration-mapping-repository-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/static-suite-registry-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/adapters/vitest-test-runner-adapter.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/agent-independence-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/gng-gate-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/k-requirements-suite-definition.ts +1 -0
- package/scripts/harness/regression-suite/infrastructure/registry/v0-migration-suite-definition.ts +1 -0
- package/scripts/harness/setup/skill-deployer.ts +82 -5
- package/scripts/harness/shared-kernel/harness-api.ts +1 -0
- package/scripts/harness/skill-quality/composition-root.ts +1 -0
- package/scripts/harness/skill-quality/infrastructure/adapters/ajv-lesson-artifact-schema-adapter.ts +5 -4
- package/scripts/harness/traceability-model/domain/services/metadata-validator.ts +12 -10
- package/scripts/harness/traceability-model/infrastructure/parsers/source-metadata-parser.ts +16 -2
- package/skills/cascade-updater/SKILL.md +16 -3
- package/skills/codebase-mapper/SKILL.md +9 -9
- package/skills/codex-delegator/SKILL.md +5 -56
- package/skills/consistency-checker/SKILL.md +5 -9
- package/skills/consistency-checker/references//343/203/201/343/202/247/343/203/203/343/202/257/343/203/252/343/202/271/343/203/210.md +6 -3
- package/skills/doc-freshness-checker/SKILL.md +6 -6
- package/skills/engineering-perspective/SKILL.md +2 -58
- package/skills/engineering-perspective/references/domain-model.md +4 -135
- package/skills/implementation-readiness-checker/SKILL.md +18 -72
- package/skills/it-test-designer/SKILL.md +25 -161
- package/skills/it-test-designer/references/it-test-design-template.md +131 -0
- package/skills/it-test-logic-designer/SKILL.md +13 -388
- package/skills/it-test-logic-designer/references/controller-test-patterns.md +100 -0
- package/skills/it-test-logic-designer/references/repository-test-patterns.md +206 -0
- package/skills/it-test-logic-designer/references/usecase-test-patterns.md +87 -0
- package/skills/pointer-validator/SKILL.md +4 -4
- package/skills/scenario-test-designer/SKILL.md +4 -68
- package/skills/scenario-test-designer/references/scenario-test-design-template.md +41 -0
- package/skills/scenario-test-designer/references/scenario-test-plan-template.md +35 -0
- package/skills/scenario-test-logic-designer/SKILL.md +21 -304
- package/skills/scenario-test-logic-designer/references/msw-patterns.md +36 -0
- package/skills/scenario-test-logic-designer/references/playwright-patterns.md +237 -0
- package/skills/test-coverage-checker/SKILL.md +39 -121
- package/skills/test-coverage-checker/references/coverage-report-template.md +99 -0
- package/skills/uiux-designer/SKILL.md +27 -92
- package/skills/uiux-designer/references/data-testid-convention.md +38 -0
- package/skills/uiux-designer/references/uiux-design-template.md +105 -0
- package/skills/unit-test-designer/SKILL.md +5 -106
- package/skills/unit-test-designer/references/unit-test-design-template.md +127 -0
- package/skills/unit-test-logic-designer/SKILL.md +28 -250
- package/skills/unit-test-logic-designer/references/test-patterns.md +207 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit agent-integration
|
|
4
|
+
*
|
|
5
|
+
* BashWriteTargetExtractor ドメインサービス
|
|
6
|
+
*
|
|
7
|
+
* Bash コマンド文字列を静的解析し、書き込み対象となるファイルパスを抽出する。
|
|
8
|
+
* Phase Gate フックが Bash 経由のファイル書き込みを検知して保護するための基盤。
|
|
9
|
+
*
|
|
10
|
+
* 副作用なし・純粋関数相当。対応パターンは以下:
|
|
11
|
+
* - リダイレクト `>` / `>>`
|
|
12
|
+
* - heredoc (`<<EOF > path`)
|
|
13
|
+
* - `tee` / `tee -a`
|
|
14
|
+
* - `sed -i` / `sed -i ''` (BSD)
|
|
15
|
+
* - `cp` / `mv` (宛先のみ)
|
|
16
|
+
* - `touch`
|
|
17
|
+
* - 複合コマンド (`&&`, `;`, `||`) とパイプ (`|`) 分割
|
|
18
|
+
* - ダブル/シングルクォート対応
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** 引数トークン (値とクォート種別) */
|
|
22
|
+
type Token = {
|
|
23
|
+
readonly value: string;
|
|
24
|
+
readonly quoted: 'none' | 'single' | 'double';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** シェル風の簡易トークナイザ。クォート境界を尊重する。 */
|
|
28
|
+
function tokenize(input: string): Token[] {
|
|
29
|
+
const tokens: Token[] = [];
|
|
30
|
+
let i = 0;
|
|
31
|
+
const len = input.length;
|
|
32
|
+
|
|
33
|
+
while (i < len) {
|
|
34
|
+
const ch = input[i];
|
|
35
|
+
|
|
36
|
+
// 空白スキップ
|
|
37
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
38
|
+
i += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 演算子をそのままトークン化
|
|
43
|
+
if (ch === '>' || ch === '<' || ch === '|' || ch === ';' || ch === '&') {
|
|
44
|
+
let op = ch;
|
|
45
|
+
if (i + 1 < len && input[i + 1] === ch && (ch === '>' || ch === '<' || ch === '|' || ch === '&')) {
|
|
46
|
+
op = ch + ch;
|
|
47
|
+
i += 2;
|
|
48
|
+
} else {
|
|
49
|
+
i += 1;
|
|
50
|
+
}
|
|
51
|
+
tokens.push({ value: op, quoted: 'none' });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ダブルクォート
|
|
56
|
+
if (ch === '"') {
|
|
57
|
+
let buf = '';
|
|
58
|
+
i += 1;
|
|
59
|
+
while (i < len && input[i] !== '"') {
|
|
60
|
+
if (input[i] === '\\' && i + 1 < len) {
|
|
61
|
+
buf += input[i + 1];
|
|
62
|
+
i += 2;
|
|
63
|
+
} else {
|
|
64
|
+
buf += input[i];
|
|
65
|
+
i += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
i += 1; // closing quote
|
|
69
|
+
tokens.push({ value: buf, quoted: 'double' });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// シングルクォート
|
|
74
|
+
if (ch === "'") {
|
|
75
|
+
let buf = '';
|
|
76
|
+
i += 1;
|
|
77
|
+
while (i < len && input[i] !== "'") {
|
|
78
|
+
buf += input[i];
|
|
79
|
+
i += 1;
|
|
80
|
+
}
|
|
81
|
+
i += 1; // closing quote
|
|
82
|
+
tokens.push({ value: buf, quoted: 'single' });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 通常トークン
|
|
87
|
+
let buf = '';
|
|
88
|
+
while (i < len) {
|
|
89
|
+
const c = input[i];
|
|
90
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') break;
|
|
91
|
+
if (c === '>' || c === '<' || c === '|' || c === ';' || c === '&') break;
|
|
92
|
+
if (c === '"' || c === "'") break;
|
|
93
|
+
buf += c;
|
|
94
|
+
i += 1;
|
|
95
|
+
}
|
|
96
|
+
if (buf.length > 0) {
|
|
97
|
+
tokens.push({ value: buf, quoted: 'none' });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return tokens;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const OPERATOR_SEPARATORS = new Set(['&&', '||', ';', '|']);
|
|
105
|
+
|
|
106
|
+
/** トークン列を複合・パイプ境界で分割 */
|
|
107
|
+
function splitByOperators(tokens: Token[]): Token[][] {
|
|
108
|
+
const groups: Token[][] = [];
|
|
109
|
+
let current: Token[] = [];
|
|
110
|
+
for (const t of tokens) {
|
|
111
|
+
if (t.quoted === 'none' && OPERATOR_SEPARATORS.has(t.value)) {
|
|
112
|
+
if (current.length > 0) {
|
|
113
|
+
groups.push(current);
|
|
114
|
+
current = [];
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
current.push(t);
|
|
119
|
+
}
|
|
120
|
+
if (current.length > 0) groups.push(current);
|
|
121
|
+
return groups;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** コマンドグループ先頭の環境変数代入 (`FOO=bar`) をスキップして実コマンドを返す */
|
|
125
|
+
function getCommandName(tokens: Token[]): string | undefined {
|
|
126
|
+
for (const t of tokens) {
|
|
127
|
+
if (t.quoted === 'none' && /^[A-Za-z_][A-Za-z0-9_]*=/.test(t.value)) continue;
|
|
128
|
+
return t.value;
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 1 コマンド分のトークン列から書き込み先を抽出する。
|
|
135
|
+
* リダイレクト先はコマンド種別によらず検出する (全コマンド共通)。
|
|
136
|
+
*/
|
|
137
|
+
function extractFromSingleCommand(tokens: Token[]): string[] {
|
|
138
|
+
const results: string[] = [];
|
|
139
|
+
|
|
140
|
+
// 1) リダイレクト `>` / `>>` の右辺 (heredoc の `>` もこのパスで拾える)
|
|
141
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
142
|
+
const t = tokens[i];
|
|
143
|
+
if (t.quoted === 'none' && (t.value === '>' || t.value === '>>')) {
|
|
144
|
+
const next = tokens[i + 1];
|
|
145
|
+
if (next !== undefined) {
|
|
146
|
+
results.push(next.value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const cmd = getCommandName(tokens);
|
|
152
|
+
if (cmd === undefined) return results;
|
|
153
|
+
|
|
154
|
+
// コマンド別の特殊処理
|
|
155
|
+
const baseName = cmd.split('/').pop() ?? cmd;
|
|
156
|
+
|
|
157
|
+
if (baseName === 'tee') {
|
|
158
|
+
// `tee [-a] FILE...`
|
|
159
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
160
|
+
const t = tokens[i];
|
|
161
|
+
if (t.quoted === 'none' && t.value.startsWith('-')) continue;
|
|
162
|
+
if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '|' || t.value === '<')) break;
|
|
163
|
+
results.push(t.value);
|
|
164
|
+
}
|
|
165
|
+
} else if (baseName === 'sed') {
|
|
166
|
+
// `sed -i [''] SCRIPT FILE` — `-i` の直後の空文字列 (BSD) はバックアップ拡張子
|
|
167
|
+
const argTokens = tokens.slice(1);
|
|
168
|
+
let inPlace = false;
|
|
169
|
+
let skipBackupExt = false;
|
|
170
|
+
const positionals: Token[] = [];
|
|
171
|
+
for (let i = 0; i < argTokens.length; i += 1) {
|
|
172
|
+
const t = argTokens[i];
|
|
173
|
+
if (t.quoted === 'none' && t.value === '-i') {
|
|
174
|
+
inPlace = true;
|
|
175
|
+
skipBackupExt = true;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (t.quoted === 'none' && t.value.startsWith('-i')) {
|
|
179
|
+
// -iBAK
|
|
180
|
+
inPlace = true;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (t.quoted === 'none' && t.value.startsWith('-')) continue;
|
|
184
|
+
if (skipBackupExt && t.quoted !== 'none' && t.value === '') {
|
|
185
|
+
// BSD `sed -i ''` — 空文字はバックアップ拡張子扱いで無視
|
|
186
|
+
skipBackupExt = false;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
skipBackupExt = false;
|
|
190
|
+
positionals.push(t);
|
|
191
|
+
}
|
|
192
|
+
if (inPlace && positionals.length >= 2) {
|
|
193
|
+
// 先頭はスクリプト、残りはファイル
|
|
194
|
+
for (let i = 1; i < positionals.length; i += 1) {
|
|
195
|
+
results.push(positionals[i].value);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else if (baseName === 'cp' || baseName === 'mv') {
|
|
199
|
+
// 宛先は最後の非オプション引数
|
|
200
|
+
const positionals: Token[] = [];
|
|
201
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
202
|
+
const t = tokens[i];
|
|
203
|
+
if (t.quoted === 'none' && t.value.startsWith('-')) continue;
|
|
204
|
+
if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '|' || t.value === '<')) break;
|
|
205
|
+
positionals.push(t);
|
|
206
|
+
}
|
|
207
|
+
if (positionals.length >= 2) {
|
|
208
|
+
results.push(positionals[positionals.length - 1].value);
|
|
209
|
+
}
|
|
210
|
+
} else if (baseName === 'touch') {
|
|
211
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
212
|
+
const t = tokens[i];
|
|
213
|
+
if (t.quoted === 'none' && t.value.startsWith('-')) continue;
|
|
214
|
+
if (t.quoted === 'none' && (t.value === '>' || t.value === '>>' || t.value === '|' || t.value === '<')) break;
|
|
215
|
+
results.push(t.value);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return results;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export class BashWriteTargetExtractor {
|
|
223
|
+
/**
|
|
224
|
+
* Bash コマンド文字列から書き込み先ファイルパスを抽出する。
|
|
225
|
+
* 副作用なし、純粋関数相当。
|
|
226
|
+
*
|
|
227
|
+
* @param command Bash コマンド文字列
|
|
228
|
+
* @returns 書き込み先ファイルパスの配列 (重複除去済み・入力順)
|
|
229
|
+
*/
|
|
230
|
+
extract(command: string): readonly string[] {
|
|
231
|
+
if (typeof command !== 'string' || command.length === 0) {
|
|
232
|
+
return Object.freeze([]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const tokens = tokenize(command);
|
|
236
|
+
const groups = splitByOperators(tokens);
|
|
237
|
+
|
|
238
|
+
const collected: string[] = [];
|
|
239
|
+
for (const group of groups) {
|
|
240
|
+
const found = extractFromSingleCommand(group);
|
|
241
|
+
for (const path of found) {
|
|
242
|
+
collected.push(path);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 重複除去 (挿入順保持)
|
|
247
|
+
const seen = new Set<string>();
|
|
248
|
+
const unique: string[] = [];
|
|
249
|
+
for (const p of collected) {
|
|
250
|
+
if (p.length === 0) continue;
|
|
251
|
+
if (seen.has(p)) continue;
|
|
252
|
+
seen.add(p);
|
|
253
|
+
unique.push(p);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return Object.freeze(unique);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -169,7 +169,7 @@ export class AsyncHookToCliTranslator {
|
|
|
169
169
|
hasCommand(commandName: string): Promise<boolean>;
|
|
170
170
|
};
|
|
171
171
|
private readonly phaseGateQueryPort: {
|
|
172
|
-
checkGate(scope: WriteTargetScope): Promise<{
|
|
172
|
+
checkGate(scope: WriteTargetScope, targetFilePath?: string): Promise<{
|
|
173
173
|
hasPassed(): boolean;
|
|
174
174
|
getBlockers(): readonly string[];
|
|
175
175
|
getWarnings(): readonly string[];
|
|
@@ -181,7 +181,7 @@ export class AsyncHookToCliTranslator {
|
|
|
181
181
|
reentryGuard: ReentryGuard;
|
|
182
182
|
cliCommandRegistryPort: { hasCommand(commandName: string): Promise<boolean> };
|
|
183
183
|
phaseGateQueryPort?: {
|
|
184
|
-
checkGate(scope: WriteTargetScope): Promise<{
|
|
184
|
+
checkGate(scope: WriteTargetScope, targetFilePath?: string): Promise<{
|
|
185
185
|
hasPassed(): boolean;
|
|
186
186
|
getBlockers(): readonly string[];
|
|
187
187
|
getWarnings(): readonly string[];
|
|
@@ -222,7 +222,11 @@ export class AsyncHookToCliTranslator {
|
|
|
222
222
|
private async translatePreToolUse(event: PreToolUseEvent): Promise<HookTranslationResult> {
|
|
223
223
|
// Step 1: Protected file check (applies to all tools)
|
|
224
224
|
const additionalPatterns = await this.configQueryPort.getProtectedFilePatterns();
|
|
225
|
-
const
|
|
225
|
+
const exclusions = await this.configQueryPort.getProtectedFileExclusions();
|
|
226
|
+
const protectedFileList = ProtectedFileList.createWithAdditionalAndExclusions(
|
|
227
|
+
additionalPatterns,
|
|
228
|
+
exclusions,
|
|
229
|
+
);
|
|
226
230
|
|
|
227
231
|
const blockedPath = event.targetFilePaths.find((fp) => protectedFileList.matches(fp));
|
|
228
232
|
if (blockedPath !== undefined) {
|
|
@@ -256,7 +260,20 @@ export class AsyncHookToCliTranslator {
|
|
|
256
260
|
});
|
|
257
261
|
}
|
|
258
262
|
|
|
259
|
-
|
|
263
|
+
// Step 3: Check if phase gate is relaxed via quickMode.relaxedGates
|
|
264
|
+
const relaxedGates = await this.configQueryPort.getRelaxedGates();
|
|
265
|
+
if (relaxedGates.includes('phase-gate')) {
|
|
266
|
+
return HookTranslationResult.create({
|
|
267
|
+
shouldBlock: false,
|
|
268
|
+
cliArgs: [],
|
|
269
|
+
expectedExitCode: 0,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const phaseGateResult = await this.phaseGateQueryPort.checkGate(
|
|
274
|
+
detectedScope,
|
|
275
|
+
event.targetFilePaths[0],
|
|
276
|
+
);
|
|
260
277
|
if (!phaseGateResult.hasPassed()) {
|
|
261
278
|
return HookTranslationResult.block({
|
|
262
279
|
reason: 'PHASE_GATE',
|
|
@@ -69,6 +69,26 @@ export class ProtectedFileList {
|
|
|
69
69
|
return new ProtectedFileList(allPatterns);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
static createWithExclusions(exclusions: string[]): ProtectedFileList {
|
|
73
|
+
const filtered = DEFAULT_PATTERNS.filter((p) => !exclusions.includes(p));
|
|
74
|
+
if (filtered.length === 0) {
|
|
75
|
+
return new ProtectedFileList([...DEFAULT_PATTERNS]);
|
|
76
|
+
}
|
|
77
|
+
return new ProtectedFileList(filtered);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static createWithAdditionalAndExclusions(
|
|
81
|
+
additionalPatterns: string[],
|
|
82
|
+
exclusions: string[],
|
|
83
|
+
): ProtectedFileList {
|
|
84
|
+
const base = DEFAULT_PATTERNS.filter((p) => !exclusions.includes(p));
|
|
85
|
+
const allPatterns = [...base, ...additionalPatterns];
|
|
86
|
+
if (allPatterns.length === 0) {
|
|
87
|
+
return new ProtectedFileList([...DEFAULT_PATTERNS]);
|
|
88
|
+
}
|
|
89
|
+
return new ProtectedFileList(allPatterns);
|
|
90
|
+
}
|
|
91
|
+
|
|
72
92
|
matches(filePath: string): boolean {
|
|
73
93
|
if (filePath === '') return false;
|
|
74
94
|
return this.patterns.some((pattern) => {
|
package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @unit agent-integration
|
|
2
|
+
// @layer domain
|
|
3
|
+
|
|
4
|
+
export class StoryReflectionQueryResultInvariantError extends Error {
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'StoryReflectionQueryResultInvariantError';
|
|
8
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class StoryReflectionQueryResult {
|
|
13
|
+
readonly passed: boolean;
|
|
14
|
+
readonly blockers: readonly string[];
|
|
15
|
+
readonly warnings: readonly string[];
|
|
16
|
+
readonly skipped: boolean;
|
|
17
|
+
|
|
18
|
+
private constructor(
|
|
19
|
+
passed: boolean,
|
|
20
|
+
blockers: readonly string[],
|
|
21
|
+
warnings: readonly string[],
|
|
22
|
+
skipped: boolean,
|
|
23
|
+
) {
|
|
24
|
+
this.passed = passed;
|
|
25
|
+
this.blockers = Object.freeze([...blockers]);
|
|
26
|
+
this.warnings = Object.freeze([...warnings]);
|
|
27
|
+
this.skipped = skipped;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static pass(): StoryReflectionQueryResult {
|
|
31
|
+
return new StoryReflectionQueryResult(true, [], [], false);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static skipped(warnings: string[] = []): StoryReflectionQueryResult {
|
|
35
|
+
return new StoryReflectionQueryResult(true, [], warnings, true);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static block(blockers: string[], warnings: string[]): StoryReflectionQueryResult {
|
|
39
|
+
if (blockers.length === 0) {
|
|
40
|
+
throw new StoryReflectionQueryResultInvariantError(
|
|
41
|
+
'passed=falseの場合、blockersは1件以上必要です',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return new StoryReflectionQueryResult(false, blockers, warnings, false);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
hasPassed(): boolean {
|
|
49
|
+
return this.passed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getBlockers(): readonly string[] {
|
|
53
|
+
return this.blockers;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getWarnings(): readonly string[] {
|
|
57
|
+
return this.warnings;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
isSkipped(): boolean {
|
|
61
|
+
return this.skipped;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// @unit agent-integration
|
|
2
|
+
// @layer infrastructure
|
|
3
|
+
/**
|
|
4
|
+
* FileSystemStoryReflectionQueryAdapter
|
|
5
|
+
*
|
|
6
|
+
* agent-integration ドメインの `StoryReflectionQueryPort` を、
|
|
7
|
+
* phase-dependency-model 側の CheckStoryReflectionUseCase +
|
|
8
|
+
* FileSystemStoryReflectionAdapter + HarnessConfigPhaseConfigProvider を
|
|
9
|
+
* オーケストレートして実装するアダプタ。
|
|
10
|
+
*
|
|
11
|
+
* 役割:
|
|
12
|
+
* 1. phasegate.config.json を読み込み、PhaseConfigSection へ正規化
|
|
13
|
+
* 2. HarnessConfigPhaseConfigProvider 経由で storyReflection 設定を解決
|
|
14
|
+
* 3. StoryReflectionChecker + FileSystemStoryReflectionAdapter 経由で
|
|
15
|
+
* inception → product の @story-id 反映を検証
|
|
16
|
+
* 4. 結果を agent-integration の StoryReflectionQueryResult に写像
|
|
17
|
+
*
|
|
18
|
+
* 例外: 設定ファイルが存在しない/読めない場合は "skipped" を返し、
|
|
19
|
+
* hook 全体をブロックしない(Fail-safe)。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { readFile } from 'node:fs/promises';
|
|
23
|
+
|
|
24
|
+
import type { StoryReflectionQueryPort } from '../../domain/ports/story-reflection-query-port.js';
|
|
25
|
+
import { StoryReflectionQueryResult } from '../../domain/value-objects/story-reflection-query-result.js';
|
|
26
|
+
import { FileSystemStoryReflectionAdapter } from '../../../phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.js';
|
|
27
|
+
import { StoryReflectionChecker } from '../../../phase-dependency-model/domain/services/story-reflection-checker.js';
|
|
28
|
+
import { CheckStoryReflectionUseCase } from '../../../phase-dependency-model/application/usecases/check-story-reflection-usecase.js';
|
|
29
|
+
import {
|
|
30
|
+
HarnessConfigPhaseConfigProvider,
|
|
31
|
+
type PhaseConfigSection,
|
|
32
|
+
type PhasePresetInput,
|
|
33
|
+
type StoryReflectionSectionInput,
|
|
34
|
+
} from '../../../phase-dependency-model/infrastructure/config/harness-config-phase-config-provider.js';
|
|
35
|
+
import type { StoryReflectionResult } from '../../../phase-dependency-model/domain/values/story-reflection-result.js';
|
|
36
|
+
|
|
37
|
+
export interface FileSystemStoryReflectionQueryAdapterDeps {
|
|
38
|
+
readonly rootDir: string;
|
|
39
|
+
readonly configPath: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface RawPhaseDependenciesSection {
|
|
43
|
+
readonly preset?: PhasePresetInput;
|
|
44
|
+
readonly override?: boolean;
|
|
45
|
+
readonly storyReflection?: StoryReflectionSectionInput;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface RawConfigDocument {
|
|
49
|
+
readonly phaseDependencies?: RawPhaseDependenciesSection;
|
|
50
|
+
readonly reporting?: { readonly outputDir?: string };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class FileSystemStoryReflectionQueryAdapter
|
|
54
|
+
implements StoryReflectionQueryPort
|
|
55
|
+
{
|
|
56
|
+
private readonly rootDir: string;
|
|
57
|
+
private readonly configPath: string;
|
|
58
|
+
|
|
59
|
+
constructor(deps: FileSystemStoryReflectionQueryAdapterDeps) {
|
|
60
|
+
this.rootDir = deps.rootDir;
|
|
61
|
+
this.configPath = deps.configPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async checkReflection(unitId: string): Promise<StoryReflectionQueryResult> {
|
|
65
|
+
let raw: RawConfigDocument;
|
|
66
|
+
try {
|
|
67
|
+
const content = await readFile(this.configPath, 'utf8');
|
|
68
|
+
raw = JSON.parse(content) as RawConfigDocument;
|
|
69
|
+
} catch {
|
|
70
|
+
// 設定が読めない場合は fail-safe で skipped 扱い
|
|
71
|
+
return StoryReflectionQueryResult.skipped();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const phaseConfigSection: PhaseConfigSection = {
|
|
75
|
+
customization: {
|
|
76
|
+
preset: raw.phaseDependencies?.preset,
|
|
77
|
+
overrideEnabled: raw.phaseDependencies?.override ?? false,
|
|
78
|
+
},
|
|
79
|
+
storyReflection: raw.phaseDependencies?.storyReflection,
|
|
80
|
+
reportingOutputDir: raw.reporting?.outputDir,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const provider = new HarnessConfigPhaseConfigProvider({
|
|
84
|
+
config: phaseConfigSection,
|
|
85
|
+
defaultOutputDir: raw.reporting?.outputDir ?? '.harness/reports',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const config = await provider.getStoryReflectionConfig();
|
|
89
|
+
|
|
90
|
+
if (!config.enabled) {
|
|
91
|
+
return StoryReflectionQueryResult.skipped();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const fsAdapter = new FileSystemStoryReflectionAdapter({ rootDir: this.rootDir });
|
|
95
|
+
const checker = new StoryReflectionChecker(fsAdapter);
|
|
96
|
+
const useCase = new CheckStoryReflectionUseCase({ checker });
|
|
97
|
+
|
|
98
|
+
const result = await useCase.execute({ unitId, config });
|
|
99
|
+
|
|
100
|
+
return FileSystemStoryReflectionQueryAdapter.mapResult(result);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static mapResult(
|
|
104
|
+
result: StoryReflectionResult,
|
|
105
|
+
): StoryReflectionQueryResult {
|
|
106
|
+
const warnings = result.warnings.map(
|
|
107
|
+
(w) =>
|
|
108
|
+
`${w.storyId}: ${w.productPath} に @story-id ${w.storyId} が未反映 (optional, inception: ${w.inceptionPath})`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (!result.isBlocked()) {
|
|
112
|
+
return StoryReflectionQueryResult.pass();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const blockers = result.violations.map(
|
|
116
|
+
(v) =>
|
|
117
|
+
`${v.productPath} に @story-id ${v.storyId} が反映されていません (inception: ${v.inceptionPath})`,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return StoryReflectionQueryResult.block(blockers, warnings);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -31,9 +31,19 @@ interface HarnessesSection {
|
|
|
31
31
|
deadCodeGC?: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
interface ProtectedFilesSection {
|
|
35
|
+
exclude?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface QuickModeSection {
|
|
39
|
+
relaxedGates?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
interface HarnessConfigDocument {
|
|
35
43
|
harnesses?: HarnessesSection;
|
|
36
44
|
project?: ProjectSection;
|
|
45
|
+
protectedFiles?: ProtectedFilesSection;
|
|
46
|
+
quickMode?: QuickModeSection;
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
export class HarnessConfigConfigQueryAdapter implements ConfigQueryPort {
|
|
@@ -77,6 +87,16 @@ export class HarnessConfigConfigQueryAdapter implements ConfigQueryPort {
|
|
|
77
87
|
return [];
|
|
78
88
|
}
|
|
79
89
|
|
|
90
|
+
async getProtectedFileExclusions(): Promise<string[]> {
|
|
91
|
+
const config = this.loadConfig();
|
|
92
|
+
return config.protectedFiles?.exclude ?? [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getRelaxedGates(): Promise<readonly string[]> {
|
|
96
|
+
const config = this.loadConfig();
|
|
97
|
+
return config.quickMode?.relaxedGates ?? [];
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
getProjectPaths(): ProjectPaths {
|
|
81
101
|
const config = this.loadConfig();
|
|
82
102
|
const paths = config.project?.paths;
|
package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts
CHANGED
|
@@ -4,16 +4,26 @@
|
|
|
4
4
|
import type { PhaseGateQueryPort } from '../../domain/ports/phase-gate-query-port.js';
|
|
5
5
|
import type { WriteTargetScope } from '../../domain/value-objects/write-target-scope.js';
|
|
6
6
|
import { PhaseGateQueryResult } from '../../domain/value-objects/phase-gate-query-result.js';
|
|
7
|
+
import { toPhaseConfigSection } from '../../../config-foundation/application/mappers/phase-config-section-mapper.js';
|
|
8
|
+
import { createConfigFoundationModule } from '../../../config-foundation/composition-root.js';
|
|
9
|
+
import { ConfigValidationError } from '../../../config-foundation/domain/errors/config-validation-error.js';
|
|
7
10
|
|
|
8
11
|
export class PhaseGateQueryAdapter implements PhaseGateQueryPort {
|
|
9
|
-
async checkGate(scope: WriteTargetScope): Promise<PhaseGateQueryResult> {
|
|
12
|
+
async checkGate(scope: WriteTargetScope, targetFilePath?: string): Promise<PhaseGateQueryResult> {
|
|
10
13
|
try {
|
|
14
|
+
const configModule = createConfigFoundationModule();
|
|
15
|
+
const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
|
|
11
16
|
const { createPhaseDependencyModelModule } = await import('../../../phase-dependency-model/composition-root.js');
|
|
12
|
-
const mod = createPhaseDependencyModelModule({
|
|
17
|
+
const mod = createPhaseDependencyModelModule({
|
|
18
|
+
rootDir: process.cwd(),
|
|
19
|
+
phaseConfig: toPhaseConfigSection(resolvedConfig.config),
|
|
20
|
+
reportOutputDir: resolvedConfig.config.reporting.outputDir,
|
|
21
|
+
});
|
|
13
22
|
const result = await mod.checkPhaseGateCommandHandler.execute({
|
|
14
23
|
targetLevel: scope.level,
|
|
15
24
|
unitId: scope.unitId,
|
|
16
25
|
storyId: scope.storyId,
|
|
26
|
+
targetFilePath,
|
|
17
27
|
});
|
|
18
28
|
|
|
19
29
|
if (result.exitCode === 0) {
|
|
@@ -25,7 +35,11 @@ export class PhaseGateQueryAdapter implements PhaseGateQueryPort {
|
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
return PhaseGateQueryResult.create(true, [], ['phase gate check returned error']);
|
|
28
|
-
} catch {
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof ConfigValidationError) {
|
|
40
|
+
return PhaseGateQueryResult.create(false, [error.message], []);
|
|
41
|
+
}
|
|
42
|
+
|
|
29
43
|
return PhaseGateQueryResult.create(true, [], ['phase-dependency-model not available']);
|
|
30
44
|
}
|
|
31
45
|
}
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { HandlePreToolUseUseCase } from '../application/usecases/handle-pre-tool-use-usecase.js';
|
|
11
|
+
import { BashWriteTargetExtractor } from '../domain/services/bash-write-target-extractor.js';
|
|
11
12
|
import { HarnessConfigConfigQueryAdapter } from '../infrastructure/adapters/harness-config-config-query-adapter.js';
|
|
12
13
|
import { PhaseGateQueryAdapter } from '../infrastructure/adapters/phase-gate-query-adapter.js';
|
|
14
|
+
import { FileSystemStoryReflectionQueryAdapter } from '../infrastructure/adapters/file-system-story-reflection-query-adapter.js';
|
|
13
15
|
import * as path from 'node:path';
|
|
14
16
|
import * as fs from 'node:fs/promises';
|
|
15
17
|
|
|
@@ -20,6 +22,7 @@ interface PreToolUseHookInput {
|
|
|
20
22
|
path?: string;
|
|
21
23
|
file_path?: string;
|
|
22
24
|
paths?: string[];
|
|
25
|
+
command?: string;
|
|
23
26
|
[key: string]: unknown;
|
|
24
27
|
};
|
|
25
28
|
}
|
|
@@ -91,13 +94,37 @@ async function main(): Promise<void> {
|
|
|
91
94
|
targetFilePaths.push(...input.tool_input.paths.map(toRelative));
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
// Bash 経由書き込みのフェーズゲート対応 (A-2.5)
|
|
98
|
+
// Bash command 文字列からリダイレクト・tee・sed -i・cp・mv・touch 等の
|
|
99
|
+
// 書き込み先ファイルパスを抽出し、フェーズゲートチェック対象に含める。
|
|
100
|
+
// Bash 書き込みを検出した場合、effectiveToolName を 'Write' に偽装して
|
|
101
|
+
// translator の WRITE_TOOLS チェックを通過させる(Bash のままではフェーズゲートが
|
|
102
|
+
// スキップされるため)。
|
|
103
|
+
let effectiveToolName = toolName;
|
|
104
|
+
if (toolName === 'Bash' && typeof input.tool_input?.command === 'string') {
|
|
105
|
+
const extractor = new BashWriteTargetExtractor();
|
|
106
|
+
const bashTargets = extractor.extract(input.tool_input.command);
|
|
107
|
+
if (bashTargets.length > 0) {
|
|
108
|
+
targetFilePaths.push(...bashTargets.map(toRelative));
|
|
109
|
+
effectiveToolName = 'Write';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
try {
|
|
95
114
|
const configPath = await findConfigPath();
|
|
96
115
|
const configQueryPort = new HarnessConfigConfigQueryAdapter(configPath);
|
|
97
116
|
const phaseGateQueryPort = new PhaseGateQueryAdapter();
|
|
98
|
-
const
|
|
117
|
+
const storyReflectionQueryPort = new FileSystemStoryReflectionQueryAdapter({
|
|
118
|
+
rootDir: path.dirname(configPath),
|
|
119
|
+
configPath,
|
|
120
|
+
});
|
|
121
|
+
const useCase = new HandlePreToolUseUseCase({
|
|
122
|
+
configQueryPort,
|
|
123
|
+
phaseGateQueryPort,
|
|
124
|
+
storyReflectionQueryPort,
|
|
125
|
+
});
|
|
99
126
|
|
|
100
|
-
const output = await useCase.execute({ toolName, targetFilePaths });
|
|
127
|
+
const output = await useCase.execute({ toolName: effectiveToolName, targetFilePaths });
|
|
101
128
|
|
|
102
129
|
if (output.shouldBlock) {
|
|
103
130
|
const msg = output.error?.message
|