phasegate 0.315.0 → 0.340.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 +4 -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/039-hook-observable-state-as-authorization-unit.md +74 -0
- package/docs/ADR/040-quick-mode-config-via-preset-resolution.md +66 -0
- package/docs/ADR/041-non-excludable-agent-trust-roots.md +68 -0
- package/docs/guide/antigravity-integration.md +26 -0
- package/docs/guide/cli-reference.md +93 -11
- package/docs/guide/codex-integration.md +44 -107
- package/docs/guide/configuration.md +43 -2
- package/docs/guide/grok-integration.md +26 -0
- package/docs/guide/hooks-integration.md +27 -4
- package/docs/guide/installation.md +4 -2
- package/docs/guide/quick-vs-full-mode.md +36 -1
- package/docs/guide/setup-artifacts.md +4 -4
- package/docs/guide/skills-overview.md +1 -1
- package/package.json +2 -2
- package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +6 -2
- package/scripts/harness/agent-integration/application/dto/normalized-pre-tool-use-request.ts +32 -0
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +17 -9
- package/scripts/harness/agent-integration/domain/ports/full-mode-requirement-query-port.ts +3 -1
- 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 +196 -54
- package/scripts/harness/agent-integration/domain/value-objects/hook-translation-result.ts +1 -1
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +39 -17
- package/scripts/harness/agent-integration/infrastructure/adapters/quick-mode-full-mode-requirement-adapter.ts +15 -1
- package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +1 -1
- package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +1 -0
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +129 -73
- 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/ci-governance/application/dto/scaffold-inception-input.ts +9 -0
- package/scripts/harness/ci-governance/application/dto/scaffold-inception-output.ts +13 -0
- package/scripts/harness/ci-governance/application/usecases/list-templates-usecase.ts +30 -0
- package/scripts/harness/ci-governance/application/usecases/scaffold-inception-usecase.ts +63 -0
- package/scripts/harness/ci-governance/application/usecases/show-template-usecase.ts +39 -0
- package/scripts/harness/ci-governance/composition-root.ts +55 -3
- package/scripts/harness/ci-governance/domain/ports/inception-doc-writer-port.ts +19 -0
- package/scripts/harness/ci-governance/domain/ports/inception-template-repository-port.ts +16 -0
- package/scripts/harness/ci-governance/domain/ports/template-catalog-port.ts +23 -0
- package/scripts/harness/ci-governance/domain/value-objects/inception-doc-kind.ts +109 -0
- package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +2 -0
- package/scripts/harness/ci-governance/domain/value-objects/template-catalog-entry.ts +53 -0
- package/scripts/harness/ci-governance/domain/value-objects/template-name.ts +50 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-doc-writer-adapter.ts +42 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-template-repository-adapter.ts +32 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-template-catalog-adapter.ts +60 -0
- package/scripts/harness/ci-governance/presentation/handlers/scaffold-inception-handler.ts +101 -0
- package/scripts/harness/ci-governance/presentation/handlers/templates-handler.ts +103 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +12 -0
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -2
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -2
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -2
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +44 -1
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +42 -1
- package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +3 -1
- package/scripts/harness/installation/application/checks/antigravity-hook-missing-check.ts +79 -0
- 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 +152 -52
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +86 -10
- 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 +23 -4
- 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 +202 -51
- package/scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts +2 -1
- package/scripts/harness/phase-dependency-model/domain/ports/plan-document-reader-port.ts +12 -0
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +4 -1
- package/scripts/harness/quick-mode/application/dto/change-category-classification-contract.ts +1 -1
- package/scripts/harness/quick-mode/application/dto/quick-mode-eligibility-contract.ts +1 -1
- package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +3 -1
- package/scripts/harness/quick-mode/domain/errors/quick-mode-config-error.ts +19 -0
- package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +59 -19
- package/scripts/harness/quick-mode/domain/types/rejection-rule.ts +1 -1
- package/scripts/harness/quick-mode/domain/value-objects/category-override-rules.ts +147 -0
- package/scripts/harness/quick-mode/domain/value-objects/change-category.ts +33 -0
- package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +31 -10
- package/scripts/harness/quick-mode/index.ts +7 -1
- package/scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts +83 -32
- package/scripts/harness/regression-suite/infrastructure/adapters/vitest-test-runner-adapter.ts +13 -3
- package/skills/product-architect/SKILL.md +19 -0
- package/skills/story-mapper/SKILL.md +11 -0
- package/skills/story-writer/SKILL.md +11 -0
- package/skills/unit-designer/SKILL.md +11 -0
- 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
- package/templates/product_overview.template.md +85 -0
- package/templates/product_overview_plan.template.md +55 -0
- package/templates/story_mapping_plan.template.md +61 -0
- package/templates/story_writer_plan.template.md +61 -0
- package/templates/unit_design_plan.template.md +63 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @unit ci-governance
|
|
2
|
+
// @layer application
|
|
3
|
+
// @work-item-id WI-368
|
|
4
|
+
|
|
5
|
+
export interface ScaffoldInceptionOutput {
|
|
6
|
+
readonly targetPath: string;
|
|
7
|
+
readonly templatePath: string;
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
readonly dryRun: boolean;
|
|
10
|
+
readonly written: boolean;
|
|
11
|
+
readonly alreadyExists: boolean;
|
|
12
|
+
readonly overwritten: boolean;
|
|
13
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @unit ci-governance
|
|
2
|
+
// @layer application
|
|
3
|
+
// @work-item-id WI-367
|
|
4
|
+
|
|
5
|
+
import type { TemplateCatalogPort } from "../../domain/ports/template-catalog-port.js";
|
|
6
|
+
|
|
7
|
+
export interface ListTemplatesOutputEntry {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly fileName: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ListTemplatesOutput {
|
|
13
|
+
readonly directoryPath: string;
|
|
14
|
+
readonly templates: readonly ListTemplatesOutputEntry[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ListTemplatesUseCase {
|
|
18
|
+
constructor(private readonly catalog: TemplateCatalogPort) {}
|
|
19
|
+
|
|
20
|
+
async execute(): Promise<ListTemplatesOutput> {
|
|
21
|
+
const entries = await this.catalog.list();
|
|
22
|
+
return {
|
|
23
|
+
directoryPath: this.catalog.directoryPath(),
|
|
24
|
+
templates: entries.map((entry) => ({
|
|
25
|
+
name: entry.name.value,
|
|
26
|
+
fileName: entry.fileName,
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @unit ci-governance
|
|
2
|
+
// @layer application
|
|
3
|
+
// @work-item-id WI-368
|
|
4
|
+
|
|
5
|
+
import type { InceptionDocWriterPort } from "../../domain/ports/inception-doc-writer-port.js";
|
|
6
|
+
import type { InceptionTemplateRepositoryPort } from "../../domain/ports/inception-template-repository-port.js";
|
|
7
|
+
import { InceptionDocKind } from "../../domain/value-objects/inception-doc-kind.js";
|
|
8
|
+
import type { ScaffoldInceptionInput } from "../dto/scaffold-inception-input.js";
|
|
9
|
+
import type { ScaffoldInceptionOutput } from "../dto/scaffold-inception-output.js";
|
|
10
|
+
|
|
11
|
+
export class ScaffoldInceptionUseCase {
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly templates: InceptionTemplateRepositoryPort,
|
|
14
|
+
private readonly writer: InceptionDocWriterPort,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
async execute(input: ScaffoldInceptionInput): Promise<ScaffoldInceptionOutput> {
|
|
18
|
+
const kind = InceptionDocKind.create(input.kind);
|
|
19
|
+
const dryRun = input.dryRun === true;
|
|
20
|
+
const force = input.force === true;
|
|
21
|
+
|
|
22
|
+
const templatePath = this.templates.resolvePath(kind);
|
|
23
|
+
const targetPath = this.writer.resolvePath(kind);
|
|
24
|
+
const alreadyExists = await this.writer.exists(kind);
|
|
25
|
+
|
|
26
|
+
if (dryRun) {
|
|
27
|
+
return {
|
|
28
|
+
targetPath,
|
|
29
|
+
templatePath,
|
|
30
|
+
kind: kind.value,
|
|
31
|
+
dryRun: true,
|
|
32
|
+
written: false,
|
|
33
|
+
alreadyExists,
|
|
34
|
+
overwritten: false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (alreadyExists && !force) {
|
|
39
|
+
return {
|
|
40
|
+
targetPath,
|
|
41
|
+
templatePath,
|
|
42
|
+
kind: kind.value,
|
|
43
|
+
dryRun: false,
|
|
44
|
+
written: false,
|
|
45
|
+
alreadyExists: true,
|
|
46
|
+
overwritten: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const raw = await this.templates.read(kind);
|
|
51
|
+
await this.writer.write(kind, raw);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
targetPath,
|
|
55
|
+
templatePath,
|
|
56
|
+
kind: kind.value,
|
|
57
|
+
dryRun: false,
|
|
58
|
+
written: true,
|
|
59
|
+
alreadyExists,
|
|
60
|
+
overwritten: alreadyExists && force,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @unit ci-governance
|
|
2
|
+
// @layer application
|
|
3
|
+
// @work-item-id WI-367
|
|
4
|
+
|
|
5
|
+
import type { TemplateCatalogPort } from "../../domain/ports/template-catalog-port.js";
|
|
6
|
+
import { TemplateName } from "../../domain/value-objects/template-name.js";
|
|
7
|
+
|
|
8
|
+
export type ShowTemplateOutput =
|
|
9
|
+
| { readonly found: true; readonly name: string; readonly content: string }
|
|
10
|
+
| {
|
|
11
|
+
readonly found: false;
|
|
12
|
+
readonly reason: "invalid-name" | "not-found";
|
|
13
|
+
readonly availableNames: readonly string[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class ShowTemplateUseCase {
|
|
17
|
+
constructor(private readonly catalog: TemplateCatalogPort) {}
|
|
18
|
+
|
|
19
|
+
async execute(rawName: string): Promise<ShowTemplateOutput> {
|
|
20
|
+
if (!TemplateName.isValid(rawName)) {
|
|
21
|
+
// 不正名では catalog を引かない。パス由来の情報を一切露出させない。
|
|
22
|
+
return { found: false, reason: "invalid-name", availableNames: [] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const name = TemplateName.create(rawName);
|
|
26
|
+
const content = await this.catalog.read(name);
|
|
27
|
+
|
|
28
|
+
if (content === null) {
|
|
29
|
+
const entries = await this.catalog.list();
|
|
30
|
+
return {
|
|
31
|
+
found: false,
|
|
32
|
+
reason: "not-found",
|
|
33
|
+
availableNames: entries.map((entry) => entry.name.value),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { found: true, name: name.value, content };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer infrastructure
|
|
3
3
|
* @unit ci-governance
|
|
4
|
+
* @work-item-id WI-367, WI-368, WI-369
|
|
4
5
|
*
|
|
5
6
|
* Composition Root - 依存性の組み立て
|
|
6
7
|
*/
|
|
@@ -10,6 +11,7 @@ import { CheckAgentContextUseCase } from "./application/usecases/check-agent-con
|
|
|
10
11
|
import { CheckEscalationUseCase } from "./application/usecases/check-escalation-usecase.js";
|
|
11
12
|
import { CreateBaselineUseCase } from "./application/usecases/create-baseline-usecase.js";
|
|
12
13
|
import { GenerateCiTemplateUseCase } from "./application/usecases/generate-ci-template-usecase.js";
|
|
14
|
+
import { ListTemplatesUseCase } from "./application/usecases/list-templates-usecase.js";
|
|
13
15
|
import { MigrateAgentsMdUseCase } from "./application/usecases/migrate-agents-md-usecase.js";
|
|
14
16
|
import { PinIntegrityUseCase } from "./application/usecases/pin-integrity-usecase.js";
|
|
15
17
|
import { RecordErrorOccurrenceUseCase } from "./application/usecases/record-error-occurrence-usecase.js";
|
|
@@ -18,6 +20,8 @@ import { RefreshClaudeMdUseCase } from "./application/usecases/refresh-claude-md
|
|
|
18
20
|
import { RenderCiTemplateUseCase } from "./application/usecases/render-ci-template-usecase.js";
|
|
19
21
|
import { ResetRepetitionUseCase } from "./application/usecases/reset-repetition-usecase.js";
|
|
20
22
|
import { ScaffoldDesignUseCase } from "./application/usecases/scaffold-design-usecase.js";
|
|
23
|
+
import { ScaffoldInceptionUseCase } from "./application/usecases/scaffold-inception-usecase.js";
|
|
24
|
+
import { ShowTemplateUseCase } from "./application/usecases/show-template-usecase.js";
|
|
21
25
|
import { ValidatePointersUseCase } from "./application/usecases/validate-pointers-usecase.js";
|
|
22
26
|
import { VerifyIntegrityUseCase } from "./application/usecases/verify-integrity-usecase.js";
|
|
23
27
|
import { ClaudeMdComposer } from "./domain/services/claude-md-composer.js";
|
|
@@ -34,8 +38,11 @@ import { ErrorRepetitionJsonRepository } from "./infrastructure/adapters/error-r
|
|
|
34
38
|
import { EscalationLogExecutorAdapter } from "./infrastructure/adapters/escalation-log-executor-adapter.js";
|
|
35
39
|
import { FileSystemDesignDocWriterAdapter } from "./infrastructure/adapters/file-system-design-doc-writer-adapter.js";
|
|
36
40
|
import { FileSystemExistenceAdapter } from "./infrastructure/adapters/file-system-existence-adapter.js";
|
|
41
|
+
import { FileSystemInceptionDocWriterAdapter } from "./infrastructure/adapters/file-system-inception-doc-writer-adapter.js";
|
|
42
|
+
import { FileSystemInceptionTemplateRepositoryAdapter } from "./infrastructure/adapters/file-system-inception-template-repository-adapter.js";
|
|
37
43
|
import { FileSystemSha1HasherAdapter } from "./infrastructure/adapters/file-system-sha1-hasher-adapter.js";
|
|
38
44
|
import { FileSystemSha256HasherAdapter } from "./infrastructure/adapters/file-system-sha256-hasher-adapter.js";
|
|
45
|
+
import { FileSystemTemplateCatalogAdapter } from "./infrastructure/adapters/file-system-template-catalog-adapter.js";
|
|
39
46
|
import { FileSystemTemplateRepositoryAdapter } from "./infrastructure/adapters/file-system-template-repository-adapter.js";
|
|
40
47
|
import { GlobFileScannerAdapter } from "./infrastructure/adapters/glob-file-scanner-adapter.js";
|
|
41
48
|
import { HarnessApiCommandExistenceAdapter } from "./infrastructure/adapters/harness-api-command-existence-adapter.js";
|
|
@@ -53,6 +60,8 @@ import { MigrateAgentsMdHandler } from "./presentation/handlers/migrate-agents-m
|
|
|
53
60
|
import { RefreshAgentContextHandler } from "./presentation/handlers/refresh-agent-context-handler.js";
|
|
54
61
|
import { RefreshClaudeMdHandler } from "./presentation/handlers/refresh-claude-md-handler.js";
|
|
55
62
|
import { ScaffoldDesignHandler } from "./presentation/handlers/scaffold-design-handler.js";
|
|
63
|
+
import { ScaffoldInceptionHandler } from "./presentation/handlers/scaffold-inception-handler.js";
|
|
64
|
+
import { TemplatesHandler } from "./presentation/handlers/templates-handler.js";
|
|
56
65
|
|
|
57
66
|
export interface CiGovernanceCompositionRoot {
|
|
58
67
|
generateCiTemplateHandler: GenerateCiTemplateHandler;
|
|
@@ -63,6 +72,8 @@ export interface CiGovernanceCompositionRoot {
|
|
|
63
72
|
checkRepetitionHandler: CheckRepetitionHandler;
|
|
64
73
|
createBaselineHandler: CreateBaselineHandler;
|
|
65
74
|
scaffoldDesignHandler: ScaffoldDesignHandler;
|
|
75
|
+
scaffoldInceptionHandler: ScaffoldInceptionHandler;
|
|
76
|
+
templatesHandler: TemplatesHandler;
|
|
66
77
|
// Use cases exposed for direct access
|
|
67
78
|
recordErrorOccurrenceUseCase: RecordErrorOccurrenceUseCase;
|
|
68
79
|
checkEscalationUseCase: CheckEscalationUseCase;
|
|
@@ -71,10 +82,33 @@ export interface CiGovernanceCompositionRoot {
|
|
|
71
82
|
validatePointersUseCase: ValidatePointersUseCase;
|
|
72
83
|
createBaselineUseCase: CreateBaselineUseCase;
|
|
73
84
|
scaffoldDesignUseCase: ScaffoldDesignUseCase;
|
|
85
|
+
scaffoldInceptionUseCase: ScaffoldInceptionUseCase;
|
|
74
86
|
integrityHandler: IntegrityHandler;
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
|
|
89
|
+
/**
|
|
90
|
+
* 解決済みの設計文書ルート(`phasegate.config.json` の `paths`)。
|
|
91
|
+
* 省略時は従来どおり `docs/product/construction` / `docs/inception` を使う。
|
|
92
|
+
*
|
|
93
|
+
* WI-369: これを渡さないと `scaffold-design` の書き込み先が
|
|
94
|
+
* `docs/product/construction` に固定され、`paths.designDocs` を移設した PJ で
|
|
95
|
+
* scaffold 先とフェーズゲートの検査先がズレる。
|
|
96
|
+
*/
|
|
97
|
+
export interface CiGovernanceDocPaths {
|
|
98
|
+
readonly designDocs?: string;
|
|
99
|
+
readonly inceptionDocs?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const DEFAULT_DESIGN_DOCS_ROOT = "docs/product/construction";
|
|
103
|
+
const DEFAULT_INCEPTION_DOCS_ROOT = "docs/inception";
|
|
104
|
+
|
|
105
|
+
export function buildCiGovernance(
|
|
106
|
+
baseDir: string,
|
|
107
|
+
harnessRoot: string = baseDir,
|
|
108
|
+
docPaths: CiGovernanceDocPaths = {},
|
|
109
|
+
): CiGovernanceCompositionRoot {
|
|
110
|
+
const designDocsRoot = docPaths.designDocs ?? DEFAULT_DESIGN_DOCS_ROOT;
|
|
111
|
+
const inceptionDocsRoot = docPaths.inceptionDocs ?? DEFAULT_INCEPTION_DOCS_ROOT;
|
|
78
112
|
// Infrastructure adapters
|
|
79
113
|
const validatorIdRegistryAdapter = new ValidatorIdRegistryAdapter();
|
|
80
114
|
const presetConfigAdapter = new PresetConfigAdapter();
|
|
@@ -119,11 +153,24 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
|
|
|
119
153
|
const baselineRepository = new BaselineJsonRepositoryAdapter(baseDir);
|
|
120
154
|
const createBaselineUseCase = new CreateBaselineUseCase(fileScanner, fileHasher, baselineRepository);
|
|
121
155
|
|
|
122
|
-
// Scaffold design adapters & use case (ISSUE-007 Wave 4)
|
|
156
|
+
// Scaffold design adapters & use case (ISSUE-007 Wave 4 / WI-369 paths 追従)
|
|
123
157
|
const templateRepository = new FileSystemTemplateRepositoryAdapter(harnessRoot);
|
|
124
|
-
const designDocWriter = new FileSystemDesignDocWriterAdapter(baseDir);
|
|
158
|
+
const designDocWriter = new FileSystemDesignDocWriterAdapter(baseDir, designDocsRoot);
|
|
125
159
|
const scaffoldDesignUseCase = new ScaffoldDesignUseCase(templateRepository, designDocWriter);
|
|
126
160
|
|
|
161
|
+
// Scaffold inception adapters & use case (WI-368)
|
|
162
|
+
const inceptionTemplateRepository = new FileSystemInceptionTemplateRepositoryAdapter(harnessRoot);
|
|
163
|
+
const inceptionDocWriter = new FileSystemInceptionDocWriterAdapter(baseDir, {
|
|
164
|
+
designDocsRoot,
|
|
165
|
+
inceptionDocsRoot,
|
|
166
|
+
});
|
|
167
|
+
const scaffoldInceptionUseCase = new ScaffoldInceptionUseCase(inceptionTemplateRepository, inceptionDocWriter);
|
|
168
|
+
|
|
169
|
+
// Template catalog adapters & use cases (WI-367)
|
|
170
|
+
const templateCatalog = new FileSystemTemplateCatalogAdapter(harnessRoot);
|
|
171
|
+
const listTemplatesUseCase = new ListTemplatesUseCase(templateCatalog);
|
|
172
|
+
const showTemplateUseCase = new ShowTemplateUseCase(templateCatalog);
|
|
173
|
+
|
|
127
174
|
// Handlers
|
|
128
175
|
const generateCiTemplateHandler = new GenerateCiTemplateHandler(generateCiTemplateUseCase, renderCiTemplateUseCase);
|
|
129
176
|
const migrateAgentsMdHandler = new MigrateAgentsMdHandler(migrateAgentsMdUseCase, validatePointersUseCase);
|
|
@@ -133,6 +180,8 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
|
|
|
133
180
|
const checkRepetitionHandler = new CheckRepetitionHandler(checkEscalationUseCase, resetRepetitionUseCase);
|
|
134
181
|
const createBaselineHandler = new CreateBaselineHandler(createBaselineUseCase);
|
|
135
182
|
const scaffoldDesignHandler = new ScaffoldDesignHandler(scaffoldDesignUseCase);
|
|
183
|
+
const scaffoldInceptionHandler = new ScaffoldInceptionHandler(scaffoldInceptionUseCase);
|
|
184
|
+
const templatesHandler = new TemplatesHandler(listTemplatesUseCase, showTemplateUseCase);
|
|
136
185
|
|
|
137
186
|
// Integrity pin adapters & handler (WI-254 / ADR-030 §Decision.3.①)
|
|
138
187
|
const integrityChecker = new IntegrityChecker();
|
|
@@ -156,6 +205,8 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
|
|
|
156
205
|
checkRepetitionHandler,
|
|
157
206
|
createBaselineHandler,
|
|
158
207
|
scaffoldDesignHandler,
|
|
208
|
+
scaffoldInceptionHandler,
|
|
209
|
+
templatesHandler,
|
|
159
210
|
recordErrorOccurrenceUseCase,
|
|
160
211
|
checkEscalationUseCase,
|
|
161
212
|
resetRepetitionUseCase,
|
|
@@ -163,6 +214,7 @@ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir
|
|
|
163
214
|
validatePointersUseCase,
|
|
164
215
|
createBaselineUseCase,
|
|
165
216
|
scaffoldDesignUseCase,
|
|
217
|
+
scaffoldInceptionUseCase,
|
|
166
218
|
integrityHandler,
|
|
167
219
|
};
|
|
168
220
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @unit ci-governance
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-368
|
|
4
|
+
|
|
5
|
+
import type { InceptionDocKind } from "../value-objects/inception-doc-kind.js";
|
|
6
|
+
|
|
7
|
+
export interface InceptionDocWriterPort {
|
|
8
|
+
/** 書き込み先の絶対パスを返す(副作用なし) */
|
|
9
|
+
resolvePath(kind: InceptionDocKind): string;
|
|
10
|
+
|
|
11
|
+
/** 書き込み先が既に存在するか */
|
|
12
|
+
exists(kind: InceptionDocKind): Promise<boolean>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* inception / product 文書を書き込む(親ディレクトリが無ければ作成)。
|
|
16
|
+
* 書き込んだ絶対パスを返す。
|
|
17
|
+
*/
|
|
18
|
+
write(kind: InceptionDocKind, content: string): Promise<string>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @unit ci-governance
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-368
|
|
4
|
+
|
|
5
|
+
import type { InceptionDocKind } from "../value-objects/inception-doc-kind.js";
|
|
6
|
+
|
|
7
|
+
export interface InceptionTemplateRepositoryPort {
|
|
8
|
+
/** テンプレートの絶対パスを返す(UI 出力用) */
|
|
9
|
+
resolvePath(kind: InceptionDocKind): string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* kind に対応するテンプレートファイルを読み込む。
|
|
13
|
+
* 存在しない場合は例外を投げる。
|
|
14
|
+
*/
|
|
15
|
+
read(kind: InceptionDocKind): Promise<string>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @unit ci-governance
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-367
|
|
4
|
+
|
|
5
|
+
import type { TemplateCatalogEntry } from "../value-objects/template-catalog-entry.js";
|
|
6
|
+
import type { TemplateName } from "../value-objects/template-name.js";
|
|
7
|
+
|
|
8
|
+
export interface TemplateCatalogPort {
|
|
9
|
+
/** テンプレートディレクトリの絶対パス(案内表示用) */
|
|
10
|
+
directoryPath(): string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 実在するテンプレートを name 昇順で列挙する。
|
|
14
|
+
* ディレクトリが存在しない場合は空配列を返す(例外にしない)。
|
|
15
|
+
*/
|
|
16
|
+
list(): Promise<readonly TemplateCatalogEntry[]>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* catalog に name 完全一致するテンプレート本文を読む。
|
|
20
|
+
* 一致するエントリが無い場合は null を返す。
|
|
21
|
+
*/
|
|
22
|
+
read(name: TemplateName): Promise<string | null>;
|
|
23
|
+
}
|