@deftai/directive-core 0.84.0 → 0.85.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/dist/cache/fetch.d.ts +17 -0
- package/dist/cache/fetch.js +50 -0
- package/dist/doctor/checks.js +16 -1
- package/dist/doctor/constants.d.ts +2 -0
- package/dist/doctor/constants.js +8 -4
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +13 -4
- package/dist/doctor/npm-registry.d.ts +23 -0
- package/dist/doctor/npm-registry.js +128 -0
- package/dist/doctor/npm-view.d.ts +10 -0
- package/dist/doctor/npm-view.js +31 -0
- package/dist/doctor/payload-staleness.js +2 -9
- package/dist/doctor/signpost-checks.d.ts +2 -1
- package/dist/doctor/signpost-checks.js +2 -0
- package/dist/doctor/types.d.ts +9 -0
- package/dist/eval-health-relocation/evaluate.js +15 -1
- package/dist/fs/projection-containment.js +1 -1
- package/dist/hooks/cursor-hooks.d.ts +6 -20
- package/dist/hooks/cursor-hooks.js +7 -92
- package/dist/init-deposit/agent-hooks.d.ts +2 -2
- package/dist/init-deposit/agent-hooks.js +15 -37
- package/dist/init-deposit/hygiene.d.ts +22 -0
- package/dist/init-deposit/hygiene.js +136 -4
- package/dist/init-deposit/legacy-detect.d.ts +25 -1
- package/dist/init-deposit/legacy-detect.js +143 -11
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +18 -5
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +2 -0
- package/dist/policy/org-force-on-migration.d.ts +44 -0
- package/dist/policy/org-force-on-migration.js +240 -0
- package/dist/policy/product-signal.d.ts +3 -1
- package/dist/policy/product-signal.js +15 -4
- package/dist/policy/value-feedback.d.ts +18 -1
- package/dist/policy/value-feedback.js +78 -5
- package/dist/preflight-cache/evaluate.js +12 -1
- package/dist/product-signal/consent-prompt.d.ts +13 -0
- package/dist/product-signal/consent-prompt.js +30 -0
- package/dist/product-signal/submit.js +22 -1
- package/dist/render/index.d.ts +1 -0
- package/dist/render/index.js +1 -0
- package/dist/render/rule-map-template.d.ts +2 -0
- package/dist/render/rule-map-template.js +407 -0
- package/dist/render/rule-map.d.ts +2 -0
- package/dist/render/rule-map.js +703 -0
- package/dist/review-monitor/constants.d.ts +7 -1
- package/dist/review-monitor/constants.js +40 -14
- package/dist/review-monitor/github-lease.d.ts +45 -0
- package/dist/review-monitor/github-lease.js +106 -0
- package/dist/review-monitor/index.d.ts +2 -0
- package/dist/review-monitor/index.js +2 -0
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +152 -0
- package/dist/review-monitor/record.d.ts +61 -13
- package/dist/review-monitor/record.js +379 -132
- package/dist/review-monitor/verify.d.ts +2 -0
- package/dist/review-monitor/verify.js +32 -13
- package/dist/scm/gh-rest.d.ts +7 -1
- package/dist/scm/gh-rest.js +35 -0
- package/dist/session/session-start.js +5 -0
- package/dist/task-surface/index.js +53 -13
- package/dist/xbrief-migrate/constants.d.ts +6 -0
- package/dist/xbrief-migrate/constants.js +6 -0
- package/dist/xbrief-migrate/index.d.ts +2 -2
- package/dist/xbrief-migrate/index.js +2 -2
- package/dist/xbrief-migrate/migrate-project.d.ts +7 -0
- package/dist/xbrief-migrate/migrate-project.js +18 -1
- package/package.json +3 -3
|
@@ -1,95 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/** Cursor ApplyPatch
|
|
1
|
+
import { DIRECT_WRITE_HOOK_MATCHER } from "./tools.js";
|
|
2
|
+
/** Cursor's ApplyPatch payload is parsed directly by the shared hook executable. */
|
|
3
3
|
export const APPLY_PATCH_TOOL_NAMES = ["ApplyPatch", "apply_patch"];
|
|
4
4
|
export const APPLY_PATCH_HOOK_MATCHER = APPLY_PATCH_TOOL_NAMES.join("|");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export const
|
|
10
|
-
export const DEFT_CURSOR_ADAPTER_COMMAND_MARKER = "deft-cursor-hook-adapter.mjs";
|
|
11
|
-
export const CURSOR_APPLY_PATCH_ADAPTER_COMMAND = `node ${CURSOR_APPLY_PATCH_ADAPTER_RELATIVE} ApplyPatch`;
|
|
12
|
-
/** Deposited adapter forwards free-form ApplyPatch stdin to hook:dispatch with explicit project root. */
|
|
13
|
-
export const CURSOR_APPLY_PATCH_ADAPTER_SOURCE = `#!/usr/bin/env node
|
|
14
|
-
import { spawnSync } from "node:child_process";
|
|
15
|
-
import { readFileSync } from "node:fs";
|
|
16
|
-
import { resolve } from "node:path";
|
|
17
|
-
|
|
18
|
-
const projectRoot = resolve(process.cwd());
|
|
19
|
-
const stdin = readFileSync(0, "utf8");
|
|
20
|
-
|
|
21
|
-
function deftCommand() {
|
|
22
|
-
for (const candidate of ["deft", "directive"]) {
|
|
23
|
-
const probe = spawnSync(candidate, ["--version"], {
|
|
24
|
-
encoding: "utf8",
|
|
25
|
-
stdio: "ignore",
|
|
26
|
-
shell: process.platform === "win32",
|
|
27
|
-
windowsHide: true,
|
|
28
|
-
});
|
|
29
|
-
if (probe.error === undefined && probe.status === 0) return candidate;
|
|
30
|
-
}
|
|
31
|
-
process.stderr.write(
|
|
32
|
-
"Directive ApplyPatch adapter: neither deft nor directive is on PATH.\\n",
|
|
33
|
-
);
|
|
34
|
-
process.exit(2);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const cli = deftCommand();
|
|
38
|
-
const result = spawnSync(
|
|
39
|
-
cli,
|
|
40
|
-
[
|
|
41
|
-
"hook:dispatch",
|
|
42
|
-
"--host",
|
|
43
|
-
"cursor",
|
|
44
|
-
"--event",
|
|
45
|
-
"tool.before",
|
|
46
|
-
"--project-root",
|
|
47
|
-
projectRoot,
|
|
48
|
-
],
|
|
49
|
-
{
|
|
50
|
-
input: stdin,
|
|
51
|
-
encoding: "utf8",
|
|
52
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
53
|
-
shell: process.platform === "win32",
|
|
54
|
-
windowsHide: true,
|
|
55
|
-
},
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
59
|
-
if (result.error) {
|
|
60
|
-
process.stderr.write(String(result.error));
|
|
61
|
-
process.exit(2);
|
|
62
|
-
}
|
|
63
|
-
if (result.status !== 0 && result.status !== null) {
|
|
64
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
65
|
-
process.exit(result.status);
|
|
66
|
-
}
|
|
67
|
-
// Cursor failClosed treats empty stdout as failure — normalize allow.
|
|
68
|
-
const out = (result.stdout ?? "").trim();
|
|
69
|
-
process.stdout.write((out.length > 0 ? out : '{"permission":"allow"}') + "\\n");
|
|
70
|
-
process.exit(0);
|
|
71
|
-
`;
|
|
72
|
-
export function cursorApplyPatchAdapterEntry() {
|
|
73
|
-
return {
|
|
74
|
-
command: CURSOR_APPLY_PATCH_ADAPTER_COMMAND,
|
|
75
|
-
matcher: APPLY_PATCH_HOOK_MATCHER,
|
|
76
|
-
failClosed: true,
|
|
77
|
-
timeout: 5,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
/** True when generic and adapter matchers share no tool tokens. */
|
|
81
|
-
export function cursorApplyPatchMatchersDisjoint() {
|
|
82
|
-
const generic = new Set(CURSOR_GENERIC_WRITE_HOOK_MATCHER.split("|"));
|
|
83
|
-
for (const token of APPLY_PATCH_HOOK_MATCHER.split("|")) {
|
|
84
|
-
if (generic.has(token))
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
/** Fail closed when Cursor hook projection would double-dispatch ApplyPatch (#2764). */
|
|
90
|
-
export function assertCursorApplyPatchMatchersDisjoint() {
|
|
91
|
-
if (!cursorApplyPatchMatchersDisjoint()) {
|
|
92
|
-
throw new Error("Cursor ApplyPatch and generic direct-write matchers overlap — refusing hook deposit (#2764).");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Cursor uses one disjoint direct-write registration: hook-dispatch recognizes
|
|
7
|
+
* JSON and free-form ApplyPatch payloads, avoiding an adapter process (#2790).
|
|
8
|
+
*/
|
|
9
|
+
export const CURSOR_DIRECT_WRITE_HOOK_MATCHER = DIRECT_WRITE_HOOK_MATCHER;
|
|
95
10
|
//# sourceMappingURL=cursor-hooks.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { HookHost } from "../hooks/dispatcher.js";
|
|
2
2
|
import { type HostHooksPolicy } from "../policy/host-hooks.js";
|
|
3
3
|
import type { InitDepositIo } from "./constants.js";
|
|
4
|
-
export { CURSOR_GENERIC_WRITE_HOOK_MATCHER, DEFT_CURSOR_ADAPTER_COMMAND_MARKER, } from "../hooks/cursor-hooks.js";
|
|
5
4
|
export { DIRECT_WRITE_HOOK_MATCHER, SPAWN_HOOK_MATCHER } from "../hooks/tools.js";
|
|
6
|
-
export declare const DEFT_HOOK_COMMAND_MARKER = "deft
|
|
5
|
+
export declare const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
|
|
6
|
+
export declare const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
|
|
7
7
|
export declare const AGENT_HOOK_PATHS: readonly [".claude/settings.json", ".grok/hooks/deft.json", ".cursor/hooks.json", ".codex/hooks.json"];
|
|
8
8
|
export type AgentHookPath = (typeof AGENT_HOOK_PATHS)[number];
|
|
9
9
|
export type AgentHookRegistrationStatus = "healthy" | "missing" | "drifted";
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { assertDepositContained } from "../deposit/contain.js";
|
|
4
|
-
import { assertCursorApplyPatchMatchersDisjoint, CURSOR_APPLY_PATCH_ADAPTER_RELATIVE, CURSOR_APPLY_PATCH_ADAPTER_SOURCE, CURSOR_GENERIC_WRITE_HOOK_MATCHER, cursorApplyPatchAdapterEntry, DEFT_CURSOR_ADAPTER_COMMAND_MARKER, } from "../hooks/cursor-hooks.js";
|
|
5
4
|
import { DIRECT_WRITE_HOOK_MATCHER, SPAWN_HOOK_MATCHER } from "../hooks/tools.js";
|
|
6
5
|
import { isHostHookDepositEnabled, loadHostHooksPolicyFromProject, } from "../policy/host-hooks.js";
|
|
7
|
-
export { CURSOR_GENERIC_WRITE_HOOK_MATCHER, DEFT_CURSOR_ADAPTER_COMMAND_MARKER, } from "../hooks/cursor-hooks.js";
|
|
8
6
|
export { DIRECT_WRITE_HOOK_MATCHER, SPAWN_HOOK_MATCHER } from "../hooks/tools.js";
|
|
9
|
-
export const DEFT_HOOK_COMMAND_MARKER = "deft
|
|
7
|
+
export const DEFT_HOOK_COMMAND_MARKER = "deft-hook";
|
|
8
|
+
export const LEGACY_DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
|
|
10
9
|
export const AGENT_HOOK_PATHS = [
|
|
11
10
|
".claude/settings.json",
|
|
12
11
|
".grok/hooks/deft.json",
|
|
@@ -64,17 +63,21 @@ function nestedCommands(value) {
|
|
|
64
63
|
});
|
|
65
64
|
}
|
|
66
65
|
function isManagedNestedGroup(value) {
|
|
67
|
-
return nestedCommands(value).some((
|
|
66
|
+
return nestedCommands(value).some((command) => command.includes(DEFT_HOOK_COMMAND_MARKER) ||
|
|
67
|
+
command.includes(LEGACY_DEFT_HOOK_COMMAND_MARKER));
|
|
68
68
|
}
|
|
69
69
|
function isManagedNestedGroupForHost(value, host) {
|
|
70
|
-
return nestedCommands(value).some((command) => command.includes(DEFT_HOOK_COMMAND_MARKER)
|
|
70
|
+
return nestedCommands(value).some((command) => (command.includes(DEFT_HOOK_COMMAND_MARKER) ||
|
|
71
|
+
command.includes(LEGACY_DEFT_HOOK_COMMAND_MARKER)) &&
|
|
72
|
+
command.includes(`--host ${host}`));
|
|
71
73
|
}
|
|
72
74
|
function isManagedCursorEntry(value) {
|
|
73
75
|
const entry = object(value);
|
|
74
76
|
if (typeof entry?.command !== "string")
|
|
75
77
|
return false;
|
|
76
78
|
return (entry.command.includes(DEFT_HOOK_COMMAND_MARKER) ||
|
|
77
|
-
entry.command.includes(
|
|
79
|
+
entry.command.includes(LEGACY_DEFT_HOOK_COMMAND_MARKER) ||
|
|
80
|
+
entry.command.includes("deft-cursor-hook-adapter.mjs"));
|
|
78
81
|
}
|
|
79
82
|
function isManagedCursorEntryForHost(value) {
|
|
80
83
|
return isManagedCursorEntry(value);
|
|
@@ -142,7 +145,6 @@ function stripManagedCursorConfig(config, path) {
|
|
|
142
145
|
return { ...config, version: 1, hooks: nextHooks };
|
|
143
146
|
}
|
|
144
147
|
function mergeCursorConfig(config, path) {
|
|
145
|
-
assertCursorApplyPatchMatchersDisjoint();
|
|
146
148
|
const hooks = hooksObject(config, path);
|
|
147
149
|
const session = eventArray(hooks, "sessionStart", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
148
150
|
const preTool = eventArray(hooks, "preToolUse", path).filter((entry) => !isManagedCursorEntry(entry));
|
|
@@ -150,10 +152,9 @@ function mergeCursorConfig(config, path) {
|
|
|
150
152
|
hooks.sessionStart = [...session, { command: command("cursor", "session.start"), timeout: 5 }];
|
|
151
153
|
hooks.preToolUse = [
|
|
152
154
|
...preTool,
|
|
153
|
-
cursorApplyPatchAdapterEntry(),
|
|
154
155
|
{
|
|
155
156
|
command: command("cursor", "tool.before"),
|
|
156
|
-
matcher:
|
|
157
|
+
matcher: DIRECT_WRITE_HOOK_MATCHER,
|
|
157
158
|
failClosed: true,
|
|
158
159
|
timeout: 5,
|
|
159
160
|
},
|
|
@@ -167,15 +168,6 @@ function mergeCursorConfig(config, path) {
|
|
|
167
168
|
hooks.preCompact = [...preCompact, { command: command("cursor", "session.compact"), timeout: 5 }];
|
|
168
169
|
return { ...config, version: 1, hooks };
|
|
169
170
|
}
|
|
170
|
-
function writeTextIfChanged(path, contents) {
|
|
171
|
-
if (existsSync(path) && readFileSync(path, "utf8") === contents)
|
|
172
|
-
return false;
|
|
173
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
174
|
-
const temporary = `${path}.deft-${process.pid}.tmp`;
|
|
175
|
-
writeFileSync(temporary, contents, "utf8");
|
|
176
|
-
renameSync(temporary, path);
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
171
|
function writeJsonIfChanged(path, payload) {
|
|
180
172
|
const next = `${JSON.stringify(payload, null, 2)}\n`;
|
|
181
173
|
if (existsSync(path) && readFileSync(path, "utf8") === next)
|
|
@@ -246,19 +238,12 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
|
|
|
246
238
|
changedPaths.push(item.path);
|
|
247
239
|
}
|
|
248
240
|
}
|
|
249
|
-
const adapterAbsolute = join(projectRoot,
|
|
241
|
+
const adapterAbsolute = join(projectRoot, ".cursor/hooks/deft-cursor-hook-adapter.mjs");
|
|
250
242
|
assertDepositContained(projectRoot, adapterAbsolute);
|
|
251
|
-
if (
|
|
252
|
-
if (writeTextIfChanged(adapterAbsolute, CURSOR_APPLY_PATCH_ADAPTER_SOURCE)) {
|
|
253
|
-
if (!changedPaths.includes(AGENT_HOOK_PATHS[2])) {
|
|
254
|
-
changedPaths.push(AGENT_HOOK_PATHS[2]);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
else if (existsSync(adapterAbsolute)) {
|
|
243
|
+
if (existsSync(adapterAbsolute)) {
|
|
259
244
|
rmSync(adapterAbsolute, { force: true });
|
|
260
|
-
if (!
|
|
261
|
-
|
|
245
|
+
if (!changedPaths.includes(AGENT_HOOK_PATHS[2])) {
|
|
246
|
+
changedPaths.push(AGENT_HOOK_PATHS[2]);
|
|
262
247
|
}
|
|
263
248
|
}
|
|
264
249
|
if (changedPaths.length > 0) {
|
|
@@ -309,18 +294,11 @@ function hasCursorRegistration(config) {
|
|
|
309
294
|
const session = Array.isArray(hooks.sessionStart) ? hooks.sessionStart : [];
|
|
310
295
|
const preTool = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
|
|
311
296
|
const preCompact = Array.isArray(hooks.preCompact) ? hooks.preCompact : [];
|
|
312
|
-
const adapter = cursorApplyPatchAdapterEntry();
|
|
313
297
|
return (session.some((entry) => object(entry)?.command === command("cursor", "session.start")) &&
|
|
314
298
|
preTool.some((entry) => {
|
|
315
299
|
const hook = object(entry);
|
|
316
300
|
return (hook?.command === command("cursor", "tool.before") &&
|
|
317
|
-
hook.matcher ===
|
|
318
|
-
hook.failClosed === true);
|
|
319
|
-
}) &&
|
|
320
|
-
preTool.some((entry) => {
|
|
321
|
-
const hook = object(entry);
|
|
322
|
-
return (hook?.command === adapter.command &&
|
|
323
|
-
hook.matcher === adapter.matcher &&
|
|
301
|
+
hook.matcher === DIRECT_WRITE_HOOK_MATCHER &&
|
|
324
302
|
hook.failClosed === true);
|
|
325
303
|
}) &&
|
|
326
304
|
preTool.some((entry) => {
|
|
@@ -57,6 +57,28 @@ export declare function stageFrameworkPaths(projectDir: string, paths: readonly
|
|
|
57
57
|
* Refs #2347.
|
|
58
58
|
*/
|
|
59
59
|
export declare const STRAY_DEPOSIT_FRAMEWORK_PATHS: readonly ["packages"];
|
|
60
|
+
/**
|
|
61
|
+
* Deposit-local files generated by init/update that are intentionally absent
|
|
62
|
+
* from `@deftai/directive-content` (#2804).
|
|
63
|
+
*/
|
|
64
|
+
export declare const DEPOSIT_GENERATED_METADATA_PATHS: readonly ["VERSION"];
|
|
65
|
+
export declare function isDepositGeneratedMetadata(relPath: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* List deposit-relative paths that are absent from the installed content package,
|
|
68
|
+
* excluding generated deposit metadata such as `VERSION` (#2804).
|
|
69
|
+
*/
|
|
70
|
+
export declare function findPackageAbsentDepositPathsSync(deftDir: string, contentRoot: string): string[];
|
|
71
|
+
export declare function findPackageAbsentDepositPaths(deftDir: string, contentRoot: string): Promise<string[]>;
|
|
72
|
+
export interface PrunePackageAbsentDepositPathsResult {
|
|
73
|
+
readonly pruned: string[];
|
|
74
|
+
readonly prunedDirs: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Remove deposit files not shipped by `@deftai/directive-content`, preserving
|
|
78
|
+
* generated deposit metadata such as `VERSION` (#2804). Subsumes the legacy
|
|
79
|
+
* hard-coded `packages/` prune (#2347).
|
|
80
|
+
*/
|
|
81
|
+
export declare function prunePackageAbsentDepositPaths(deftDir: string, contentRoot: string, io: InitDepositIo): Promise<PrunePackageAbsentDepositPathsResult>;
|
|
60
82
|
export interface PruneStrayDepositPathsResult {
|
|
61
83
|
readonly pruned: string[];
|
|
62
84
|
}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Refs #1576, #1453, #1430.
|
|
6
6
|
*/
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
import { rm, stat } from "node:fs/promises";
|
|
10
|
-
import { join, relative } from "node:path";
|
|
8
|
+
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
9
|
+
import { readdir, rm, stat } from "node:fs/promises";
|
|
10
|
+
import { dirname, join, relative } from "node:path";
|
|
11
11
|
import { gitPorcelain } from "../story-ready/git.js";
|
|
12
12
|
import { CANONICAL_INSTALL_ROOT } from "./constants.js";
|
|
13
13
|
export const CODEQL_CONFIG_REL = ".github/codeql/codeql-config.yml";
|
|
@@ -24,7 +24,6 @@ export function installerManagedMatchers() {
|
|
|
24
24
|
{ exact: ".claude/settings.json" },
|
|
25
25
|
{ exact: ".grok/hooks/deft.json" },
|
|
26
26
|
{ exact: ".cursor/hooks.json" },
|
|
27
|
-
{ exact: ".cursor/hooks/deft-cursor-hook-adapter.mjs" },
|
|
28
27
|
{ exact: ".codex/hooks.json" },
|
|
29
28
|
{ exact: ".gitattributes" },
|
|
30
29
|
{ exact: ".gitignore" },
|
|
@@ -146,6 +145,139 @@ export function stageFrameworkPaths(projectDir, paths, seams = {}) {
|
|
|
146
145
|
* Refs #2347.
|
|
147
146
|
*/
|
|
148
147
|
export const STRAY_DEPOSIT_FRAMEWORK_PATHS = ["packages"];
|
|
148
|
+
/**
|
|
149
|
+
* Deposit-local files generated by init/update that are intentionally absent
|
|
150
|
+
* from `@deftai/directive-content` (#2804).
|
|
151
|
+
*/
|
|
152
|
+
export const DEPOSIT_GENERATED_METADATA_PATHS = ["VERSION"];
|
|
153
|
+
export function isDepositGeneratedMetadata(relPath) {
|
|
154
|
+
const normalized = relPath.replace(/\\/g, "/");
|
|
155
|
+
return DEPOSIT_GENERATED_METADATA_PATHS.includes(normalized);
|
|
156
|
+
}
|
|
157
|
+
function normalizeRelativePath(relPath) {
|
|
158
|
+
return relPath.replace(/\\/g, "/");
|
|
159
|
+
}
|
|
160
|
+
function listRelativeFilePathsSync(root) {
|
|
161
|
+
if (!existsSync(root))
|
|
162
|
+
return [];
|
|
163
|
+
const results = [];
|
|
164
|
+
const walk = (dir, prefix) => {
|
|
165
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
166
|
+
const rel = normalizeRelativePath(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
167
|
+
const abs = join(dir, entry.name);
|
|
168
|
+
if (entry.isSymbolicLink())
|
|
169
|
+
continue;
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
walk(abs, rel);
|
|
172
|
+
}
|
|
173
|
+
else if (entry.isFile()) {
|
|
174
|
+
results.push(rel);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
walk(root, "");
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
async function listRelativeFilePaths(root) {
|
|
182
|
+
if (!existsSync(root))
|
|
183
|
+
return [];
|
|
184
|
+
const results = [];
|
|
185
|
+
const walk = async (dir, prefix) => {
|
|
186
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
187
|
+
for (const entry of entries) {
|
|
188
|
+
const rel = normalizeRelativePath(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
189
|
+
const abs = join(dir, entry.name);
|
|
190
|
+
if (entry.isSymbolicLink())
|
|
191
|
+
continue;
|
|
192
|
+
if (entry.isDirectory()) {
|
|
193
|
+
await walk(abs, rel);
|
|
194
|
+
}
|
|
195
|
+
else if (entry.isFile()) {
|
|
196
|
+
results.push(rel);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
await walk(root, "");
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
function contentDirectoryPaths(contentFiles) {
|
|
204
|
+
const dirs = new Set();
|
|
205
|
+
for (const file of contentFiles) {
|
|
206
|
+
let dir = dirname(file);
|
|
207
|
+
while (dir && dir !== ".") {
|
|
208
|
+
dirs.add(normalizeRelativePath(dir));
|
|
209
|
+
const parent = dirname(dir);
|
|
210
|
+
if (parent === dir)
|
|
211
|
+
break;
|
|
212
|
+
dir = parent;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return dirs;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* List deposit-relative paths that are absent from the installed content package,
|
|
219
|
+
* excluding generated deposit metadata such as `VERSION` (#2804).
|
|
220
|
+
*/
|
|
221
|
+
export function findPackageAbsentDepositPathsSync(deftDir, contentRoot) {
|
|
222
|
+
const depositFiles = listRelativeFilePathsSync(deftDir);
|
|
223
|
+
const contentFiles = new Set(listRelativeFilePathsSync(contentRoot));
|
|
224
|
+
return depositFiles
|
|
225
|
+
.filter((rel) => !contentFiles.has(rel) && !isDepositGeneratedMetadata(rel))
|
|
226
|
+
.sort();
|
|
227
|
+
}
|
|
228
|
+
export async function findPackageAbsentDepositPaths(deftDir, contentRoot) {
|
|
229
|
+
const depositFiles = await listRelativeFilePaths(deftDir);
|
|
230
|
+
const contentFiles = new Set(await listRelativeFilePaths(contentRoot));
|
|
231
|
+
return depositFiles
|
|
232
|
+
.filter((rel) => !contentFiles.has(rel) && !isDepositGeneratedMetadata(rel))
|
|
233
|
+
.sort();
|
|
234
|
+
}
|
|
235
|
+
async function pruneEmptyParentsForFile(deftDir, contentDirs, relFile) {
|
|
236
|
+
const prunedDirs = [];
|
|
237
|
+
let rel = dirname(relFile);
|
|
238
|
+
while (rel && rel !== ".") {
|
|
239
|
+
const normalized = normalizeRelativePath(rel);
|
|
240
|
+
if (contentDirs.has(normalized))
|
|
241
|
+
break;
|
|
242
|
+
const abs = join(deftDir, rel);
|
|
243
|
+
try {
|
|
244
|
+
if (!existsSync(abs) || readdirSync(abs).length !== 0)
|
|
245
|
+
break;
|
|
246
|
+
rmSync(abs, { recursive: false, force: true });
|
|
247
|
+
prunedDirs.push(normalized);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
rel = dirname(rel);
|
|
253
|
+
}
|
|
254
|
+
return prunedDirs;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Remove deposit files not shipped by `@deftai/directive-content`, preserving
|
|
258
|
+
* generated deposit metadata such as `VERSION` (#2804). Subsumes the legacy
|
|
259
|
+
* hard-coded `packages/` prune (#2347).
|
|
260
|
+
*/
|
|
261
|
+
export async function prunePackageAbsentDepositPaths(deftDir, contentRoot, io) {
|
|
262
|
+
const absent = await findPackageAbsentDepositPaths(deftDir, contentRoot);
|
|
263
|
+
const contentDirs = contentDirectoryPaths(listRelativeFilePathsSync(contentRoot));
|
|
264
|
+
const pruned = [];
|
|
265
|
+
const prunedDirs = [];
|
|
266
|
+
for (const rel of absent) {
|
|
267
|
+
try {
|
|
268
|
+
rmSync(join(deftDir, rel), { force: true });
|
|
269
|
+
pruned.push(rel);
|
|
270
|
+
prunedDirs.push(...(await pruneEmptyParentsForFile(deftDir, contentDirs, rel)));
|
|
271
|
+
}
|
|
272
|
+
catch (cause) {
|
|
273
|
+
io.printf(`Warning: could not prune .deft/core/${rel}: ${String(cause)}\n`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (pruned.length > 0) {
|
|
277
|
+
io.printf(`Pruned ${pruned.length} package-absent deposit file(s) not shipped by @deftai/directive-content (#2804).\n`);
|
|
278
|
+
}
|
|
279
|
+
return { pruned, prunedDirs };
|
|
280
|
+
}
|
|
149
281
|
/**
|
|
150
282
|
* Remove framework-source subdirectories from `.deft/core/` that are not
|
|
151
283
|
* present in the deposited content package (#2347). Silently skips any path
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
/** Non-zero exit code for a use-time legacy-layout refusal (needs-action). */
|
|
27
27
|
export declare const LEGACY_LAYOUT_REFUSED_EXIT_CODE = 2;
|
|
28
|
-
export type LegacyLayoutKind = "orphan-deft-version" | "legacy-deft-prefixed" | "git-clone-or-submodule" | "pre-v0.27-sentinel-agents-md";
|
|
28
|
+
export type LegacyLayoutKind = "orphan-deft-version" | "legacy-deft-prefixed" | "git-clone-or-submodule" | "pre-v0.27-sentinel-agents-md" | "dual-layout";
|
|
29
29
|
export interface LegacyLayoutDetection {
|
|
30
30
|
readonly legacy: boolean;
|
|
31
31
|
readonly kind: LegacyLayoutKind | null;
|
|
@@ -37,6 +37,12 @@ export interface LegacyDetectSeams {
|
|
|
37
37
|
readonly isDir?: (p: string) => boolean;
|
|
38
38
|
readonly readText?: (p: string) => string | null;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Detect a post-bridge dual-layout: canonical `.deft/core/` plus a leftover
|
|
42
|
+
* framework-marked `deft/` tree. Doctor-only -- the npm deposit path treats
|
|
43
|
+
* `.deft/core/` as canonical and must not refuse here (#2805 / #2804 wiring).
|
|
44
|
+
*/
|
|
45
|
+
export declare function detectDualLayout(projectDir: string, seams?: LegacyDetectSeams): LegacyLayoutDetection | null;
|
|
40
46
|
/**
|
|
41
47
|
* Classify the on-disk layout at `projectDir`. Returns `legacy: false` for the
|
|
42
48
|
* canonical `.deft/core/` vendored layout and for a greenfield project (so init
|
|
@@ -58,4 +64,22 @@ export declare function buildLegacyRefusalMessage(command: "init" | "update", de
|
|
|
58
64
|
export declare function buildLegacyRefusalJson(command: "init" | "update", projectDir: string, detection: LegacyLayoutDetection): Record<string, unknown>;
|
|
59
65
|
/** One-line doctor signpost carrying the stable URL (no baked version/command). */
|
|
60
66
|
export declare function legacyLayoutSignpostLine(detection: LegacyLayoutDetection): string;
|
|
67
|
+
/** Doctor / npm signpost for the post-bridge dual-layout state (#2805). */
|
|
68
|
+
export declare function dualLayoutSignpostLine(detection: LegacyLayoutDetection): string;
|
|
69
|
+
export interface LegacyDeftCleanupSeams {
|
|
70
|
+
readonly gitPorcelain?: (projectRoot: string) => string | null;
|
|
71
|
+
readonly runGitRm?: (projectDir: string, relPath: string) => void;
|
|
72
|
+
readonly removeDir?: (absPath: string) => void;
|
|
73
|
+
}
|
|
74
|
+
export type LegacyDeftCleanupAction = "removed" | "staged" | "refused" | "skipped";
|
|
75
|
+
export interface LegacyDeftCleanupResult {
|
|
76
|
+
readonly ok: boolean;
|
|
77
|
+
readonly action: LegacyDeftCleanupAction;
|
|
78
|
+
readonly detail: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Remove a clean tracked legacy `deft/` tree after bridge migration (#2805).
|
|
82
|
+
* Refuses when the tree has local modifications or untracked files.
|
|
83
|
+
*/
|
|
84
|
+
export declare function tryCleanupLegacyDeftTree(projectDir: string, seams?: LegacyDeftCleanupSeams): LegacyDeftCleanupResult;
|
|
61
85
|
//# sourceMappingURL=legacy-detect.d.ts.map
|
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
*
|
|
24
24
|
* Refs #1912, #1669.
|
|
25
25
|
*/
|
|
26
|
-
import {
|
|
26
|
+
import { execFileSync } from "node:child_process";
|
|
27
|
+
import { rmSync, statSync } from "node:fs";
|
|
27
28
|
import { join } from "node:path";
|
|
28
29
|
import { GO_BRIDGE_RELEASES_URL, UPGRADING_DOC_URL } from "../doctor/constants.js";
|
|
29
30
|
import { extractManagedSection, parseInstallRootFromAgentsMd } from "../doctor/manifest.js";
|
|
30
31
|
import { readTextSafe } from "../doctor/paths.js";
|
|
32
|
+
import { gitPorcelain } from "../story-ready/git.js";
|
|
31
33
|
/** Non-zero exit code for a use-time legacy-layout refusal (needs-action). */
|
|
32
34
|
export const LEGACY_LAYOUT_REFUSED_EXIT_CODE = 2;
|
|
33
35
|
const NOT_LEGACY = {
|
|
@@ -53,6 +55,16 @@ function defaultIsDir(p) {
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
/** Does `.gitmodules` reference the deft framework at a deft install path? */
|
|
58
|
+
const LEGACY_DEFT_DIR = "deft";
|
|
59
|
+
function isFrameworkDeftDir(projectDir, isDir, isFile) {
|
|
60
|
+
const deftDir = join(projectDir, LEGACY_DEFT_DIR);
|
|
61
|
+
const deftMarkers = [
|
|
62
|
+
join(deftDir, "VERSION"),
|
|
63
|
+
join(deftDir, "main.md"),
|
|
64
|
+
join(deftDir, "Taskfile.yml"),
|
|
65
|
+
];
|
|
66
|
+
return isDir(deftDir) && (deftMarkers.some((m) => isFile(m)) || isDir(join(deftDir, "skills")));
|
|
67
|
+
}
|
|
56
68
|
function gitmodulesReferencesFramework(text) {
|
|
57
69
|
const normalized = text.replace(/\r\n/g, "\n").toLowerCase();
|
|
58
70
|
if (normalized.includes("deftai/directive")) {
|
|
@@ -62,6 +74,27 @@ function gitmodulesReferencesFramework(text) {
|
|
|
62
74
|
// when the url host differs (mirror, fork). Match `path = deft` / `path = .deft`.
|
|
63
75
|
return /(^|\n)\s*path\s*=\s*\.?deft(\/|\s|$)/.test(normalized);
|
|
64
76
|
}
|
|
77
|
+
const DUAL_LAYOUT_DETAIL = "Found both the canonical .deft/core/ deposit and a leftover legacy deft/ " +
|
|
78
|
+
"framework tree -- remove deft/ so .deft/core/ is the sole framework root.";
|
|
79
|
+
/**
|
|
80
|
+
* Detect a post-bridge dual-layout: canonical `.deft/core/` plus a leftover
|
|
81
|
+
* framework-marked `deft/` tree. Doctor-only -- the npm deposit path treats
|
|
82
|
+
* `.deft/core/` as canonical and must not refuse here (#2805 / #2804 wiring).
|
|
83
|
+
*/
|
|
84
|
+
export function detectDualLayout(projectDir, seams = {}) {
|
|
85
|
+
const isFile = seams.isFile ?? defaultIsFile;
|
|
86
|
+
const isDir = seams.isDir ?? defaultIsDir;
|
|
87
|
+
const hasCanonicalCore = isDir(join(projectDir, ".deft", "core"));
|
|
88
|
+
if (!hasCanonicalCore || !isFrameworkDeftDir(projectDir, isDir, isFile)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
legacy: true,
|
|
93
|
+
kind: "dual-layout",
|
|
94
|
+
detail: DUAL_LAYOUT_DETAIL,
|
|
95
|
+
evidence: [".deft/core/", "deft/"],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
65
98
|
/**
|
|
66
99
|
* Classify the on-disk layout at `projectDir`. Returns `legacy: false` for the
|
|
67
100
|
* canonical `.deft/core/` vendored layout and for a greenfield project (so init
|
|
@@ -71,10 +104,11 @@ export function detectLegacyLayout(projectDir, seams = {}) {
|
|
|
71
104
|
const isFile = seams.isFile ?? defaultIsFile;
|
|
72
105
|
const isDir = seams.isDir ?? defaultIsDir;
|
|
73
106
|
const readText = seams.readText ?? readTextSafe;
|
|
107
|
+
const hasCanonicalCore = isDir(join(projectDir, ".deft", "core"));
|
|
108
|
+
const deftDirIsFramework = isFrameworkDeftDir(projectDir, isDir, isFile);
|
|
74
109
|
// Canonical vendored layout present -> the npm path owns it; not legacy. Any
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
if (isDir(join(projectDir, ".deft", "core"))) {
|
|
110
|
+
// leftover deft/ tree is a dual-layout hygiene issue surfaced by Doctor only.
|
|
111
|
+
if (hasCanonicalCore) {
|
|
78
112
|
return NOT_LEGACY;
|
|
79
113
|
}
|
|
80
114
|
// Orphan `.deft/VERSION` (the old pre-`.deft/core/` manifest location) with no
|
|
@@ -92,13 +126,7 @@ export function detectLegacyLayout(projectDir, seams = {}) {
|
|
|
92
126
|
// framework markers. Distinguish a git-clone / submodule deposit (a `.git`
|
|
93
127
|
// inside `deft/`, or a `.gitmodules` pointing at the framework) from a plain
|
|
94
128
|
// legacy-prefixed vendored copy.
|
|
95
|
-
const deftDir = join(projectDir,
|
|
96
|
-
const deftMarkers = [
|
|
97
|
-
join(deftDir, "VERSION"),
|
|
98
|
-
join(deftDir, "main.md"),
|
|
99
|
-
join(deftDir, "Taskfile.yml"),
|
|
100
|
-
];
|
|
101
|
-
const deftDirIsFramework = isDir(deftDir) && (deftMarkers.some((m) => isFile(m)) || isDir(join(deftDir, "skills")));
|
|
129
|
+
const deftDir = join(projectDir, LEGACY_DEFT_DIR);
|
|
102
130
|
if (deftDirIsFramework) {
|
|
103
131
|
const submoduleGit = isFile(join(deftDir, ".git")) || isDir(join(deftDir, ".git"));
|
|
104
132
|
if (submoduleGit) {
|
|
@@ -215,9 +243,113 @@ export function buildLegacyRefusalJson(command, projectDir, detection) {
|
|
|
215
243
|
}
|
|
216
244
|
/** One-line doctor signpost carrying the stable URL (no baked version/command). */
|
|
217
245
|
export function legacyLayoutSignpostLine(detection) {
|
|
246
|
+
if (detection.kind === "dual-layout") {
|
|
247
|
+
return dualLayoutSignpostLine(detection);
|
|
248
|
+
}
|
|
218
249
|
return (`Legacy Deft layout detected (${detection.kind ?? "unknown"}): ${detection.detail} ` +
|
|
219
250
|
"Run the frozen Go bridge installer to migrate to .deft/core/, then use the npm " +
|
|
220
251
|
`CLI (\`npx @deftai/directive update\`). See ${UPGRADING_DOC_URL} ` +
|
|
221
252
|
`(frozen bridge: ${GO_BRIDGE_RELEASES_URL}).`);
|
|
222
253
|
}
|
|
254
|
+
/** Doctor / npm signpost for the post-bridge dual-layout state (#2805). */
|
|
255
|
+
export function dualLayoutSignpostLine(detection) {
|
|
256
|
+
return (`Dual Deft layout detected (${detection.kind ?? "dual-layout"}): ${detection.detail} ` +
|
|
257
|
+
"Remove the legacy deft/ tree so .deft/core/ is the sole framework root " +
|
|
258
|
+
"(when clean: `git rm -r deft/`; reconcile local edits first if `git status deft/` is dirty). " +
|
|
259
|
+
`See ${UPGRADING_DOC_URL}.`);
|
|
260
|
+
}
|
|
261
|
+
function porcelainEntryPath(line) {
|
|
262
|
+
let path = line.slice(3).trim().replace(/\\/g, "/");
|
|
263
|
+
if (path.startsWith('"') && path.endsWith('"')) {
|
|
264
|
+
path = path.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
265
|
+
}
|
|
266
|
+
return path;
|
|
267
|
+
}
|
|
268
|
+
function porcelainTouchesLegacyDeft(porcelain) {
|
|
269
|
+
const touched = [];
|
|
270
|
+
for (const line of porcelain.split("\n")) {
|
|
271
|
+
if (!line.trim())
|
|
272
|
+
continue;
|
|
273
|
+
const path = porcelainEntryPath(line);
|
|
274
|
+
if (path === LEGACY_DEFT_DIR || path.startsWith(`${LEGACY_DEFT_DIR}/`)) {
|
|
275
|
+
touched.push(path);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return touched;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Remove a clean tracked legacy `deft/` tree after bridge migration (#2805).
|
|
282
|
+
* Refuses when the tree has local modifications or untracked files.
|
|
283
|
+
*/
|
|
284
|
+
export function tryCleanupLegacyDeftTree(projectDir, seams = {}) {
|
|
285
|
+
const detection = detectDualLayout(projectDir);
|
|
286
|
+
if (detection === null) {
|
|
287
|
+
return {
|
|
288
|
+
ok: true,
|
|
289
|
+
action: "skipped",
|
|
290
|
+
detail: "No dual-layout legacy deft/ tree to remove.",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const readPorcelain = seams.gitPorcelain ?? gitPorcelain;
|
|
294
|
+
const porcelain = readPorcelain(projectDir);
|
|
295
|
+
if (porcelain === null) {
|
|
296
|
+
return {
|
|
297
|
+
ok: false,
|
|
298
|
+
action: "refused",
|
|
299
|
+
detail: "Cannot determine whether the legacy deft/ tree is clean (git unavailable). " +
|
|
300
|
+
"Inspect `git status deft/` and remove the tree manually once clean.",
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const dirtyPaths = porcelainTouchesLegacyDeft(porcelain);
|
|
304
|
+
if (dirtyPaths.length > 0) {
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
action: "refused",
|
|
308
|
+
detail: "Refusing to remove the legacy deft/ tree because it has local modifications or " +
|
|
309
|
+
"untracked files. Reconcile or commit those changes, then re-run " +
|
|
310
|
+
"`npx @deftai/directive update`, or remove deft/ manually once clean. " +
|
|
311
|
+
`Dirty paths: ${JSON.stringify(dirtyPaths.slice(0, 5))}` +
|
|
312
|
+
(dirtyPaths.length > 5 ? ", …" : "") +
|
|
313
|
+
".",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const deftDir = join(projectDir, LEGACY_DEFT_DIR);
|
|
317
|
+
const removeDir = seams.removeDir ?? ((p) => rmSync(p, { recursive: true, force: true }));
|
|
318
|
+
const runGitRm = seams.runGitRm ??
|
|
319
|
+
((root, relPath) => {
|
|
320
|
+
execFileSync("git", ["rm", "-r", "--ignore-unmatch", relPath], {
|
|
321
|
+
cwd: root,
|
|
322
|
+
encoding: "utf8",
|
|
323
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
try {
|
|
327
|
+
runGitRm(projectDir, LEGACY_DEFT_DIR);
|
|
328
|
+
removeDir(deftDir);
|
|
329
|
+
return {
|
|
330
|
+
ok: true,
|
|
331
|
+
action: "staged",
|
|
332
|
+
detail: "Removed the legacy deft/ framework tree and staged its deletion; " +
|
|
333
|
+
".deft/core/ is now the sole framework root.",
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
try {
|
|
338
|
+
removeDir(deftDir);
|
|
339
|
+
return {
|
|
340
|
+
ok: true,
|
|
341
|
+
action: "removed",
|
|
342
|
+
detail: "Removed the legacy deft/ framework tree from disk; stage the deletion with " +
|
|
343
|
+
"`git add -u deft/` if it was tracked.",
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
catch (cause) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
action: "refused",
|
|
350
|
+
detail: `Could not remove the legacy deft/ tree: ${String(cause)}`,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
223
355
|
//# sourceMappingURL=legacy-detect.js.map
|