intentdna 1.8.4 → 1.8.6
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/dist/cli/commands/sync.js +19 -12
- package/dist/cli/commands/verify.js +1 -1
- package/dist/runtime/claude-sync-target.d.ts +3 -1
- package/dist/runtime/claude-sync-target.js +24 -7
- package/dist/runtime/settings-adapter.d.ts +12 -0
- package/dist/runtime/settings-adapter.js +119 -2
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "intentdna",
|
|
11
11
|
"description": "Declarative policy layer for AI agent behavior with plugin-managed hook runtime for Claude Code.",
|
|
12
|
-
"version": "1.8.
|
|
12
|
+
"version": "1.8.6",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Samuel"
|
|
15
15
|
},
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
]
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"version": "1.8.
|
|
28
|
+
"version": "1.8.6"
|
|
29
29
|
}
|
package/README.md
CHANGED
|
@@ -17,11 +17,11 @@ IntentDNA is not a model company, generic agent framework, prompt library, canva
|
|
|
17
17
|
## Install or update
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install --global intentdna@1.8.
|
|
20
|
+
npm install --global intentdna@1.8.5
|
|
21
21
|
dna --version
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Version `1.8.
|
|
24
|
+
Version `1.8.5` keeps Claude plugin Hook delivery in `hooks/hooks.json`: plugin sync does not register project `settings.json` Hooks, retracts exact stale bin-mode `dna-hook` entries once, and leaves user settings untouched in steady state. Online generates copyable local commands only; you still run and review them in your own project.
|
|
25
25
|
|
|
26
26
|
## Product Shape
|
|
27
27
|
|
|
@@ -612,6 +612,13 @@ export async function runSync(opts) {
|
|
|
612
612
|
opts.skillsDir = resolve(projectDir, ".agents", "skills");
|
|
613
613
|
process.stderr.write("Codex workflow projection: .agents/skills\n");
|
|
614
614
|
}
|
|
615
|
+
const requestedMode = opts.mode ?? (opts.plugin === true ? "plugin" : opts.plugin === false ? "bin" : "auto");
|
|
616
|
+
let resolvedMode = requestedMode === "auto" ? "bin" : requestedMode;
|
|
617
|
+
if (!opts.remove && requestedMode === "auto") {
|
|
618
|
+
resolvedMode = await detectMode();
|
|
619
|
+
process.stderr.write(`Auto-detect: ${resolvedMode} mode${resolvedMode === "plugin" ? " (intentdna registered as Claude Code plugin)" : ""}\n`);
|
|
620
|
+
}
|
|
621
|
+
opts.plugin = resolvedMode === "plugin";
|
|
615
622
|
if (!opts.remove && !opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath && !opts.codex) {
|
|
616
623
|
// Auto-detect Claude Code environment → fill all outputs
|
|
617
624
|
const hasClaudeDir = await fileExists(resolve(projectDir, ".claude"));
|
|
@@ -621,16 +628,11 @@ export async function runSync(opts) {
|
|
|
621
628
|
opts.agentsDir = resolve(projectDir, ".claude", "agents");
|
|
622
629
|
opts.skillsDir = resolve(projectDir, ".claude", "skills");
|
|
623
630
|
opts.settingsPath = resolve(projectDir, ".claude", "settings.json");
|
|
624
|
-
process.stderr.write(
|
|
631
|
+
process.stderr.write(resolvedMode === "plugin"
|
|
632
|
+
? "Auto-detect: Claude Code → CLAUDE.md + agents + skills; plugin hooks (no settings injection)\n"
|
|
633
|
+
: "Auto-detect: Claude Code → CLAUDE.md + agents + skills + settings\n");
|
|
625
634
|
}
|
|
626
635
|
}
|
|
627
|
-
const requestedMode = opts.mode ?? (opts.plugin === true ? "plugin" : opts.plugin === false ? "bin" : "auto");
|
|
628
|
-
let resolvedMode = requestedMode === "auto" ? "bin" : requestedMode;
|
|
629
|
-
if (!opts.remove && requestedMode === "auto") {
|
|
630
|
-
resolvedMode = await detectMode();
|
|
631
|
-
process.stderr.write(`Auto-detect: ${resolvedMode} mode${resolvedMode === "plugin" ? " (intentdna registered as Claude Code plugin)" : ""}\n`);
|
|
632
|
-
}
|
|
633
|
-
opts.plugin = resolvedMode === "plugin";
|
|
634
636
|
// Handle --remove
|
|
635
637
|
if (opts.remove) {
|
|
636
638
|
if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.codex) {
|
|
@@ -919,7 +921,7 @@ export async function runSync(opts) {
|
|
|
919
921
|
}
|
|
920
922
|
const configPath = opts.files.length === 1 ? opts.files[0] : null;
|
|
921
923
|
const variables = await resolveVariables(rawVars, configPath, { dryRun: opts.dryRun });
|
|
922
|
-
// Step 8: Generate .
|
|
924
|
+
// Step 8: Generate project-root .mcp.json only for Claude Code skills.
|
|
923
925
|
if (skillSurface === "claude_code") {
|
|
924
926
|
const mcpDeps = {};
|
|
925
927
|
for (const d of dnas) {
|
|
@@ -927,7 +929,7 @@ export async function runSync(opts) {
|
|
|
927
929
|
Object.assign(mcpDeps, d.mcp);
|
|
928
930
|
}
|
|
929
931
|
if (Object.keys(mcpDeps).length > 0) {
|
|
930
|
-
const mcpJsonPath = resolve(projectDir, ".
|
|
932
|
+
const mcpJsonPath = resolve(projectDir, ".mcp.json");
|
|
931
933
|
if (opts.dryRun) {
|
|
932
934
|
process.stderr.write(`Dry-run: would write ${Object.keys(mcpDeps).length} MCP server(s) to ${mcpJsonPath}\n`);
|
|
933
935
|
}
|
|
@@ -1061,7 +1063,7 @@ export async function runSync(opts) {
|
|
|
1061
1063
|
}
|
|
1062
1064
|
}
|
|
1063
1065
|
/**
|
|
1064
|
-
* Write MCP server configuration to .
|
|
1066
|
+
* Write MCP server configuration to the Claude project-root .mcp.json.
|
|
1065
1067
|
* Merges with existing config — DNA-declared servers are added/updated,
|
|
1066
1068
|
* but existing user-configured servers are NOT overwritten.
|
|
1067
1069
|
*/
|
|
@@ -1072,7 +1074,12 @@ async function writeMCPConfig(mcpDeps, mcpJsonPath, variables) {
|
|
|
1072
1074
|
const raw = await readFile(mcpJsonPath, "utf-8");
|
|
1073
1075
|
existing = JSON.parse(raw);
|
|
1074
1076
|
}
|
|
1075
|
-
catch {
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
1079
|
+
if (code !== "ENOENT") {
|
|
1080
|
+
throw new Error(`Cannot read Claude MCP config: ${mcpJsonPath}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1076
1083
|
const mcpServers = (existing.mcpServers ?? {});
|
|
1077
1084
|
for (const [name, server] of Object.entries(mcpDeps)) {
|
|
1078
1085
|
// Don't overwrite existing user-configured servers
|
|
@@ -524,7 +524,7 @@ export async function runHealth() {
|
|
|
524
524
|
}
|
|
525
525
|
catch {
|
|
526
526
|
// Check for plugin registration instead
|
|
527
|
-
const mcpPath = resolve(cwd, ".
|
|
527
|
+
const mcpPath = resolve(cwd, ".mcp.json");
|
|
528
528
|
try {
|
|
529
529
|
const raw = await rf(mcpPath, "utf-8");
|
|
530
530
|
const mcp = JSON.parse(raw);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ConstraintIR, RoleDef } from "../schema/types.js";
|
|
2
2
|
import type { SyncOutputArtifactId } from "./output-artifact-registry.js";
|
|
3
3
|
import { type CompiledIRFile } from "./plugin-adapter.js";
|
|
4
|
-
import { type DNASettings, type HookEventKey } from "./settings-adapter.js";
|
|
4
|
+
import { type DNASettings, type HookEventKey, type IntentDNASettingsHookCleanupResult } from "./settings-adapter.js";
|
|
5
5
|
import type { AdapterCapabilityReport, CapabilityReportEntry, PlannedArtifact, SyncTargetPlan } from "./sync-target-plan.js";
|
|
6
6
|
export type ClaudeSyncMode = "plugin" | "bin";
|
|
7
7
|
export interface ClaudeCorePlannedArtifact extends PlannedArtifact {
|
|
@@ -31,6 +31,7 @@ export interface ClaudeCoreSyncTargetPlan extends SyncTargetPlan {
|
|
|
31
31
|
legacyHooksDir: string;
|
|
32
32
|
mcpConfigPath: string;
|
|
33
33
|
settingsHookRegistration?: ClaudeSettingsHookRegistration;
|
|
34
|
+
pluginSettingsPath?: string;
|
|
34
35
|
pluginSkipsSettings: boolean;
|
|
35
36
|
plannedArtifacts: ClaudeCorePlannedArtifact[];
|
|
36
37
|
capabilityReport: ClaudeCoreAdapterCapabilityReport;
|
|
@@ -38,6 +39,7 @@ export interface ClaudeCoreSyncTargetPlan extends SyncTargetPlan {
|
|
|
38
39
|
export interface ExecuteClaudeCoreSyncTargetPlanResult {
|
|
39
40
|
messages: string[];
|
|
40
41
|
removedLegacyHooks: string[];
|
|
42
|
+
settingsHookCleanup?: IntentDNASettingsHookCleanupResult;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* Remove legacy bash hook scripts written by previous Intent DNA versions.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { createCompiledIR, writeCompiledIR } from "./plugin-adapter.js";
|
|
4
|
-
import { compilePluginSettings, detectEnabledEvents, mergeSettingsFile, recommendHookTimeouts } from "./settings-adapter.js";
|
|
4
|
+
import { compilePluginSettings, detectEnabledEvents, mergeSettingsFile, recommendHookTimeouts, removeIntentDNASettingsHooks, } from "./settings-adapter.js";
|
|
5
5
|
import { formatSyncTargetPlanDiagnostics } from "./sync-target-plan.js";
|
|
6
6
|
const LEGACY_BASH_HOOKS = [
|
|
7
7
|
"dna-pre-tool-use.sh",
|
|
@@ -46,7 +46,7 @@ export function createClaudeCoreSyncTargetPlan(options) {
|
|
|
46
46
|
const compiledIR = createCompiledIR(options.ir, options.roles);
|
|
47
47
|
const compiledIRPath = resolve(options.projectDir, ".dna", "compiled", "ir.json");
|
|
48
48
|
const legacyHooksDir = resolve(options.projectDir, ".claude", "hooks");
|
|
49
|
-
const mcpConfigPath = resolve(options.projectDir, ".
|
|
49
|
+
const mcpConfigPath = resolve(options.projectDir, ".mcp.json");
|
|
50
50
|
const settingsHookRegistration = createSettingsHookRegistration(options.ir, options.mode, options.settingsPath);
|
|
51
51
|
const plannedArtifacts = [
|
|
52
52
|
{
|
|
@@ -83,6 +83,7 @@ export function createClaudeCoreSyncTargetPlan(options) {
|
|
|
83
83
|
legacyHooksDir,
|
|
84
84
|
mcpConfigPath,
|
|
85
85
|
settingsHookRegistration,
|
|
86
|
+
pluginSettingsPath: options.mode === "plugin" ? options.settingsPath : undefined,
|
|
86
87
|
pluginSkipsSettings: options.mode === "plugin" && !!options.settingsPath,
|
|
87
88
|
plannedArtifacts,
|
|
88
89
|
capabilityReport: createClaudeCapabilityReport(plannedArtifacts, options.mode, settingsHookRegistration),
|
|
@@ -217,13 +218,18 @@ function createClaudeEvidencePlan(plannedArtifacts) {
|
|
|
217
218
|
};
|
|
218
219
|
}
|
|
219
220
|
async function registerDNAMCPServer(projectDir) {
|
|
220
|
-
const mcpJsonPath = resolve(projectDir, ".
|
|
221
|
+
const mcpJsonPath = resolve(projectDir, ".mcp.json");
|
|
221
222
|
let existing = {};
|
|
222
223
|
try {
|
|
223
224
|
const raw = await readFile(mcpJsonPath, "utf-8");
|
|
224
225
|
existing = JSON.parse(raw);
|
|
225
226
|
}
|
|
226
|
-
catch {
|
|
227
|
+
catch (error) {
|
|
228
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
229
|
+
if (code !== "ENOENT") {
|
|
230
|
+
throw new Error(`Cannot read Claude MCP config: ${mcpJsonPath}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
227
233
|
const mcpServers = (existing.mcpServers ?? {});
|
|
228
234
|
if (mcpServers["intentdna"])
|
|
229
235
|
return { path: mcpJsonPath, registered: false };
|
|
@@ -238,6 +244,7 @@ async function registerDNAMCPServer(projectDir) {
|
|
|
238
244
|
}
|
|
239
245
|
export async function executeClaudeCoreSyncTargetPlan(plan, projectDir) {
|
|
240
246
|
const messages = [];
|
|
247
|
+
let settingsHookCleanup;
|
|
241
248
|
const writtenIrPath = await writeCompiledIR(projectDir, plan.compiledIR);
|
|
242
249
|
messages.push(`Wrote compiled IR: ${writtenIrPath}`);
|
|
243
250
|
const removedLegacyHooks = await removeLegacyBashHooks(plan.legacyHooksDir);
|
|
@@ -253,9 +260,19 @@ export async function executeClaudeCoreSyncTargetPlan(plan, projectDir) {
|
|
|
253
260
|
messages.push(`Bin mode: registered ${plan.settingsHookRegistration.enabledEvents.length} hook event(s) in ${plan.settingsHookRegistration.path}`);
|
|
254
261
|
}
|
|
255
262
|
else if (plan.pluginSkipsSettings) {
|
|
256
|
-
|
|
263
|
+
settingsHookCleanup = await removeIntentDNASettingsHooks(plan.pluginSettingsPath);
|
|
264
|
+
if (settingsHookCleanup.removedCommands > 0) {
|
|
265
|
+
const action = settingsHookCleanup.removedFile ? "removed" : "cleaned";
|
|
266
|
+
messages.push(`Plugin mode: ${action} ${settingsHookCleanup.removedCommands} stale bin-mode dna-hook command(s) from ${plan.pluginSettingsPath}; hooks are managed by plugin framework`);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
messages.push("Plugin mode: hooks managed by plugin framework (settings.json not written)");
|
|
270
|
+
}
|
|
271
|
+
if (settingsHookCleanup.warning) {
|
|
272
|
+
messages.push(`Plugin mode: preserved settings.json without cleanup (${settingsHookCleanup.warning})`);
|
|
273
|
+
}
|
|
257
274
|
}
|
|
258
|
-
return { messages, removedLegacyHooks };
|
|
275
|
+
return { messages, removedLegacyHooks, settingsHookCleanup };
|
|
259
276
|
}
|
|
260
277
|
export function formatClaudeCoreSyncTargetPlanDryRun(plan) {
|
|
261
278
|
const lines = [
|
|
@@ -267,7 +284,7 @@ export function formatClaudeCoreSyncTargetPlanDryRun(plan) {
|
|
|
267
284
|
lines.push(`Dry-run: would register ${plan.settingsHookRegistration.enabledEvents.length} hook event(s) in ${plan.settingsHookRegistration.path}`);
|
|
268
285
|
}
|
|
269
286
|
else if (plan.pluginSkipsSettings) {
|
|
270
|
-
lines.push("Plugin mode: hooks managed by plugin framework
|
|
287
|
+
lines.push("Plugin mode: hooks managed by plugin framework; would remove stale bin-mode dna-hook commands if present and would not register settings hooks");
|
|
271
288
|
}
|
|
272
289
|
const capabilityEntries = Object.entries(plan.capabilityReport.capabilities)
|
|
273
290
|
.sort(([left], [right]) => left.localeCompare(right));
|
|
@@ -18,6 +18,13 @@ export interface SettingsHookEntry {
|
|
|
18
18
|
export interface DNASettings {
|
|
19
19
|
hooks: Record<string, SettingsHookEntry[]>;
|
|
20
20
|
}
|
|
21
|
+
export interface IntentDNASettingsHookCleanupResult {
|
|
22
|
+
changed: boolean;
|
|
23
|
+
removedCommands: number;
|
|
24
|
+
removedEvents: string[];
|
|
25
|
+
removedFile: boolean;
|
|
26
|
+
warning?: string;
|
|
27
|
+
}
|
|
21
28
|
/**
|
|
22
29
|
* Compile settings.json hook configuration for dna-hook binary.
|
|
23
30
|
* Uses `dna-hook <event>` commands.
|
|
@@ -34,3 +41,8 @@ export declare function detectEnabledEvents(ir: import("../schema/types.js").Con
|
|
|
34
41
|
* Replaces DNA hooks (identified by "dna-" prefix), keeps user hooks.
|
|
35
42
|
*/
|
|
36
43
|
export declare function mergeSettingsFile(dnaSettings: DNASettings, settingsPath: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Remove bin-mode IntentDNA hook commands when Claude plugin hooks own dispatch.
|
|
46
|
+
* Invalid or user-owned settings are preserved without rewriting the file.
|
|
47
|
+
*/
|
|
48
|
+
export declare function removeIntentDNASettingsHooks(settingsPath: string): Promise<IntentDNASettingsHookCleanupResult>;
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* See docs/archive/2026H1/references/cc-source-analysis.md §2 for the full hook event catalog.
|
|
8
8
|
*/
|
|
9
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
10
10
|
import { getHookEventBySettingsKey, settingsHookEventKeys } from "../hooks/event-registry.js";
|
|
11
|
-
// ──
|
|
11
|
+
// ── Bin Mode (project settings via dna-hook binary) ───────
|
|
12
12
|
/**
|
|
13
13
|
* Compile settings.json hook configuration for dna-hook binary.
|
|
14
14
|
* Uses `dna-hook <event>` commands.
|
|
@@ -131,3 +131,120 @@ export async function mergeSettingsFile(dnaSettings, settingsPath) {
|
|
|
131
131
|
existing.hooks = mergedHooks;
|
|
132
132
|
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
133
133
|
}
|
|
134
|
+
const INTENTDNA_SETTINGS_HOOK_COMMANDS = new Set(settingsHookEventKeys().map((key) => `dna-hook ${getHookEventBySettingsKey(key).event}`));
|
|
135
|
+
function isIntentDNASettingsHookCommand(value, eventName) {
|
|
136
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
137
|
+
return false;
|
|
138
|
+
const hook = value;
|
|
139
|
+
const expectedCommand = `dna-hook ${eventName}`;
|
|
140
|
+
return hook.type === "command"
|
|
141
|
+
&& typeof hook.command === "string"
|
|
142
|
+
&& hook.command === expectedCommand
|
|
143
|
+
&& INTENTDNA_SETTINGS_HOOK_COMMANDS.has(expectedCommand);
|
|
144
|
+
}
|
|
145
|
+
function isGeneratedIntentDNASettingsHookEntry(entry) {
|
|
146
|
+
return entry.matcher === ""
|
|
147
|
+
&& Object.keys(entry).every((key) => key === "matcher" || key === "hooks");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Remove bin-mode IntentDNA hook commands when Claude plugin hooks own dispatch.
|
|
151
|
+
* Invalid or user-owned settings are preserved without rewriting the file.
|
|
152
|
+
*/
|
|
153
|
+
export async function removeIntentDNASettingsHooks(settingsPath) {
|
|
154
|
+
const unchanged = (warning) => ({
|
|
155
|
+
changed: false,
|
|
156
|
+
removedCommands: 0,
|
|
157
|
+
removedEvents: [],
|
|
158
|
+
removedFile: false,
|
|
159
|
+
...(warning ? { warning } : {}),
|
|
160
|
+
});
|
|
161
|
+
let raw;
|
|
162
|
+
try {
|
|
163
|
+
raw = await readFile(settingsPath, "utf-8");
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
167
|
+
return code === "ENOENT" ? unchanged() : unchanged("could not read settings.json");
|
|
168
|
+
}
|
|
169
|
+
let parsed;
|
|
170
|
+
try {
|
|
171
|
+
parsed = JSON.parse(raw);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return unchanged("settings.json is not valid JSON");
|
|
175
|
+
}
|
|
176
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
177
|
+
return unchanged("settings.json root is not an object");
|
|
178
|
+
}
|
|
179
|
+
const settings = parsed;
|
|
180
|
+
const rawHooks = settings.hooks;
|
|
181
|
+
if (!rawHooks || typeof rawHooks !== "object" || Array.isArray(rawHooks)) {
|
|
182
|
+
return unchanged();
|
|
183
|
+
}
|
|
184
|
+
const cleanedHooks = {};
|
|
185
|
+
const removedEvents = [];
|
|
186
|
+
let removedCommands = 0;
|
|
187
|
+
for (const [eventName, rawEntries] of Object.entries(rawHooks)) {
|
|
188
|
+
if (!Array.isArray(rawEntries)) {
|
|
189
|
+
cleanedHooks[eventName] = rawEntries;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
let eventRemoved = false;
|
|
193
|
+
const cleanedEntries = [];
|
|
194
|
+
for (const rawEntry of rawEntries) {
|
|
195
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
196
|
+
cleanedEntries.push(rawEntry);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const entry = rawEntry;
|
|
200
|
+
if (!Array.isArray(entry.hooks)) {
|
|
201
|
+
cleanedEntries.push(rawEntry);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let entryRemovedCommands = 0;
|
|
205
|
+
const retainedCommands = entry.hooks.filter((hook) => {
|
|
206
|
+
const owned = isIntentDNASettingsHookCommand(hook, eventName);
|
|
207
|
+
if (owned) {
|
|
208
|
+
removedCommands += 1;
|
|
209
|
+
entryRemovedCommands += 1;
|
|
210
|
+
eventRemoved = true;
|
|
211
|
+
}
|
|
212
|
+
return !owned;
|
|
213
|
+
});
|
|
214
|
+
if (entryRemovedCommands === 0) {
|
|
215
|
+
cleanedEntries.push(rawEntry);
|
|
216
|
+
}
|
|
217
|
+
else if (retainedCommands.length > 0 || !isGeneratedIntentDNASettingsHookEntry(entry)) {
|
|
218
|
+
cleanedEntries.push({ ...entry, hooks: retainedCommands });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (eventRemoved)
|
|
222
|
+
removedEvents.push(eventName);
|
|
223
|
+
if (!eventRemoved || cleanedEntries.length > 0)
|
|
224
|
+
cleanedHooks[eventName] = cleanedEntries;
|
|
225
|
+
}
|
|
226
|
+
if (removedCommands === 0)
|
|
227
|
+
return unchanged();
|
|
228
|
+
if (Object.keys(cleanedHooks).length > 0) {
|
|
229
|
+
settings.hooks = cleanedHooks;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
delete settings.hooks;
|
|
233
|
+
}
|
|
234
|
+
if (Object.keys(settings).length === 0) {
|
|
235
|
+
await unlink(settingsPath);
|
|
236
|
+
return {
|
|
237
|
+
changed: true,
|
|
238
|
+
removedCommands,
|
|
239
|
+
removedEvents,
|
|
240
|
+
removedFile: true,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
244
|
+
return {
|
|
245
|
+
changed: true,
|
|
246
|
+
removedCommands,
|
|
247
|
+
removedEvents,
|
|
248
|
+
removedFile: false,
|
|
249
|
+
};
|
|
250
|
+
}
|