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,16 +7,29 @@
|
|
|
7
7
|
// @work-item-id WI-208
|
|
8
8
|
// @work-item-id WI-209
|
|
9
9
|
// @work-item-id WI-216
|
|
10
|
+
// @work-item-id WI-385
|
|
11
|
+
// @work-item-id WI-387
|
|
10
12
|
|
|
11
|
-
import { access, copyFile, lstat, mkdir,
|
|
13
|
+
import { access, copyFile, lstat, mkdir, readdir, readFile, readlink, rm, rmdir, writeFile } from "node:fs/promises";
|
|
12
14
|
import { dirname, join, relative, resolve } from "node:path";
|
|
13
15
|
import type { DeploymentEntry } from "../../domain/deployment-entry.js";
|
|
14
16
|
import type { RepairMode } from "../../domain/repair-mode.js";
|
|
17
|
+
import { removeNamedHookJson } from "../named-hook-json.js";
|
|
15
18
|
import type { HashCalculatorPort } from "../ports/hash-calculator-port.js";
|
|
16
19
|
import type { ManifestRepositoryPort } from "../ports/manifest-repository-port.js";
|
|
17
20
|
|
|
18
21
|
type UninstallAction = "missing-manifest" | "delete" | "unlink" | "reverse-merge" | "skip" | "refuse";
|
|
19
|
-
type StrategyType =
|
|
22
|
+
type StrategyType =
|
|
23
|
+
| "created"
|
|
24
|
+
| "json"
|
|
25
|
+
| "json-named"
|
|
26
|
+
| "shell"
|
|
27
|
+
| "package-json"
|
|
28
|
+
| "markdown-managed"
|
|
29
|
+
| "symlink"
|
|
30
|
+
| "yaml-add"
|
|
31
|
+
| "text-managed"
|
|
32
|
+
| "unknown";
|
|
20
33
|
|
|
21
34
|
export interface UninstallPlanItem {
|
|
22
35
|
readonly path: string;
|
|
@@ -115,19 +128,37 @@ export function reverseJsonMerge(currentContent: string, templateContent: string
|
|
|
115
128
|
return `${JSON.stringify(result, null, 2)}\n`;
|
|
116
129
|
}
|
|
117
130
|
|
|
131
|
+
export function reverseNamedHookJson(currentContent: string): string {
|
|
132
|
+
const current = JSON.parse(currentContent) as unknown;
|
|
133
|
+
const result = removeNamedHookJson(isRecord(current) ? current : {});
|
|
134
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
135
|
+
}
|
|
136
|
+
|
|
118
137
|
export function reverseShellMerge(currentContent: string): string {
|
|
119
138
|
const pattern = new RegExp(`\\n?${escapeRegExp(SHELL_BEGIN)}[\\s\\S]*?${escapeRegExp(SHELL_END)}\\n?`);
|
|
120
|
-
return currentContent
|
|
139
|
+
return currentContent
|
|
140
|
+
.replace(pattern, "\n")
|
|
141
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
142
|
+
.replace(/\s*$/, "\n")
|
|
143
|
+
.replace(/^\n/, "");
|
|
121
144
|
}
|
|
122
145
|
|
|
123
146
|
export function reverseManagedMarkdown(currentContent: string): string {
|
|
124
147
|
const pattern = new RegExp(`\\n?${escapeRegExp(MARKDOWN_BEGIN)}[\\s\\S]*?${escapeRegExp(MARKDOWN_END)}\\n?`);
|
|
125
|
-
return currentContent
|
|
148
|
+
return currentContent
|
|
149
|
+
.replace(pattern, "\n")
|
|
150
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
151
|
+
.replace(/\s*$/, "\n")
|
|
152
|
+
.replace(/^\n/, "");
|
|
126
153
|
}
|
|
127
154
|
|
|
128
155
|
export function reverseTextManaged(currentContent: string): string {
|
|
129
156
|
const pattern = new RegExp(`\\n?${escapeRegExp(TEXT_BEGIN)}[\\s\\S]*?${escapeRegExp(TEXT_END)}\\n?`);
|
|
130
|
-
return currentContent
|
|
157
|
+
return currentContent
|
|
158
|
+
.replace(pattern, "\n")
|
|
159
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
160
|
+
.replace(/\s*$/, "\n")
|
|
161
|
+
.replace(/^\n/, "");
|
|
131
162
|
}
|
|
132
163
|
|
|
133
164
|
export function reversePackageJsonMerge(currentContent: string): string {
|
|
@@ -140,7 +171,9 @@ export function reversePackageJsonMerge(currentContent: string): string {
|
|
|
140
171
|
else delete result.devDependencies;
|
|
141
172
|
}
|
|
142
173
|
if (isRecord(result.scripts)) {
|
|
143
|
-
const scripts = Object.fromEntries(
|
|
174
|
+
const scripts = Object.fromEntries(
|
|
175
|
+
Object.entries(result.scripts).filter(([name]) => !name.startsWith(PHASEGATE_SCRIPT_PREFIX)),
|
|
176
|
+
);
|
|
144
177
|
if (Object.keys(scripts).length > 0) result.scripts = scripts;
|
|
145
178
|
else delete result.scripts;
|
|
146
179
|
}
|
|
@@ -214,7 +247,10 @@ export class RunUninstallUseCase {
|
|
|
214
247
|
return { plan, refused, changed, backupDir, archivedManifestPath };
|
|
215
248
|
}
|
|
216
249
|
|
|
217
|
-
private async planEntry(
|
|
250
|
+
private async planEntry(
|
|
251
|
+
input: RunUninstallInput,
|
|
252
|
+
entry: DeploymentEntry,
|
|
253
|
+
): Promise<{
|
|
218
254
|
readonly item: UninstallPlanItem;
|
|
219
255
|
readonly needsBackup: boolean;
|
|
220
256
|
readonly apply: () => Promise<void>;
|
|
@@ -222,13 +258,22 @@ export class RunUninstallUseCase {
|
|
|
222
258
|
const absolutePath = this.resolveProjectPath(input.projectRoot, entry.path);
|
|
223
259
|
const strategy = this.strategyFor(entry.path, entry.mode);
|
|
224
260
|
if (entry.mode === "symlink") return this.planSymlink(input.projectRoot, entry);
|
|
225
|
-
if (entry.mode === "created" && await this.isDirectory(absolutePath)) {
|
|
261
|
+
if (entry.mode === "created" && (await this.isDirectory(absolutePath))) {
|
|
226
262
|
return this.planCreatedDirectory(input, entry);
|
|
227
263
|
}
|
|
228
264
|
const currentContent = await readTextOrNull(absolutePath);
|
|
229
265
|
if (currentContent === null && strategy !== "symlink") {
|
|
230
266
|
return {
|
|
231
|
-
item: this.item(
|
|
267
|
+
item: this.item(
|
|
268
|
+
entry.path,
|
|
269
|
+
"skip",
|
|
270
|
+
"mechanical",
|
|
271
|
+
strategy,
|
|
272
|
+
false,
|
|
273
|
+
`${entry.path}: already absent`,
|
|
274
|
+
"no changes",
|
|
275
|
+
null,
|
|
276
|
+
),
|
|
232
277
|
needsBackup: false,
|
|
233
278
|
apply: async () => {},
|
|
234
279
|
};
|
|
@@ -249,7 +294,16 @@ export class RunUninstallUseCase {
|
|
|
249
294
|
}
|
|
250
295
|
if (target !== null && this.hashCalculator.compute(target).equals(entry.hash)) {
|
|
251
296
|
return {
|
|
252
|
-
item: this.item(
|
|
297
|
+
item: this.item(
|
|
298
|
+
entry.path,
|
|
299
|
+
"unlink",
|
|
300
|
+
"mechanical",
|
|
301
|
+
"symlink",
|
|
302
|
+
true,
|
|
303
|
+
`${entry.path}: remove symlink`,
|
|
304
|
+
`- symlink ${target}`,
|
|
305
|
+
null,
|
|
306
|
+
),
|
|
253
307
|
needsBackup: false,
|
|
254
308
|
apply: async () => {
|
|
255
309
|
await rm(absolutePath, { force: true });
|
|
@@ -257,7 +311,16 @@ export class RunUninstallUseCase {
|
|
|
257
311
|
};
|
|
258
312
|
}
|
|
259
313
|
return {
|
|
260
|
-
item: this.item(
|
|
314
|
+
item: this.item(
|
|
315
|
+
entry.path,
|
|
316
|
+
"skip",
|
|
317
|
+
"manual",
|
|
318
|
+
"symlink",
|
|
319
|
+
false,
|
|
320
|
+
`${entry.path}: non-phasegate symlink/path skipped`,
|
|
321
|
+
"manual review required",
|
|
322
|
+
null,
|
|
323
|
+
),
|
|
261
324
|
needsBackup: false,
|
|
262
325
|
apply: async () => {},
|
|
263
326
|
};
|
|
@@ -273,7 +336,8 @@ export class RunUninstallUseCase {
|
|
|
273
336
|
}
|
|
274
337
|
|
|
275
338
|
private async planCreatedDirectory(input: RunUninstallInput, entry: DeploymentEntry) {
|
|
276
|
-
if (entry.path === ".claude/skills" || entry.path === ".codex/skills"
|
|
339
|
+
if (entry.path === ".claude/skills" || entry.path === ".codex/skills" || entry.path === ".agents/skills")
|
|
340
|
+
return this.planLegacyPersonalSkillsDirectory(input, entry);
|
|
277
341
|
const absolutePath = this.resolveProjectPath(input.projectRoot, entry.path);
|
|
278
342
|
return {
|
|
279
343
|
item: this.item(
|
|
@@ -319,13 +383,19 @@ export class RunUninstallUseCase {
|
|
|
319
383
|
|
|
320
384
|
private async listBundledSkills(harnessRoot: string): Promise<string[]> {
|
|
321
385
|
const entries = await readdir(join(harnessRoot, "skills"), { withFileTypes: true });
|
|
322
|
-
return entries
|
|
386
|
+
return entries
|
|
387
|
+
.filter((entry) => entry.isDirectory())
|
|
388
|
+
.map((entry) => entry.name)
|
|
389
|
+
.sort();
|
|
323
390
|
}
|
|
324
391
|
|
|
325
392
|
private async planCreated(projectRoot: string, entry: DeploymentEntry, currentContent: string) {
|
|
326
393
|
const absolutePath = this.resolveProjectPath(projectRoot, entry.path);
|
|
327
394
|
const currentHash = this.hashCalculator.compute(currentContent);
|
|
328
|
-
const matchesManifest =
|
|
395
|
+
const matchesManifest =
|
|
396
|
+
currentHash.equals(entry.hash) ||
|
|
397
|
+
entry.path.endsWith("/.harness-version") ||
|
|
398
|
+
entry.path === "skills/.harness-version";
|
|
329
399
|
const repairMode: RepairMode = matchesManifest ? "mechanical" : "ai-assisted";
|
|
330
400
|
return {
|
|
331
401
|
item: this.item(
|
|
@@ -345,14 +415,28 @@ export class RunUninstallUseCase {
|
|
|
345
415
|
};
|
|
346
416
|
}
|
|
347
417
|
|
|
348
|
-
private async planMerged(
|
|
418
|
+
private async planMerged(
|
|
419
|
+
input: RunUninstallInput,
|
|
420
|
+
entry: DeploymentEntry,
|
|
421
|
+
currentContent: string,
|
|
422
|
+
strategy: StrategyType,
|
|
423
|
+
) {
|
|
349
424
|
const currentHash = this.hashCalculator.compute(currentContent);
|
|
350
425
|
const matchesManifest = currentHash.equals(entry.hash);
|
|
351
426
|
try {
|
|
352
427
|
const next = await this.reverseMerged(input.harnessRoot, entry.path, currentContent, strategy);
|
|
353
428
|
if (next === currentContent) {
|
|
354
429
|
return {
|
|
355
|
-
item: this.item(
|
|
430
|
+
item: this.item(
|
|
431
|
+
entry.path,
|
|
432
|
+
"skip",
|
|
433
|
+
"mechanical",
|
|
434
|
+
strategy,
|
|
435
|
+
false,
|
|
436
|
+
`${entry.path}: managed portion already absent`,
|
|
437
|
+
"no changes",
|
|
438
|
+
null,
|
|
439
|
+
),
|
|
356
440
|
needsBackup: false,
|
|
357
441
|
apply: async () => {},
|
|
358
442
|
};
|
|
@@ -376,19 +460,38 @@ export class RunUninstallUseCase {
|
|
|
376
460
|
};
|
|
377
461
|
} catch {
|
|
378
462
|
return {
|
|
379
|
-
item: this.item(
|
|
463
|
+
item: this.item(
|
|
464
|
+
entry.path,
|
|
465
|
+
"skip",
|
|
466
|
+
"manual",
|
|
467
|
+
strategy,
|
|
468
|
+
false,
|
|
469
|
+
`${entry.path}: reverse merge requires manual review`,
|
|
470
|
+
"manual review required",
|
|
471
|
+
SKILL_HINT,
|
|
472
|
+
),
|
|
380
473
|
needsBackup: false,
|
|
381
474
|
apply: async () => {},
|
|
382
475
|
};
|
|
383
476
|
}
|
|
384
477
|
}
|
|
385
478
|
|
|
386
|
-
private async reverseMerged(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
479
|
+
private async reverseMerged(
|
|
480
|
+
harnessRoot: string,
|
|
481
|
+
path: string,
|
|
482
|
+
currentContent: string,
|
|
483
|
+
strategy: StrategyType,
|
|
484
|
+
): Promise<string> {
|
|
485
|
+
if (strategy === "shell")
|
|
486
|
+
return currentContent.includes(SHELL_BEGIN) ? reverseShellMerge(currentContent) : currentContent;
|
|
487
|
+
if (strategy === "markdown-managed")
|
|
488
|
+
return currentContent.includes(MARKDOWN_BEGIN) ? reverseManagedMarkdown(currentContent) : currentContent;
|
|
489
|
+
if (strategy === "text-managed")
|
|
490
|
+
return currentContent.includes(TEXT_BEGIN) ? reverseTextManaged(currentContent) : currentContent;
|
|
390
491
|
if (strategy === "package-json") return reversePackageJsonMerge(currentContent);
|
|
391
|
-
if (strategy === "json") return
|
|
492
|
+
if (strategy === "json-named") return reverseNamedHookJson(currentContent);
|
|
493
|
+
if (strategy === "json")
|
|
494
|
+
return reverseJsonMerge(currentContent, await readFile(join(harnessRoot, this.templateFor(path)), "utf8"));
|
|
392
495
|
throw new Error(`Unsupported merged strategy: ${strategy}`);
|
|
393
496
|
}
|
|
394
497
|
|
|
@@ -396,10 +499,18 @@ export class RunUninstallUseCase {
|
|
|
396
499
|
if (mode === "symlink") return "symlink";
|
|
397
500
|
if (mode === "created") return path.endsWith(".yml") || path.endsWith(".yaml") ? "yaml-add" : "created";
|
|
398
501
|
if (path === "package.json") return "package-json";
|
|
399
|
-
if (
|
|
502
|
+
if (
|
|
503
|
+
path === "AGENTS.md" ||
|
|
504
|
+
path === "CLAUDE.md" ||
|
|
505
|
+
path === ".claude/CLAUDE.md" ||
|
|
506
|
+
path === ".claude/CLAUDE.local.md" ||
|
|
507
|
+
path === ".codex/AGENTS.local.md"
|
|
508
|
+
) {
|
|
400
509
|
return "markdown-managed";
|
|
401
510
|
}
|
|
402
511
|
if (path === ".git/info/exclude") return "text-managed";
|
|
512
|
+
if (path === ".agents/hooks.json") return "json-named";
|
|
513
|
+
if (path.startsWith(".claude/scripts/") && path.endsWith(".sh")) return "shell";
|
|
403
514
|
if (path.endsWith(".json")) return "json";
|
|
404
515
|
if (path.startsWith(".husky/")) return "shell";
|
|
405
516
|
return "unknown";
|
|
@@ -408,8 +519,11 @@ export class RunUninstallUseCase {
|
|
|
408
519
|
private templateFor(path: string): string {
|
|
409
520
|
if (path === ".claude/settings.json") return "templates/.claude/settings.json";
|
|
410
521
|
if (path === ".codex/hooks.json") return "templates/.codex/hooks.json";
|
|
522
|
+
if (path === ".agents/hooks.json") return "templates/.agents/hooks.json";
|
|
523
|
+
if (path.startsWith(".claude/scripts/")) return `templates/${path}`;
|
|
411
524
|
if (path === "CLAUDE.md") return "docs/templates/agent-context/CLAUDE.md.template.md";
|
|
412
|
-
if (path === ".claude/CLAUDE.md" || path === ".claude/CLAUDE.local.md")
|
|
525
|
+
if (path === ".claude/CLAUDE.md" || path === ".claude/CLAUDE.local.md")
|
|
526
|
+
return "docs/templates/agent-context/CLAUDE.md.template.md";
|
|
413
527
|
if (path === "AGENTS.md") return "docs/templates/agent-context/AGENTS.md.template.md";
|
|
414
528
|
if (path === ".codex/AGENTS.local.md") return "docs/templates/agent-context/AGENTS.md.template.md";
|
|
415
529
|
throw new Error(`No template for ${path}`);
|
|
@@ -455,7 +569,17 @@ export class RunUninstallUseCase {
|
|
|
455
569
|
diff: string,
|
|
456
570
|
skillHint: string | null,
|
|
457
571
|
): UninstallPlanItem {
|
|
458
|
-
return {
|
|
572
|
+
return {
|
|
573
|
+
path,
|
|
574
|
+
action,
|
|
575
|
+
repairMode,
|
|
576
|
+
strategy,
|
|
577
|
+
changed,
|
|
578
|
+
protected: this.isProtectedPath(path),
|
|
579
|
+
summary,
|
|
580
|
+
diff,
|
|
581
|
+
skillHint,
|
|
582
|
+
};
|
|
459
583
|
}
|
|
460
584
|
|
|
461
585
|
private requiresForce(item: UninstallPlanItem): boolean {
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// @work-item-id WI-148
|
|
5
5
|
// @work-item-id WI-215
|
|
6
6
|
// @work-item-id WI-330
|
|
7
|
+
// @work-item-id WI-385
|
|
8
|
+
// @work-item-id WI-390
|
|
7
9
|
|
|
10
|
+
import { AntigravityHookMissingCheck } from "./application/checks/antigravity-hook-missing-check.js";
|
|
8
11
|
import { CiWorkflowMissingCheck } from "./application/checks/ci-workflow-missing-check.js";
|
|
9
12
|
import { ClaudeContextMissingCheck } from "./application/checks/claude-context-missing-check.js";
|
|
10
13
|
import { ClaudeHookMissingCheck } from "./application/checks/claude-hook-missing-check.js";
|
|
@@ -13,9 +16,11 @@ import { CodexContextMissingCheck } from "./application/checks/codex-context-mis
|
|
|
13
16
|
import { CodexHookMissingCheck } from "./application/checks/codex-hook-missing-check.js";
|
|
14
17
|
import { CodexSkillsSymlinkCheck } from "./application/checks/codex-skills-symlink-check.js";
|
|
15
18
|
import { ConfigStatusCheck } from "./application/checks/config-status-check.js";
|
|
19
|
+
import { GrokHookMissingCheck } from "./application/checks/grok-hook-missing-check.js";
|
|
16
20
|
import { HuskyCommitMsgMissingCheck } from "./application/checks/husky-commit-msg-missing-check.js";
|
|
17
21
|
import { HuskyPreCommitMissingCheck } from "./application/checks/husky-pre-commit-missing-check.js";
|
|
18
22
|
import { HuskyPrePushMissingCheck } from "./application/checks/husky-pre-push-missing-check.js";
|
|
23
|
+
import { HuskyRuntimeInactiveCheck } from "./application/checks/husky-runtime-inactive-check.js";
|
|
19
24
|
import { PackageJsonDevdepMissingCheck } from "./application/checks/package-json-devdep-missing-check.js";
|
|
20
25
|
import { WiWorkflowDriftCheck } from "./application/checks/wi-workflow-drift-check.js";
|
|
21
26
|
import { RunDoctorDiagnosticsUseCase } from "./application/usecases/run-doctor-diagnostics.js";
|
|
@@ -29,6 +34,7 @@ import { ConfigStatusProbeAdapter } from "./infrastructure/adapters/config-statu
|
|
|
29
34
|
import { FileSystemManifestRepositoryAdapter } from "./infrastructure/adapters/file-system-manifest-repository-adapter.js";
|
|
30
35
|
import { NodeCryptoHashAdapter } from "./infrastructure/adapters/node-crypto-hash-adapter.js";
|
|
31
36
|
import { NodeFsFileInspectorAdapter } from "./infrastructure/adapters/node-fs-file-inspector-adapter.js";
|
|
37
|
+
import { GitHooksRuntimeProbeAdapter } from "./infrastructure/adapters/git-hooks-runtime-probe-adapter.js";
|
|
32
38
|
import { SkillDeployerModelDelegationAdapter } from "./infrastructure/adapters/skill-deployer-model-delegation-adapter.js";
|
|
33
39
|
import { DoctorHandler } from "./presentation/cli/doctor-handler.js";
|
|
34
40
|
import { InstallHandler } from "./presentation/cli/install-handler.js";
|
|
@@ -49,14 +55,18 @@ export function createInstallationModule() {
|
|
|
49
55
|
const hashCalculator = new NodeCryptoHashAdapter();
|
|
50
56
|
const modelDelegation = new SkillDeployerModelDelegationAdapter();
|
|
51
57
|
const configStatusProbe = new ConfigStatusProbeAdapter();
|
|
58
|
+
const gitHooksRuntimeProbe = new GitHooksRuntimeProbeAdapter();
|
|
52
59
|
const checks = [
|
|
53
60
|
new ClaudeHookMissingCheck(),
|
|
54
61
|
new ClaudeContextMissingCheck(),
|
|
55
62
|
new CodexHookMissingCheck(),
|
|
56
63
|
new CodexContextMissingCheck(),
|
|
64
|
+
new GrokHookMissingCheck(),
|
|
65
|
+
new AntigravityHookMissingCheck(),
|
|
57
66
|
new HuskyPreCommitMissingCheck(),
|
|
58
67
|
new HuskyCommitMsgMissingCheck(),
|
|
59
68
|
new HuskyPrePushMissingCheck(),
|
|
69
|
+
new HuskyRuntimeInactiveCheck(gitHooksRuntimeProbe),
|
|
60
70
|
new CiWorkflowMissingCheck(),
|
|
61
71
|
new PackageJsonDevdepMissingCheck(),
|
|
62
72
|
new ClaudeSkillsSymlinkCheck(),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @unit installation
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-385
|
|
4
|
+
|
|
5
|
+
export const AGENT_TARGETS = ["claude", "codex", "both", "grok", "antigravity", "all"] as const;
|
|
6
|
+
|
|
7
|
+
export type AgentTarget = (typeof AGENT_TARGETS)[number];
|
|
8
|
+
|
|
9
|
+
export interface AgentTargetSelection {
|
|
10
|
+
readonly claudeHook: boolean;
|
|
11
|
+
readonly claudeContext: boolean;
|
|
12
|
+
readonly claudeSkills: boolean;
|
|
13
|
+
readonly codexHook: boolean;
|
|
14
|
+
readonly codexSkills: boolean;
|
|
15
|
+
readonly agentsContext: boolean;
|
|
16
|
+
readonly antigravityHook: boolean;
|
|
17
|
+
readonly antigravitySkills: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isAgentTarget(value: string): value is AgentTarget {
|
|
21
|
+
return (AGENT_TARGETS as readonly string[]).includes(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveAgentTarget(target: AgentTarget): AgentTargetSelection {
|
|
25
|
+
const includesClaude = target === "claude" || target === "both" || target === "all";
|
|
26
|
+
const includesCodex = target === "codex" || target === "both" || target === "all";
|
|
27
|
+
const includesGrok = target === "grok" || target === "all";
|
|
28
|
+
const includesAntigravity = target === "antigravity" || target === "all";
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
claudeHook: includesClaude || includesGrok,
|
|
31
|
+
claudeContext: includesClaude,
|
|
32
|
+
claudeSkills: includesClaude || includesGrok,
|
|
33
|
+
codexHook: includesCodex,
|
|
34
|
+
codexSkills: includesCodex,
|
|
35
|
+
agentsContext: includesCodex || includesGrok || includesAntigravity,
|
|
36
|
+
antigravityHook: includesAntigravity,
|
|
37
|
+
antigravitySkills: includesAntigravity,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
// @work-item-id WI-145
|
|
4
4
|
// @work-item-id WI-215
|
|
5
5
|
// @work-item-id WI-330
|
|
6
|
+
// @work-item-id WI-385
|
|
7
|
+
// @work-item-id WI-390
|
|
6
8
|
|
|
7
9
|
export const CHECK_IDS = [
|
|
8
10
|
"claude-hook-missing",
|
|
9
11
|
"claude-context-missing",
|
|
10
12
|
"codex-hook-missing",
|
|
11
13
|
"codex-context-missing",
|
|
14
|
+
"grok-hook-missing",
|
|
15
|
+
"antigravity-hook-missing",
|
|
12
16
|
"husky-pre-commit-missing",
|
|
13
17
|
"husky-commit-msg-missing",
|
|
14
18
|
"husky-pre-push-missing",
|
|
19
|
+
"husky-runtime-inactive",
|
|
15
20
|
"ci-workflow-missing",
|
|
16
21
|
"package-json-devdep-missing",
|
|
17
22
|
"claude-skills-symlink",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @unit installation
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-390
|
|
4
|
+
|
|
5
|
+
export type HuskyRuntimeInactiveReason = 'hooks-path-unset' | 'hooks-path-unsupported' | 'shim-missing';
|
|
6
|
+
|
|
7
|
+
export type HuskyRuntimeKind = 'active' | 'inactive' | 'unavailable';
|
|
8
|
+
|
|
9
|
+
export class HuskyRuntimeState {
|
|
10
|
+
private constructor(
|
|
11
|
+
readonly kind: HuskyRuntimeKind,
|
|
12
|
+
readonly hooksPath: string | null,
|
|
13
|
+
readonly reason: HuskyRuntimeInactiveReason | null,
|
|
14
|
+
readonly detail: string | null,
|
|
15
|
+
) {
|
|
16
|
+
Object.freeze(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static active(hooksPath: '.husky' | '.husky/_'): HuskyRuntimeState {
|
|
20
|
+
return new HuskyRuntimeState('active', hooksPath, null, null);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static inactive(reason: HuskyRuntimeInactiveReason, hooksPath: string | null): HuskyRuntimeState {
|
|
24
|
+
return new HuskyRuntimeState('inactive', hooksPath, reason, null);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static unavailable(detail: string): HuskyRuntimeState {
|
|
28
|
+
return new HuskyRuntimeState('unavailable', null, null, detail);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
isActive(): boolean {
|
|
32
|
+
return this.kind === 'active';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// @layer domain
|
|
3
3
|
// @work-item-id WI-145
|
|
4
4
|
// @work-item-id WI-215
|
|
5
|
+
// @work-item-id WI-385
|
|
6
|
+
// @work-item-id WI-390
|
|
5
7
|
|
|
6
8
|
import type { CheckId } from "./check-id.js";
|
|
7
9
|
import { SuggestedSkill } from "./suggested-skill.js";
|
|
@@ -27,9 +29,12 @@ export class RepairTable {
|
|
|
27
29
|
["claude-context-missing", CONFIG_DOCTOR],
|
|
28
30
|
["codex-hook-missing", CONFIG_DOCTOR],
|
|
29
31
|
["codex-context-missing", CONFIG_DOCTOR],
|
|
32
|
+
["grok-hook-missing", CONFIG_DOCTOR],
|
|
33
|
+
["antigravity-hook-missing", CONFIG_DOCTOR],
|
|
30
34
|
["husky-pre-commit-missing", CONFIG_DOCTOR],
|
|
31
35
|
["husky-commit-msg-missing", CONFIG_DOCTOR],
|
|
32
36
|
["husky-pre-push-missing", null],
|
|
37
|
+
["husky-runtime-inactive", null],
|
|
33
38
|
["ci-workflow-missing", TOOLKIT_GUIDE],
|
|
34
39
|
["package-json-devdep-missing", null],
|
|
35
40
|
["claude-skills-symlink", null],
|
package/scripts/harness/installation/infrastructure/adapters/git-hooks-runtime-probe-adapter.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @unit installation
|
|
2
|
+
// @layer infrastructure
|
|
3
|
+
// @work-item-id WI-390
|
|
4
|
+
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import { access } from 'node:fs/promises';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import { HuskyRuntimeState } from '../../domain/husky-runtime-state.js';
|
|
10
|
+
import type { GitHooksRuntimeProbe } from '../../domain/ports/git-hooks-runtime-probe.js';
|
|
11
|
+
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
const HUSKY_V9_SHIMS = ['h', 'pre-commit', 'commit-msg', 'pre-push'] as const;
|
|
14
|
+
|
|
15
|
+
async function pathExists(targetPath: string): Promise<boolean> {
|
|
16
|
+
try {
|
|
17
|
+
await access(targetPath);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeHooksPath(value: string): string {
|
|
25
|
+
return value.trim().replaceAll('\\', '/').replace(/^\.\//, '').replace(/\/+$/, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class GitHooksRuntimeProbeAdapter implements GitHooksRuntimeProbe {
|
|
29
|
+
async probe(projectRoot: string): Promise<HuskyRuntimeState> {
|
|
30
|
+
let stdout: string;
|
|
31
|
+
try {
|
|
32
|
+
({ stdout } = await execFileAsync('git', ['-C', projectRoot, 'config', '--get', 'core.hooksPath']));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : null;
|
|
35
|
+
if (code === 1) {
|
|
36
|
+
return HuskyRuntimeState.inactive('hooks-path-unset', null);
|
|
37
|
+
}
|
|
38
|
+
return HuskyRuntimeState.unavailable(error instanceof Error ? error.message : String(error));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const hooksPath = normalizeHooksPath(stdout);
|
|
42
|
+
if (hooksPath.length === 0) {
|
|
43
|
+
return HuskyRuntimeState.inactive('hooks-path-unset', null);
|
|
44
|
+
}
|
|
45
|
+
if (hooksPath !== '.husky' && hooksPath !== '.husky/_') {
|
|
46
|
+
return HuskyRuntimeState.inactive('hooks-path-unsupported', hooksPath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (hooksPath === '.husky') {
|
|
50
|
+
return (await pathExists(join(projectRoot, '.husky')))
|
|
51
|
+
? HuskyRuntimeState.active('.husky')
|
|
52
|
+
: HuskyRuntimeState.inactive('shim-missing', hooksPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const shimsPresent = await Promise.all(
|
|
56
|
+
HUSKY_V9_SHIMS.map((shim) => pathExists(join(projectRoot, '.husky', '_', shim))),
|
|
57
|
+
);
|
|
58
|
+
return shimsPresent.every(Boolean)
|
|
59
|
+
? HuskyRuntimeState.active('.husky/_')
|
|
60
|
+
: HuskyRuntimeState.inactive('shim-missing', hooksPath);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -3,8 +3,17 @@
|
|
|
3
3
|
// @work-item-id WI-146
|
|
4
4
|
// @work-item-id WI-175
|
|
5
5
|
// @work-item-id WI-207
|
|
6
|
+
// @work-item-id WI-384
|
|
7
|
+
// @work-item-id WI-385
|
|
6
8
|
|
|
9
|
+
import {
|
|
10
|
+
ANTIGRAVITY_CLI_ONLY_NOTICE,
|
|
11
|
+
CODEX_HOOK_TRUST_REQUIRED_NOTICE,
|
|
12
|
+
GROK_HOOK_TRUST_UNVERIFIABLE_NOTICE,
|
|
13
|
+
} from "../../application/operator-notice.js";
|
|
7
14
|
import type { RunInstallUseCase } from "../../application/usecases/run-install.js";
|
|
15
|
+
import type { AgentTarget } from "../../domain/agent-target.js";
|
|
16
|
+
import type { SkillSet } from "../../application/bundled-skill-selection.js";
|
|
8
17
|
|
|
9
18
|
export interface InstallHandlerInput {
|
|
10
19
|
readonly projectRoot: string;
|
|
@@ -18,9 +27,9 @@ export interface InstallHandlerInput {
|
|
|
18
27
|
readonly includeCodex?: boolean;
|
|
19
28
|
readonly includeHusky?: boolean;
|
|
20
29
|
readonly includeCi?: boolean;
|
|
21
|
-
readonly skillSet?:
|
|
30
|
+
readonly skillSet?: SkillSet;
|
|
22
31
|
readonly workflow?: "standard" | "strict";
|
|
23
|
-
readonly agent?:
|
|
32
|
+
readonly agent?: AgentTarget;
|
|
24
33
|
readonly personal?: boolean;
|
|
25
34
|
}
|
|
26
35
|
|
|
@@ -34,14 +43,23 @@ export class InstallHandler {
|
|
|
34
43
|
|
|
35
44
|
async execute(input: InstallHandlerInput): Promise<InstallHandlerResult> {
|
|
36
45
|
const result = await this.useCase.execute(input);
|
|
46
|
+
const operatorNotices = [
|
|
47
|
+
...(result.plan.some((item) => item.path === ".codex/hooks.json" && item.changed)
|
|
48
|
+
? [CODEX_HOOK_TRUST_REQUIRED_NOTICE]
|
|
49
|
+
: []),
|
|
50
|
+
...(input.agent === "grok" || input.agent === "all" ? [GROK_HOOK_TRUST_UNVERIFIABLE_NOTICE] : []),
|
|
51
|
+
...(input.agent === "antigravity" || input.agent === "all" ? [ANTIGRAVITY_CLI_ONLY_NOTICE] : []),
|
|
52
|
+
];
|
|
37
53
|
if (input.json) {
|
|
38
54
|
return {
|
|
39
|
-
stdout: JSON.stringify(result, null, 2),
|
|
55
|
+
stdout: JSON.stringify({ ...result, operatorNotices }, null, 2),
|
|
40
56
|
exitCode: result.refused.length > 0 || result.error !== undefined ? 1 : 0,
|
|
41
57
|
};
|
|
42
58
|
}
|
|
43
59
|
const lines = [
|
|
44
|
-
input.apply
|
|
60
|
+
input.apply
|
|
61
|
+
? `phasegate install${input.personal ? " --personal" : ""} apply`
|
|
62
|
+
: `phasegate install${input.personal ? " --personal" : ""} dry-run`,
|
|
45
63
|
...result.plan.map((item) => {
|
|
46
64
|
const hint = item.skillHint ? `; hint: ${item.skillHint}` : "";
|
|
47
65
|
const warn = item.warning ? `\n WARNING: ${item.warning}` : "";
|
|
@@ -49,12 +67,14 @@ export class InstallHandler {
|
|
|
49
67
|
}),
|
|
50
68
|
];
|
|
51
69
|
if (result.backupDir !== null) lines.push(`backups: ${result.backupDir}`);
|
|
70
|
+
for (const notice of operatorNotices) lines.push(`Notice [${notice.code}]: ${notice.message}`);
|
|
52
71
|
if (result.error !== undefined) {
|
|
53
72
|
lines.push("");
|
|
54
73
|
lines.push(`Apply error: ${result.error.target} ${result.error.operation} failed with ${result.error.code}`);
|
|
55
74
|
lines.push(`Cause: ${result.error.likelyCause}`);
|
|
56
75
|
lines.push(`Recovery: ${result.error.recovery}`);
|
|
57
|
-
if (result.error.partialChanges.length > 0)
|
|
76
|
+
if (result.error.partialChanges.length > 0)
|
|
77
|
+
lines.push(`Partial changes: ${result.error.partialChanges.join(", ")}`);
|
|
58
78
|
}
|
|
59
79
|
if (result.refused.length > 0) {
|
|
60
80
|
lines.push("");
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// @unit installation
|
|
2
2
|
// @layer presentation
|
|
3
3
|
// @work-item-id WI-148
|
|
4
|
+
// @work-item-id WI-384
|
|
4
5
|
|
|
5
6
|
import type { RunReconcileUseCase } from "../../application/usecases/run-reconcile.js";
|
|
7
|
+
import { CODEX_HOOK_TRUST_REQUIRED_NOTICE } from "../../application/operator-notice.js";
|
|
6
8
|
|
|
7
9
|
export interface ReconcileHandlerInput {
|
|
8
10
|
readonly projectRoot: string;
|
|
@@ -24,9 +26,12 @@ export class ReconcileHandler {
|
|
|
24
26
|
|
|
25
27
|
async execute(input: ReconcileHandlerInput): Promise<ReconcileHandlerResult> {
|
|
26
28
|
const result = await this.useCase.execute(input);
|
|
29
|
+
const operatorNotices = result.plan.some((item) => item.path === ".codex/hooks.json" && item.changed)
|
|
30
|
+
? [CODEX_HOOK_TRUST_REQUIRED_NOTICE]
|
|
31
|
+
: [];
|
|
27
32
|
if (input.json) {
|
|
28
33
|
return {
|
|
29
|
-
stdout: JSON.stringify(result, null, 2),
|
|
34
|
+
stdout: JSON.stringify({ ...result, operatorNotices }, null, 2),
|
|
30
35
|
exitCode: result.refused.length > 0 ? 1 : 0,
|
|
31
36
|
};
|
|
32
37
|
}
|
|
@@ -38,6 +43,7 @@ export class ReconcileHandler {
|
|
|
38
43
|
}),
|
|
39
44
|
];
|
|
40
45
|
if (result.backupDir !== null) lines.push(`backups: ${result.backupDir}`);
|
|
46
|
+
for (const notice of operatorNotices) lines.push(`Notice [${notice.code}]: ${notice.message}`);
|
|
41
47
|
if (result.refused.length > 0) {
|
|
42
48
|
lines.push("");
|
|
43
49
|
lines.push("Refused ai-assisted/manual targets. Re-run with --force after reviewing the hint.");
|