phasegate 0.335.0 → 0.341.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.ja.md +22 -9
- package/README.md +28 -17
- package/docs/ADR/038-config-state-operation-permission-policy.md +5 -2
- package/docs/ADR/041-non-excludable-agent-trust-roots.md +68 -0
- package/docs/folder_management_rules.md +10 -1
- package/docs/guide/antigravity-integration.md +26 -0
- package/docs/guide/cli-reference.md +27 -7
- package/docs/guide/codex-integration.md +44 -107
- package/docs/guide/configuration.md +28 -2
- package/docs/guide/grok-integration.md +26 -0
- package/docs/guide/hooks-integration.md +34 -5
- package/docs/guide/installation.md +4 -2
- package/docs/guide/setup-artifacts.md +4 -4
- package/docs/guide/skills-overview.md +4 -1
- package/package.json +2 -1
- package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +1 -1
- package/scripts/harness/agent-integration/application/dto/normalized-pre-tool-use-request.ts +32 -0
- package/scripts/harness/agent-integration/application/usecases/handle-post-tool-use-usecase.ts +4 -0
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +38 -3
- package/scripts/harness/agent-integration/application/usecases/handle-stop-usecase.ts +3 -9
- package/scripts/harness/agent-integration/domain/ports/full-mode-requirement-query-port.ts +3 -1
- package/scripts/harness/agent-integration/domain/ports/story-reflection-query-port.ts +2 -0
- package/scripts/harness/agent-integration/domain/services/apply-patch-write-target-extractor.ts +98 -0
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +6 -52
- package/scripts/harness/agent-integration/domain/services/hook-to-cli-translator.ts +8 -5
- package/scripts/harness/agent-integration/domain/value-objects/hook-translation-result.ts +2 -2
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +35 -18
- package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts +10 -2
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +12 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/child-process-cli-executor-adapter.ts +64 -13
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +108 -2
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +8 -6
- package/scripts/harness/agent-integration/presentation/hook-skip-event-recorder.ts +3 -0
- package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +1 -1
- package/scripts/harness/agent-integration/presentation/post-tool-use-feedback.ts +39 -0
- package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +21 -27
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +126 -70
- package/scripts/harness/agent-integration/presentation/pre-tool-use-payload-normalizer.ts +228 -0
- package/scripts/harness/agent-integration/presentation/pre-tool-use-response-renderer.ts +38 -0
- package/scripts/harness/agent-integration/presentation/stop-hook.ts +1 -3
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/biome-cli-executor-adapter.ts +21 -1
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +5 -0
- package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +2 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +15 -0
- package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +6 -3
- package/scripts/harness/harness-api/domain/ports/biome-lint-port.ts +2 -1
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +12 -1
- package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +10 -2
- package/scripts/harness/installation/application/bundled-skill-selection.ts +28 -2
- package/scripts/harness/installation/application/checks/antigravity-hook-missing-check.ts +79 -0
- package/scripts/harness/installation/application/checks/check-utils.ts +2 -2
- package/scripts/harness/installation/application/checks/codex-hook-missing-check.ts +70 -6
- package/scripts/harness/installation/application/checks/grok-hook-missing-check.ts +79 -0
- package/scripts/harness/installation/application/checks/husky-runtime-inactive-check.ts +33 -0
- package/scripts/harness/installation/application/named-hook-json.ts +17 -0
- package/scripts/harness/installation/application/operator-notice.ts +42 -0
- package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +18 -7
- package/scripts/harness/installation/application/usecases/run-install.ts +157 -57
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +148 -31
- package/scripts/harness/installation/application/usecases/run-uninstall.ts +149 -25
- package/scripts/harness/installation/composition-root.ts +10 -0
- package/scripts/harness/installation/domain/agent-target.ts +39 -0
- package/scripts/harness/installation/domain/check-id.ts +5 -0
- package/scripts/harness/installation/domain/husky-runtime-state.ts +34 -0
- package/scripts/harness/installation/domain/ports/git-hooks-runtime-probe.ts +9 -0
- package/scripts/harness/installation/domain/repair-table.ts +5 -0
- package/scripts/harness/installation/infrastructure/adapters/git-hooks-runtime-probe-adapter.ts +62 -0
- package/scripts/harness/installation/presentation/cli/install-handler.ts +25 -5
- package/scripts/harness/installation/presentation/cli/reconcile-handler.ts +7 -1
- package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +27 -0
- package/scripts/harness/main.ts +227 -128
- package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +48 -5
- package/scripts/harness/phase-dependency-model/domain/services/work-item-reflection-scope-resolver.ts +64 -0
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-mapping.ts +8 -2
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +6 -1
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-work-item-dependency-catalog.ts +73 -0
- package/scripts/harness/quick-mode/application/dto/change-category-classification-contract.ts +5 -1
- package/scripts/harness/quick-mode/application/dto/quick-mode-eligibility-contract.ts +1 -1
- package/scripts/harness/quick-mode/application/ports/change-risk-advisory-port.ts +16 -0
- package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +3 -1
- package/scripts/harness/quick-mode/composition-root.ts +5 -1
- package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +13 -2
- package/scripts/harness/quick-mode/domain/types/rejection-rule.ts +1 -1
- package/scripts/harness/quick-mode/infrastructure/adapters/snapshot-risk-advisory-adapter.ts +118 -0
- package/scripts/harness/quick-mode/presentation/formatters/change-category-formatter.ts +7 -0
- package/scripts/harness/quick-mode/presentation/handlers/check-change-category-handler.ts +12 -1
- package/scripts/harness/regression-suite/infrastructure/adapters/vitest-test-runner-adapter.ts +13 -3
- package/scripts/harness/setup/skill-deployer.ts +3 -45
- package/scripts/harness/skill-quality/application/dto/run-plan-checker-loop-output.ts +1 -0
- package/scripts/harness/skill-quality/application/usecases/apply-cascade-update-usecase.ts +9 -4
- package/scripts/harness/skill-quality/application/usecases/execute-tdd-cycle-usecase.ts +2 -1
- package/scripts/harness/skill-quality/application/usecases/run-plan-checker-loop-usecase.ts +8 -0
- package/scripts/harness/skill-quality/composition-root.ts +14 -7
- package/scripts/harness/skill-quality/domain/ports/plan-check-executor-port.ts +2 -0
- package/scripts/harness/skill-quality/domain/value-objects/cascade-update-target.ts +15 -1
- package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +9 -1
- package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +18 -2
- package/scripts/harness/skill-quality/presentation/handlers/apply-cascade-update-handler.ts +8 -2
- package/scripts/harness/skill-quality/presentation/handlers/execute-tdd-cycle-handler.ts +4 -3
- package/scripts/harness/skill-quality/presentation/handlers/run-plan-checker-loop-handler.ts +11 -4
- package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +41 -0
- package/skills/README.md +7 -0
- package/skills/cascade-updater/SKILL.md +37 -109
- package/skills/cascade-updater/references//345/261/244/345/210/245/345/210/244/345/256/232/343/202/254/343/202/244/343/203/211.md +5 -3
- package/skills/release-publisher/SKILL.md +3 -1
- package/skills/skill-creator/SKILL.md +8 -9
- package/templates/.agents/hooks.json +16 -0
- package/templates/.claude/scripts/analyze-errors-hook.sh +2 -2
- package/templates/.claude/settings.json +5 -3
- package/templates/.codex/hooks.json +2 -2
|
@@ -7,24 +7,34 @@
|
|
|
7
7
|
* @work-item-id WI-345
|
|
8
8
|
* @work-item-id WI-347
|
|
9
9
|
* @work-item-id WI-376
|
|
10
|
+
* @work-item-id WI-384
|
|
11
|
+
* @work-item-id WI-385
|
|
12
|
+
* @work-item-id WI-386
|
|
10
13
|
*
|
|
11
14
|
* PreToolUse Hook Adapter
|
|
12
15
|
* Claude Code の PreToolUse Hook エントリポイント
|
|
13
16
|
* stdin からJSON を読み取り、HandlePreToolUseUseCase を呼び出す
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import
|
|
19
|
+
import * as fs from "node:fs/promises";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import { createQuickModeCompositionRoot } from "../../quick-mode/composition-root.js";
|
|
22
|
+
import type {
|
|
23
|
+
CanonicalPreToolUseInput,
|
|
24
|
+
HookResponseProfile,
|
|
25
|
+
} from "../application/dto/normalized-pre-tool-use-request.js";
|
|
26
|
+
import { HandlePreToolUseUseCase } from "../application/usecases/handle-pre-tool-use-usecase.js";
|
|
27
|
+
import { ApplyPatchWriteTargetExtractor } from "../domain/services/apply-patch-write-target-extractor.js";
|
|
28
|
+
import { BashWriteTargetExtractor } from "../domain/services/bash-write-target-extractor.js";
|
|
29
|
+
import { CiGovernanceBaselineGrandfatherAdapter } from "../infrastructure/adapters/ci-governance-baseline-grandfather-adapter.js";
|
|
30
|
+
import { FileSystemFullModeSessionQueryAdapter } from "../infrastructure/adapters/file-system-full-mode-session-query-adapter.js";
|
|
31
|
+
import { FileSystemStoryReflectionQueryAdapter } from "../infrastructure/adapters/file-system-story-reflection-query-adapter.js";
|
|
32
|
+
import { HarnessConfigConfigQueryAdapter } from "../infrastructure/adapters/harness-config-config-query-adapter.js";
|
|
33
|
+
import { HarnessErrorGuidanceAdapter } from "../infrastructure/adapters/harness-error-guidance-adapter.js";
|
|
34
|
+
import { PhaseGateQueryAdapter } from "../infrastructure/adapters/phase-gate-query-adapter.js";
|
|
35
|
+
import { QuickModeFullModeRequirementAdapter } from "../infrastructure/adapters/quick-mode-full-mode-requirement-adapter.js";
|
|
36
|
+
import { PreToolUsePayloadNormalizer } from "./pre-tool-use-payload-normalizer.js";
|
|
37
|
+
import { PreToolUseResponseRenderer } from "./pre-tool-use-response-renderer.js";
|
|
28
38
|
|
|
29
39
|
/**
|
|
30
40
|
* WI-376 (ADR-039): 呼び出し元 skill 名を受け取るフィールド(caller_skill)は持たない。
|
|
@@ -33,23 +43,13 @@ import * as fs from 'node:fs/promises';
|
|
|
33
43
|
*/
|
|
34
44
|
interface PreToolUseHookInput {
|
|
35
45
|
cwd?: string;
|
|
36
|
-
tool_name
|
|
37
|
-
tool_input
|
|
38
|
-
path?: string;
|
|
39
|
-
file_path?: string;
|
|
40
|
-
paths?: string[];
|
|
41
|
-
command?: string;
|
|
42
|
-
content?: string;
|
|
43
|
-
old_string?: string;
|
|
44
|
-
new_string?: string;
|
|
45
|
-
old_str?: string;
|
|
46
|
-
new_str?: string;
|
|
47
|
-
[key: string]: unknown;
|
|
48
|
-
};
|
|
46
|
+
tool_name: string;
|
|
47
|
+
tool_input: CanonicalPreToolUseInput;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
interface TargetChange {
|
|
52
51
|
filePath: string;
|
|
52
|
+
changeKind?: "CREATE" | "MODIFY" | "DELETE";
|
|
53
53
|
beforeContent?: string | null;
|
|
54
54
|
afterContent?: string | null;
|
|
55
55
|
}
|
|
@@ -59,15 +59,15 @@ async function readStdin(): Promise<string> {
|
|
|
59
59
|
for await (const chunk of process.stdin) {
|
|
60
60
|
chunks.push(chunk as Buffer);
|
|
61
61
|
}
|
|
62
|
-
return Buffer.concat(chunks).toString(
|
|
62
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
async function findConfigPath(startDir: string): Promise<string> {
|
|
66
66
|
let dir = startDir;
|
|
67
67
|
while (true) {
|
|
68
68
|
const candidates = [
|
|
69
|
-
path.join(dir,
|
|
70
|
-
path.join(dir,
|
|
69
|
+
path.join(dir, "phasegate.config.json"),
|
|
70
|
+
path.join(dir, ".phasegate-local", "phasegate.config.json"),
|
|
71
71
|
];
|
|
72
72
|
for (const candidate of candidates) {
|
|
73
73
|
try {
|
|
@@ -79,18 +79,22 @@ async function findConfigPath(startDir: string): Promise<string> {
|
|
|
79
79
|
if (parent === dir) break;
|
|
80
80
|
dir = parent;
|
|
81
81
|
}
|
|
82
|
-
return path.join(startDir,
|
|
82
|
+
return path.join(startDir, "phasegate.config.json");
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function projectRootForConfig(configPath: string): string {
|
|
86
86
|
const configDir = path.dirname(configPath);
|
|
87
|
-
return path.basename(configDir) ===
|
|
87
|
+
return path.basename(configDir) === ".phasegate-local" ? path.dirname(configDir) : configDir;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function isProjectExternalPath(filePath: string, cwd: string, projectRoot: string): boolean {
|
|
91
91
|
const resolvedPath = path.resolve(cwd, filePath);
|
|
92
92
|
const relativePath = path.relative(projectRoot, resolvedPath);
|
|
93
|
-
return relativePath ===
|
|
93
|
+
return relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeProjectPath(filePath: string, cwd: string, projectRoot: string): string {
|
|
97
|
+
return path.relative(projectRoot, path.resolve(cwd, filePath)).split(path.sep).join("/");
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
async function main(): Promise<void> {
|
|
@@ -98,29 +102,41 @@ async function main(): Promise<void> {
|
|
|
98
102
|
try {
|
|
99
103
|
raw = await readStdin();
|
|
100
104
|
} catch {
|
|
101
|
-
process.stderr.write(
|
|
105
|
+
process.stderr.write("stdin読み取りエラー\n");
|
|
102
106
|
process.exit(2);
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
let
|
|
109
|
+
let parsed: unknown;
|
|
106
110
|
try {
|
|
107
|
-
|
|
111
|
+
parsed = JSON.parse(raw) as unknown;
|
|
108
112
|
} catch {
|
|
109
113
|
process.stderr.write(`不正なJSONです: ${raw}\n`);
|
|
110
114
|
process.exit(2);
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
const normalizer = new PreToolUsePayloadNormalizer();
|
|
118
|
+
const normalized = normalizer.normalize(parsed);
|
|
119
|
+
if (!normalized.ok) {
|
|
120
|
+
if (normalized.responseProfile === undefined) {
|
|
121
|
+
process.stderr.write(`${normalized.reason}\n`);
|
|
122
|
+
process.exit(2);
|
|
123
|
+
}
|
|
124
|
+
exitWithRenderedResponse(new PreToolUseResponseRenderer().deny(normalized.responseProfile, normalized.reason));
|
|
117
125
|
}
|
|
126
|
+
const request = normalized.request;
|
|
127
|
+
const responseProfile = request.responseProfile;
|
|
128
|
+
const input: PreToolUseHookInput = {
|
|
129
|
+
cwd: request.cwd,
|
|
130
|
+
tool_name: request.toolName,
|
|
131
|
+
tool_input: request.toolInput,
|
|
132
|
+
};
|
|
133
|
+
const toolName = input.tool_name;
|
|
118
134
|
|
|
119
135
|
const cwd = path.resolve(input.cwd ?? process.cwd());
|
|
120
136
|
const toRelative = (p: string): string => {
|
|
121
137
|
if (path.isAbsolute(p)) {
|
|
122
138
|
const rel = path.relative(cwd, p);
|
|
123
|
-
return rel.startsWith(
|
|
139
|
+
return rel.startsWith("..") ? p : rel;
|
|
124
140
|
}
|
|
125
141
|
return p;
|
|
126
142
|
};
|
|
@@ -145,7 +161,26 @@ async function main(): Promise<void> {
|
|
|
145
161
|
// translator の WRITE_TOOLS チェックを通過させる(Bash のままではフェーズゲートが
|
|
146
162
|
// スキップされるため)。
|
|
147
163
|
let effectiveToolName = toolName;
|
|
148
|
-
if (toolName ===
|
|
164
|
+
if (toolName === "apply_patch") {
|
|
165
|
+
const command = input.tool_input?.command;
|
|
166
|
+
if (typeof command !== "string" || command.length === 0) {
|
|
167
|
+
deny(responseProfile, "apply_patch の tool_input.command が必要です");
|
|
168
|
+
}
|
|
169
|
+
const extractor = new ApplyPatchWriteTargetExtractor();
|
|
170
|
+
const patchTargets = extractor.extract(command);
|
|
171
|
+
if (patchTargets.length === 0) {
|
|
172
|
+
deny(responseProfile, "apply_patch command から書き込み対象を抽出できませんでした");
|
|
173
|
+
}
|
|
174
|
+
targetFilePaths.push(...patchTargets.map((target) => toRelative(target.filePath)));
|
|
175
|
+
targetChanges.push(
|
|
176
|
+
...patchTargets.map((target) => ({
|
|
177
|
+
filePath: toRelative(target.filePath),
|
|
178
|
+
changeKind: target.changeKind,
|
|
179
|
+
})),
|
|
180
|
+
);
|
|
181
|
+
effectiveToolName = "Write";
|
|
182
|
+
}
|
|
183
|
+
if (toolName === "Bash" && typeof input.tool_input?.command === "string") {
|
|
149
184
|
const extractor = new BashWriteTargetExtractor();
|
|
150
185
|
const bashTargets = extractor.extract(input.tool_input.command);
|
|
151
186
|
if (bashTargets.length > 0) {
|
|
@@ -160,19 +195,22 @@ async function main(): Promise<void> {
|
|
|
160
195
|
recordedTargetPaths.add(change.filePath);
|
|
161
196
|
}
|
|
162
197
|
}
|
|
163
|
-
effectiveToolName =
|
|
198
|
+
effectiveToolName = "Write";
|
|
164
199
|
}
|
|
165
200
|
}
|
|
166
201
|
|
|
167
202
|
try {
|
|
168
203
|
const configPath = await findConfigPath(cwd);
|
|
169
204
|
const projectRoot = projectRootForConfig(configPath);
|
|
170
|
-
const projectTargetFilePaths = targetFilePaths
|
|
171
|
-
(filePath) => !isProjectExternalPath(filePath, cwd, projectRoot)
|
|
172
|
-
|
|
173
|
-
const projectTargetChanges = targetChanges
|
|
174
|
-
(change) => !isProjectExternalPath(change.filePath, cwd, projectRoot)
|
|
175
|
-
|
|
205
|
+
const projectTargetFilePaths = targetFilePaths
|
|
206
|
+
.filter((filePath) => !isProjectExternalPath(filePath, cwd, projectRoot))
|
|
207
|
+
.map((filePath) => normalizeProjectPath(filePath, cwd, projectRoot));
|
|
208
|
+
const projectTargetChanges = targetChanges
|
|
209
|
+
.filter((change) => !isProjectExternalPath(change.filePath, cwd, projectRoot))
|
|
210
|
+
.map((change) => ({
|
|
211
|
+
...change,
|
|
212
|
+
filePath: normalizeProjectPath(change.filePath, cwd, projectRoot),
|
|
213
|
+
}));
|
|
176
214
|
const configQueryPort = new HarnessConfigConfigQueryAdapter(configPath);
|
|
177
215
|
const phaseGateQueryPort = new PhaseGateQueryAdapter();
|
|
178
216
|
const storyReflectionQueryPort = new FileSystemStoryReflectionQueryAdapter({
|
|
@@ -211,40 +249,56 @@ async function main(): Promise<void> {
|
|
|
211
249
|
});
|
|
212
250
|
|
|
213
251
|
if (output.shouldBlock) {
|
|
214
|
-
const msg =
|
|
215
|
-
?? `ファイル保護によりブロックされました: ${output.blockedFilePath ??
|
|
216
|
-
|
|
217
|
-
process.exit(2);
|
|
252
|
+
const msg =
|
|
253
|
+
output.error?.message ?? `ファイル保護によりブロックされました: ${output.blockedFilePath ?? "不明なファイル"}`;
|
|
254
|
+
deny(responseProfile, msg);
|
|
218
255
|
}
|
|
219
256
|
|
|
220
257
|
// Quick Mode が write を許可した場合に visibility を上げる informational notice。
|
|
221
258
|
// exit 0 は維持し semantics は変えない。WI-087 finding #3。
|
|
222
259
|
if (output.quickModeAllowed !== undefined) {
|
|
223
260
|
const cat = output.quickModeAllowed.dominantCategory;
|
|
224
|
-
const suffix = cat !== undefined && cat !==
|
|
261
|
+
const suffix = cat !== undefined && cat !== "" ? `, category=${cat}` : "";
|
|
225
262
|
process.stderr.write(`phasegate: write allowed (Quick Mode${suffix})\n`);
|
|
226
263
|
}
|
|
227
264
|
if (output.fullModeSessionAllowed !== undefined) {
|
|
228
265
|
const session = output.fullModeSessionAllowed;
|
|
229
|
-
const workItem = session.workItemId !== undefined ? `, workItem=${session.workItemId}` :
|
|
230
|
-
const unit = session.unit !== undefined ? `, unit=${session.unit}` :
|
|
266
|
+
const workItem = session.workItemId !== undefined ? `, workItem=${session.workItemId}` : "";
|
|
267
|
+
const unit = session.unit !== undefined ? `, unit=${session.unit}` : "";
|
|
231
268
|
process.stderr.write(`phasegate: write allowed (Full Mode session${workItem}${unit})\n`);
|
|
269
|
+
for (const warning of output.storyReflectionWarnings ?? []) {
|
|
270
|
+
process.stderr.write(`phasegate: dependency reflection warning (advisory): ${warning}\n`);
|
|
271
|
+
}
|
|
232
272
|
}
|
|
233
273
|
|
|
234
274
|
process.exit(0);
|
|
235
275
|
} catch (error) {
|
|
236
|
-
|
|
237
|
-
process.exit(2);
|
|
276
|
+
deny(responseProfile, `実行エラー: ${String(error)}`);
|
|
238
277
|
}
|
|
239
278
|
}
|
|
240
279
|
|
|
280
|
+
function exitWithRenderedResponse(response: {
|
|
281
|
+
readonly stdout: string;
|
|
282
|
+
readonly stderr: string;
|
|
283
|
+
readonly exitCode: 0 | 2;
|
|
284
|
+
}): never {
|
|
285
|
+
if (response.stdout.length > 0) process.stdout.write(response.stdout);
|
|
286
|
+
if (response.stderr.length > 0) process.stderr.write(response.stderr);
|
|
287
|
+
process.exit(response.exitCode);
|
|
288
|
+
throw new Error("process.exit returned unexpectedly");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function deny(profile: HookResponseProfile, reason: string): never {
|
|
292
|
+
exitWithRenderedResponse(new PreToolUseResponseRenderer().deny(profile, reason));
|
|
293
|
+
}
|
|
294
|
+
|
|
241
295
|
async function buildTargetChanges(
|
|
242
296
|
input: PreToolUseHookInput,
|
|
243
297
|
cwd: string,
|
|
244
298
|
toRelative: (p: string) => string,
|
|
245
299
|
): Promise<TargetChange[]> {
|
|
246
300
|
const toolInput = input.tool_input;
|
|
247
|
-
if (toolInput == null || typeof toolInput !==
|
|
301
|
+
if (toolInput == null || typeof toolInput !== "object") {
|
|
248
302
|
return [];
|
|
249
303
|
}
|
|
250
304
|
|
|
@@ -254,18 +308,20 @@ async function buildTargetChanges(
|
|
|
254
308
|
}
|
|
255
309
|
|
|
256
310
|
const filePath = toRelative(rawPath);
|
|
257
|
-
const oldString =
|
|
258
|
-
const newString =
|
|
259
|
-
if (typeof oldString ===
|
|
311
|
+
const oldString = toolInput.old_string;
|
|
312
|
+
const newString = toolInput.new_string;
|
|
313
|
+
if (typeof oldString === "string" && typeof newString === "string") {
|
|
260
314
|
return [{ filePath, beforeContent: oldString, afterContent: newString }];
|
|
261
315
|
}
|
|
262
316
|
|
|
263
|
-
if (typeof toolInput.content ===
|
|
264
|
-
return [
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
317
|
+
if (typeof toolInput.content === "string") {
|
|
318
|
+
return [
|
|
319
|
+
{
|
|
320
|
+
filePath,
|
|
321
|
+
beforeContent: await readExistingContent(cwd, rawPath),
|
|
322
|
+
afterContent: toolInput.content,
|
|
323
|
+
},
|
|
324
|
+
];
|
|
269
325
|
}
|
|
270
326
|
|
|
271
327
|
return [];
|
|
@@ -274,7 +330,7 @@ async function buildTargetChanges(
|
|
|
274
330
|
async function readExistingContent(cwd: string, filePath: string): Promise<string | null> {
|
|
275
331
|
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
|
|
276
332
|
try {
|
|
277
|
-
return await fs.readFile(absolutePath,
|
|
333
|
+
return await fs.readFile(absolutePath, "utf8");
|
|
278
334
|
} catch {
|
|
279
335
|
return null;
|
|
280
336
|
}
|
|
@@ -286,7 +342,7 @@ async function buildBashTargetChange(
|
|
|
286
342
|
toRelative: (p: string) => string,
|
|
287
343
|
): Promise<TargetChange> {
|
|
288
344
|
const filePath = toRelative(rawPath);
|
|
289
|
-
if (rawPath.includes(
|
|
345
|
+
if (rawPath.includes("$") || rawPath.includes("`") || rawPath.startsWith("~")) {
|
|
290
346
|
// hook ではシェル展開後の実パスを解決できないため、存在チェックを避けて MODIFY 既定にする。
|
|
291
347
|
return { filePath };
|
|
292
348
|
}
|
|
@@ -296,10 +352,10 @@ async function buildBashTargetChange(
|
|
|
296
352
|
return { filePath };
|
|
297
353
|
} catch (error) {
|
|
298
354
|
const code = (error as NodeJS.ErrnoException).code;
|
|
299
|
-
if (code ===
|
|
355
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
300
356
|
// Bash では afterContent を取得できないため、空文字を CREATE 判定用の
|
|
301
357
|
// 「変更後内容あり」sentinel として渡す。実ファイルへの書き込みは行わない。
|
|
302
|
-
return { filePath, beforeContent: null, afterContent:
|
|
358
|
+
return { filePath, beforeContent: null, afterContent: "" };
|
|
303
359
|
}
|
|
304
360
|
// 権限エラー等で存在を確認できない場合は従来どおり MODIFY 既定(安全側)。
|
|
305
361
|
return { filePath };
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unit agent-integration
|
|
3
|
+
* @layer presentation
|
|
4
|
+
* @work-item-id WI-385
|
|
5
|
+
*
|
|
6
|
+
* Runtime 固有の未信頼 payload を、agent 名に依存せず canonical input へ変換する。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
CanonicalPreToolUseInput,
|
|
11
|
+
HookResponseProfile,
|
|
12
|
+
NormalizedPreToolUseRequest,
|
|
13
|
+
PayloadShape,
|
|
14
|
+
PreToolUseNormalizationResult,
|
|
15
|
+
} from "../application/dto/normalized-pre-tool-use-request.js";
|
|
16
|
+
|
|
17
|
+
type UnknownRecord = Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
const PATH_KEYS = ["file_path", "filePath", "TargetFile", "targetFile", "target_file", "path"] as const;
|
|
20
|
+
const CONTENT_KEYS = ["content", "CodeContent", "codeContent"] as const;
|
|
21
|
+
const OLD_CONTENT_KEYS = ["old_string", "oldString", "old_str", "oldContent", "OldContent"] as const;
|
|
22
|
+
const NEW_CONTENT_KEYS = ["new_string", "newString", "new_str", "newContent", "NewContent"] as const;
|
|
23
|
+
const COMMAND_KEYS = ["command", "CommandLine", "Command"] as const;
|
|
24
|
+
|
|
25
|
+
const DIRECT_WRITE_TOOLS = new Set(["Write", "write", "write_to_file"]);
|
|
26
|
+
const DIRECT_EDIT_TOOLS = new Set([
|
|
27
|
+
"Edit",
|
|
28
|
+
"search_replace",
|
|
29
|
+
"hashline_edit",
|
|
30
|
+
"replace_file_content",
|
|
31
|
+
"multi_replace_file_content",
|
|
32
|
+
]);
|
|
33
|
+
const COMMAND_TOOLS = new Set(["Bash", "run_terminal_command", "run_command"]);
|
|
34
|
+
|
|
35
|
+
const PROFILE_BY_SHAPE: Readonly<Record<PayloadShape, HookResponseProfile>> = Object.freeze({
|
|
36
|
+
FLAT_SNAKE_CASE: "LEGACY_EXIT_ONLY",
|
|
37
|
+
FLAT_CAMEL_CASE: "COMPATIBILITY_DENY_ENVELOPE",
|
|
38
|
+
NESTED_TOOL_CALL: "TOP_LEVEL_DENY_ENVELOPE",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function isRecord(value: unknown): value is UnknownRecord {
|
|
42
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function nonEmptyString(record: UnknownRecord, keys: readonly string[]): string | undefined {
|
|
46
|
+
for (const key of keys) {
|
|
47
|
+
const value = record[key];
|
|
48
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function optionalString(record: UnknownRecord, keys: readonly string[]): string | undefined {
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
const value = record[key];
|
|
56
|
+
if (typeof value === "string") return value;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function canonicalToolName(observedName: string): string {
|
|
62
|
+
if (DIRECT_WRITE_TOOLS.has(observedName)) return "Write";
|
|
63
|
+
if (DIRECT_EDIT_TOOLS.has(observedName)) return "Edit";
|
|
64
|
+
if (COMMAND_TOOLS.has(observedName)) return "Bash";
|
|
65
|
+
return observedName;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function canonicalDirectInput(toolInput: UnknownRecord): CanonicalPreToolUseInput {
|
|
69
|
+
const filePath = nonEmptyString(toolInput, PATH_KEYS);
|
|
70
|
+
const paths = Array.isArray(toolInput.paths)
|
|
71
|
+
? toolInput.paths.filter(
|
|
72
|
+
(candidate): candidate is string => typeof candidate === "string" && candidate.trim().length > 0,
|
|
73
|
+
)
|
|
74
|
+
: undefined;
|
|
75
|
+
const content = optionalString(toolInput, CONTENT_KEYS);
|
|
76
|
+
const oldString = optionalString(toolInput, OLD_CONTENT_KEYS);
|
|
77
|
+
const newString = optionalString(toolInput, NEW_CONTENT_KEYS);
|
|
78
|
+
return {
|
|
79
|
+
...(filePath === undefined ? {} : { file_path: filePath }),
|
|
80
|
+
...(paths === undefined ? {} : { paths }),
|
|
81
|
+
...(content === undefined ? {} : { content }),
|
|
82
|
+
...(oldString === undefined ? {} : { old_string: oldString }),
|
|
83
|
+
...(newString === undefined ? {} : { new_string: newString }),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function canonicalLegacyInput(toolInput: UnknownRecord): CanonicalPreToolUseInput {
|
|
88
|
+
const paths = Array.isArray(toolInput.paths)
|
|
89
|
+
? toolInput.paths.filter((candidate): candidate is string => typeof candidate === "string")
|
|
90
|
+
: undefined;
|
|
91
|
+
const oldString = optionalString(toolInput, ["old_string", "old_str"]);
|
|
92
|
+
const newString = optionalString(toolInput, ["new_string", "new_str"]);
|
|
93
|
+
return {
|
|
94
|
+
...(typeof toolInput.path === "string" ? { path: toolInput.path } : {}),
|
|
95
|
+
...(typeof toolInput.file_path === "string" ? { file_path: toolInput.file_path } : {}),
|
|
96
|
+
...(paths === undefined ? {} : { paths }),
|
|
97
|
+
...(typeof toolInput.command === "string" ? { command: toolInput.command } : {}),
|
|
98
|
+
...(typeof toolInput.content === "string" ? { content: toolInput.content } : {}),
|
|
99
|
+
...(oldString === undefined ? {} : { old_string: oldString }),
|
|
100
|
+
...(newString === undefined ? {} : { new_string: newString }),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractionFailure(
|
|
105
|
+
profile: HookResponseProfile,
|
|
106
|
+
observedName: string,
|
|
107
|
+
expected: "path" | "command" | "patch",
|
|
108
|
+
): PreToolUseNormalizationResult {
|
|
109
|
+
const candidates =
|
|
110
|
+
expected === "path" ? PATH_KEYS.join(", ") : expected === "command" ? COMMAND_KEYS.join(", ") : "patch, command";
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
responseProfile: profile,
|
|
114
|
+
reason: `${observedName} の書き込み対象を抽出できませんでした。受理可能な ${expected} key: ${candidates}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizeKnownInput(
|
|
119
|
+
observedName: string,
|
|
120
|
+
toolInput: UnknownRecord,
|
|
121
|
+
profile: HookResponseProfile,
|
|
122
|
+
inputTruncated: boolean,
|
|
123
|
+
shape: PayloadShape,
|
|
124
|
+
): { readonly toolName: string; readonly toolInput: CanonicalPreToolUseInput } | PreToolUseNormalizationResult {
|
|
125
|
+
if (observedName === "apply_patch") {
|
|
126
|
+
if (inputTruncated) {
|
|
127
|
+
return { ok: false, responseProfile: profile, reason: "切り詰められた apply_patch は全対象を保証できません" };
|
|
128
|
+
}
|
|
129
|
+
const patch =
|
|
130
|
+
shape === "FLAT_SNAKE_CASE"
|
|
131
|
+
? nonEmptyString(toolInput, ["command"])
|
|
132
|
+
: nonEmptyString(toolInput, ["patch", "command"]);
|
|
133
|
+
if (patch === undefined) return extractionFailure(profile, observedName, "patch");
|
|
134
|
+
return { toolName: "apply_patch", toolInput: { command: patch } };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (COMMAND_TOOLS.has(observedName)) {
|
|
138
|
+
if (inputTruncated) {
|
|
139
|
+
return { ok: false, responseProfile: profile, reason: "切り詰められた command は全対象を保証できません" };
|
|
140
|
+
}
|
|
141
|
+
const command = nonEmptyString(toolInput, COMMAND_KEYS);
|
|
142
|
+
if (command === undefined) return extractionFailure(profile, observedName, "command");
|
|
143
|
+
return { toolName: "Bash", toolInput: { command } };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (DIRECT_WRITE_TOOLS.has(observedName) || DIRECT_EDIT_TOOLS.has(observedName)) {
|
|
147
|
+
const directInput = canonicalDirectInput(toolInput);
|
|
148
|
+
if (directInput.file_path === undefined && (directInput.paths === undefined || directInput.paths.length === 0))
|
|
149
|
+
return extractionFailure(profile, observedName, "path");
|
|
150
|
+
return { toolName: canonicalToolName(observedName), toolInput: directInput };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { toolName: canonicalToolName(observedName), toolInput: canonicalLegacyInput(toolInput) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export class PreToolUsePayloadNormalizer {
|
|
157
|
+
normalize(payload: unknown): PreToolUseNormalizationResult {
|
|
158
|
+
if (!isRecord(payload)) return { ok: false, reason: "PreToolUse payload は JSON object である必要があります" };
|
|
159
|
+
|
|
160
|
+
const toolCall = isRecord(payload.toolCall) ? payload.toolCall : undefined;
|
|
161
|
+
const shapes: PayloadShape[] = [];
|
|
162
|
+
if (typeof payload.tool_name === "string") shapes.push("FLAT_SNAKE_CASE");
|
|
163
|
+
if (typeof payload.toolName === "string" && isRecord(payload.toolInput)) shapes.push("FLAT_CAMEL_CASE");
|
|
164
|
+
if (toolCall !== undefined && typeof toolCall.name === "string" && isRecord(toolCall.args)) {
|
|
165
|
+
shapes.push("NESTED_TOOL_CALL");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (shapes.length === 0) {
|
|
169
|
+
if ("hook_event_name" in payload || "session_id" in payload || "tool_input" in payload) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
responseProfile: "LEGACY_EXIT_ONLY",
|
|
173
|
+
reason: "tool_nameフィールドが必要です",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (toolCall !== undefined) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
responseProfile: "TOP_LEVEL_DENY_ENVELOPE",
|
|
180
|
+
reason: "toolCall payload の name と args を検出できないため拒否しました",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return { ok: false, reason: "対応する PreToolUse payload shape を検出できませんでした" };
|
|
184
|
+
}
|
|
185
|
+
if (shapes.length > 1)
|
|
186
|
+
return { ok: false, reason: "複数の PreToolUse payload shape が混在しているため拒否しました" };
|
|
187
|
+
|
|
188
|
+
const shape = shapes[0];
|
|
189
|
+
const responseProfile = PROFILE_BY_SHAPE[shape];
|
|
190
|
+
const observedName =
|
|
191
|
+
shape === "FLAT_SNAKE_CASE"
|
|
192
|
+
? (payload.tool_name as string)
|
|
193
|
+
: shape === "FLAT_CAMEL_CASE"
|
|
194
|
+
? (payload.toolName as string)
|
|
195
|
+
: (toolCall?.name as string);
|
|
196
|
+
const toolInput =
|
|
197
|
+
shape === "FLAT_SNAKE_CASE"
|
|
198
|
+
? isRecord(payload.tool_input)
|
|
199
|
+
? payload.tool_input
|
|
200
|
+
: {}
|
|
201
|
+
: shape === "FLAT_CAMEL_CASE"
|
|
202
|
+
? (payload.toolInput as UnknownRecord)
|
|
203
|
+
: (toolCall?.args as UnknownRecord);
|
|
204
|
+
const inputTruncated = payload.toolInputTruncated === true;
|
|
205
|
+
const normalizedInput = normalizeKnownInput(observedName, toolInput, responseProfile, inputTruncated, shape);
|
|
206
|
+
if ("ok" in normalizedInput) return normalizedInput;
|
|
207
|
+
|
|
208
|
+
const cwd =
|
|
209
|
+
shape === "NESTED_TOOL_CALL"
|
|
210
|
+
? Array.isArray(payload.workspacePaths)
|
|
211
|
+
? payload.workspacePaths.find(
|
|
212
|
+
(candidate): candidate is string => typeof candidate === "string" && candidate.length > 0,
|
|
213
|
+
)
|
|
214
|
+
: undefined
|
|
215
|
+
: typeof payload.cwd === "string"
|
|
216
|
+
? payload.cwd
|
|
217
|
+
: undefined;
|
|
218
|
+
const request: NormalizedPreToolUseRequest = {
|
|
219
|
+
shape,
|
|
220
|
+
responseProfile,
|
|
221
|
+
cwd,
|
|
222
|
+
toolName: normalizedInput.toolName,
|
|
223
|
+
toolInput: normalizedInput.toolInput,
|
|
224
|
+
inputTruncated,
|
|
225
|
+
};
|
|
226
|
+
return { ok: true, request };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unit agent-integration
|
|
3
|
+
* @layer presentation
|
|
4
|
+
* @work-item-id WI-385
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HookResponseProfile } from "../application/dto/normalized-pre-tool-use-request.js";
|
|
8
|
+
|
|
9
|
+
export interface RenderedPreToolUseResponse {
|
|
10
|
+
readonly stdout: string;
|
|
11
|
+
readonly stderr: string;
|
|
12
|
+
readonly exitCode: 0 | 2;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class PreToolUseResponseRenderer {
|
|
16
|
+
deny(profile: HookResponseProfile, reason: string): RenderedPreToolUseResponse {
|
|
17
|
+
if (profile === "LEGACY_EXIT_ONLY") {
|
|
18
|
+
return { stdout: "", stderr: `${reason}\n`, exitCode: 2 };
|
|
19
|
+
}
|
|
20
|
+
const envelope =
|
|
21
|
+
profile === "COMPATIBILITY_DENY_ENVELOPE"
|
|
22
|
+
? {
|
|
23
|
+
decision: "deny",
|
|
24
|
+
reason,
|
|
25
|
+
hookSpecificOutput: {
|
|
26
|
+
hookEventName: "PreToolUse",
|
|
27
|
+
permissionDecision: "deny",
|
|
28
|
+
permissionDecisionReason: reason,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
: { decision: "deny", reason };
|
|
32
|
+
return { stdout: `${JSON.stringify(envelope)}\n`, stderr: `${reason}\n`, exitCode: 2 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
allow(_profile: HookResponseProfile): RenderedPreToolUseResponse {
|
|
36
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @work-item-id WI-203
|
|
5
5
|
* @work-item-id WI-208
|
|
6
6
|
* @work-item-id WI-323
|
|
7
|
+
* @work-item-id WI-220
|
|
7
8
|
*
|
|
8
9
|
* Stop Hook Adapter
|
|
9
10
|
* Claude Code の Stop Hook エントリポイント
|
|
@@ -14,7 +15,6 @@ import * as path from "node:path";
|
|
|
14
15
|
import { HandleStopUseCase } from "../application/usecases/handle-stop-usecase.js";
|
|
15
16
|
import { ChildProcessCliExecutorAdapter } from "../infrastructure/adapters/child-process-cli-executor-adapter.js";
|
|
16
17
|
import { EnvFileReentryGuardStateAdapter } from "../infrastructure/adapters/env-file-reentry-guard-state-adapter.js";
|
|
17
|
-
import { HarnessApiCliCommandRegistryAdapter } from "../infrastructure/adapters/harness-api-cli-command-registry-adapter.js";
|
|
18
18
|
import { HarnessConfigConfigQueryAdapter } from "../infrastructure/adapters/harness-config-config-query-adapter.js";
|
|
19
19
|
import { recordHookSkipEvent } from "./hook-skip-event-recorder.js";
|
|
20
20
|
|
|
@@ -106,14 +106,12 @@ async function main(): Promise<void> {
|
|
|
106
106
|
const configPath = await findConfigPath();
|
|
107
107
|
const reentryGuardStatePort = new EnvFileReentryGuardStateAdapter({ strategy: "env" });
|
|
108
108
|
const configQueryPort = new HarnessConfigConfigQueryAdapter(configPath);
|
|
109
|
-
const cliCommandRegistryPort = new HarnessApiCliCommandRegistryAdapter();
|
|
110
109
|
const cliExecutorPort = new ChildProcessCliExecutorAdapter();
|
|
111
110
|
|
|
112
111
|
const useCase = new HandleStopUseCase({
|
|
113
112
|
reentryGuardStatePort,
|
|
114
113
|
cliExecutorPort,
|
|
115
114
|
configQueryPort,
|
|
116
|
-
cliCommandRegistryPort,
|
|
117
115
|
});
|
|
118
116
|
|
|
119
117
|
const output = await useCase.execute({ sessionId });
|