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.
Files changed (106) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.ja.md +22 -9
  3. package/README.md +28 -17
  4. package/docs/ADR/038-config-state-operation-permission-policy.md +5 -2
  5. package/docs/ADR/039-hook-observable-state-as-authorization-unit.md +74 -0
  6. package/docs/ADR/040-quick-mode-config-via-preset-resolution.md +66 -0
  7. package/docs/ADR/041-non-excludable-agent-trust-roots.md +68 -0
  8. package/docs/guide/antigravity-integration.md +26 -0
  9. package/docs/guide/cli-reference.md +93 -11
  10. package/docs/guide/codex-integration.md +44 -107
  11. package/docs/guide/configuration.md +43 -2
  12. package/docs/guide/grok-integration.md +26 -0
  13. package/docs/guide/hooks-integration.md +27 -4
  14. package/docs/guide/installation.md +4 -2
  15. package/docs/guide/quick-vs-full-mode.md +36 -1
  16. package/docs/guide/setup-artifacts.md +4 -4
  17. package/docs/guide/skills-overview.md +1 -1
  18. package/package.json +2 -2
  19. package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +6 -2
  20. package/scripts/harness/agent-integration/application/dto/normalized-pre-tool-use-request.ts +32 -0
  21. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +17 -9
  22. package/scripts/harness/agent-integration/domain/ports/full-mode-requirement-query-port.ts +3 -1
  23. package/scripts/harness/agent-integration/domain/services/apply-patch-write-target-extractor.ts +98 -0
  24. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +196 -54
  25. package/scripts/harness/agent-integration/domain/value-objects/hook-translation-result.ts +1 -1
  26. package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +39 -17
  27. package/scripts/harness/agent-integration/infrastructure/adapters/quick-mode-full-mode-requirement-adapter.ts +15 -1
  28. package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +1 -1
  29. package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +1 -0
  30. package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +129 -73
  31. package/scripts/harness/agent-integration/presentation/pre-tool-use-payload-normalizer.ts +228 -0
  32. package/scripts/harness/agent-integration/presentation/pre-tool-use-response-renderer.ts +38 -0
  33. package/scripts/harness/ci-governance/application/dto/scaffold-inception-input.ts +9 -0
  34. package/scripts/harness/ci-governance/application/dto/scaffold-inception-output.ts +13 -0
  35. package/scripts/harness/ci-governance/application/usecases/list-templates-usecase.ts +30 -0
  36. package/scripts/harness/ci-governance/application/usecases/scaffold-inception-usecase.ts +63 -0
  37. package/scripts/harness/ci-governance/application/usecases/show-template-usecase.ts +39 -0
  38. package/scripts/harness/ci-governance/composition-root.ts +55 -3
  39. package/scripts/harness/ci-governance/domain/ports/inception-doc-writer-port.ts +19 -0
  40. package/scripts/harness/ci-governance/domain/ports/inception-template-repository-port.ts +16 -0
  41. package/scripts/harness/ci-governance/domain/ports/template-catalog-port.ts +23 -0
  42. package/scripts/harness/ci-governance/domain/value-objects/inception-doc-kind.ts +109 -0
  43. package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +2 -0
  44. package/scripts/harness/ci-governance/domain/value-objects/template-catalog-entry.ts +53 -0
  45. package/scripts/harness/ci-governance/domain/value-objects/template-name.ts +50 -0
  46. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-doc-writer-adapter.ts +42 -0
  47. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-template-repository-adapter.ts +32 -0
  48. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-template-catalog-adapter.ts +60 -0
  49. package/scripts/harness/ci-governance/presentation/handlers/scaffold-inception-handler.ts +101 -0
  50. package/scripts/harness/ci-governance/presentation/handlers/templates-handler.ts +103 -0
  51. package/scripts/harness/config-foundation/domain/harness-config.ts +12 -0
  52. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -2
  53. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -2
  54. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -2
  55. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +44 -1
  56. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +42 -1
  57. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +3 -1
  58. package/scripts/harness/installation/application/checks/antigravity-hook-missing-check.ts +79 -0
  59. package/scripts/harness/installation/application/checks/codex-hook-missing-check.ts +70 -6
  60. package/scripts/harness/installation/application/checks/grok-hook-missing-check.ts +79 -0
  61. package/scripts/harness/installation/application/checks/husky-runtime-inactive-check.ts +33 -0
  62. package/scripts/harness/installation/application/named-hook-json.ts +17 -0
  63. package/scripts/harness/installation/application/operator-notice.ts +42 -0
  64. package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +18 -7
  65. package/scripts/harness/installation/application/usecases/run-install.ts +152 -52
  66. package/scripts/harness/installation/application/usecases/run-reconcile.ts +86 -10
  67. package/scripts/harness/installation/application/usecases/run-uninstall.ts +149 -25
  68. package/scripts/harness/installation/composition-root.ts +10 -0
  69. package/scripts/harness/installation/domain/agent-target.ts +39 -0
  70. package/scripts/harness/installation/domain/check-id.ts +5 -0
  71. package/scripts/harness/installation/domain/husky-runtime-state.ts +34 -0
  72. package/scripts/harness/installation/domain/ports/git-hooks-runtime-probe.ts +9 -0
  73. package/scripts/harness/installation/domain/repair-table.ts +5 -0
  74. package/scripts/harness/installation/infrastructure/adapters/git-hooks-runtime-probe-adapter.ts +62 -0
  75. package/scripts/harness/installation/presentation/cli/install-handler.ts +23 -4
  76. package/scripts/harness/installation/presentation/cli/reconcile-handler.ts +7 -1
  77. package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +27 -0
  78. package/scripts/harness/main.ts +202 -51
  79. package/scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts +2 -1
  80. package/scripts/harness/phase-dependency-model/domain/ports/plan-document-reader-port.ts +12 -0
  81. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +4 -1
  82. package/scripts/harness/quick-mode/application/dto/change-category-classification-contract.ts +1 -1
  83. package/scripts/harness/quick-mode/application/dto/quick-mode-eligibility-contract.ts +1 -1
  84. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +3 -1
  85. package/scripts/harness/quick-mode/domain/errors/quick-mode-config-error.ts +19 -0
  86. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +59 -19
  87. package/scripts/harness/quick-mode/domain/types/rejection-rule.ts +1 -1
  88. package/scripts/harness/quick-mode/domain/value-objects/category-override-rules.ts +147 -0
  89. package/scripts/harness/quick-mode/domain/value-objects/change-category.ts +33 -0
  90. package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +31 -10
  91. package/scripts/harness/quick-mode/index.ts +7 -1
  92. package/scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts +83 -32
  93. package/scripts/harness/regression-suite/infrastructure/adapters/vitest-test-runner-adapter.ts +13 -3
  94. package/skills/product-architect/SKILL.md +19 -0
  95. package/skills/story-mapper/SKILL.md +11 -0
  96. package/skills/story-writer/SKILL.md +11 -0
  97. package/skills/unit-designer/SKILL.md +11 -0
  98. package/templates/.agents/hooks.json +16 -0
  99. package/templates/.claude/scripts/analyze-errors-hook.sh +2 -2
  100. package/templates/.claude/settings.json +5 -3
  101. package/templates/.codex/hooks.json +2 -2
  102. package/templates/product_overview.template.md +85 -0
  103. package/templates/product_overview_plan.template.md +55 -0
  104. package/templates/story_mapping_plan.template.md +61 -0
  105. package/templates/story_writer_plan.template.md +61 -0
  106. 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,9 @@
1
+ // @unit ci-governance
2
+ // @layer application
3
+ // @work-item-id WI-368
4
+
5
+ export interface ScaffoldInceptionInput {
6
+ readonly kind: string;
7
+ readonly dryRun?: boolean;
8
+ readonly force?: boolean;
9
+ }
@@ -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
- export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir): CiGovernanceCompositionRoot {
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
+ }