ccdesk 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +7 -4
  3. package/dist/core/agent-edit.d.ts +6 -0
  4. package/dist/{server → core}/agent-edit.js +15 -21
  5. package/dist/core/backups.d.ts +19 -0
  6. package/dist/{server → core}/backups.js +64 -48
  7. package/dist/core/core.d.ts +96 -0
  8. package/dist/core/core.js +57 -0
  9. package/dist/core/index.d.ts +22 -0
  10. package/dist/core/index.js +38 -0
  11. package/dist/core/mcp-edit.d.ts +23 -0
  12. package/dist/{server → core}/mcp-edit.js +121 -100
  13. package/dist/core/mutations.d.ts +20 -0
  14. package/dist/{server → core}/mutations.js +8 -15
  15. package/dist/core/open.d.ts +28 -0
  16. package/dist/{server → core}/open.js +38 -13
  17. package/dist/core/parse-edit.d.ts +12 -0
  18. package/dist/core/parse-edit.js +64 -0
  19. package/dist/core/reference.d.ts +3 -0
  20. package/dist/core/reference.js +11 -0
  21. package/dist/core/scan/compare.d.ts +7 -0
  22. package/dist/{server → core}/scan/compare.js +2 -2
  23. package/dist/core/scan/context.d.ts +13 -0
  24. package/dist/{server → core}/scan/context.js +55 -32
  25. package/dist/core/scan/data.d.ts +14 -0
  26. package/dist/core/scan/data.js +26 -0
  27. package/dist/core/scan/env.d.ts +28 -0
  28. package/dist/{server → core}/scan/env.js +15 -4
  29. package/dist/core/scan/files.d.ts +68 -0
  30. package/dist/core/scan/files.js +270 -0
  31. package/dist/core/scan/git.d.ts +20 -0
  32. package/dist/core/scan/git.js +106 -0
  33. package/dist/core/scan/index.d.ts +5 -0
  34. package/dist/{server → core}/scan/index.js +119 -82
  35. package/dist/core/scan/instructions.d.ts +54 -0
  36. package/dist/{server → core}/scan/instructions.js +141 -73
  37. package/dist/core/scan/inventory.d.ts +38 -0
  38. package/dist/{server → core}/scan/inventory.js +134 -115
  39. package/dist/core/scan/mcp.d.ts +28 -0
  40. package/dist/{server → core}/scan/mcp.js +153 -95
  41. package/dist/core/scan/memory.d.ts +8 -0
  42. package/dist/{server → core}/scan/memory.js +8 -8
  43. package/dist/core/scan/path-test.d.ts +9 -0
  44. package/dist/core/scan/path-test.js +156 -0
  45. package/dist/core/scan/permissions.d.ts +31 -0
  46. package/dist/{server → core}/scan/permissions.js +5 -8
  47. package/dist/core/scan/plugins.d.ts +39 -0
  48. package/dist/{server → core}/scan/plugins.js +36 -27
  49. package/dist/core/scan/projects.d.ts +9 -0
  50. package/dist/{server → core}/scan/projects.js +7 -5
  51. package/dist/core/scan/sandbox.d.ts +14 -0
  52. package/dist/{server → core}/scan/sandbox.js +87 -59
  53. package/dist/core/scan/settings.d.ts +37 -0
  54. package/dist/{server → core}/scan/settings.js +98 -93
  55. package/dist/core/scan/tools.d.ts +10 -0
  56. package/dist/{server → core}/scan/tools.js +28 -20
  57. package/dist/core/scan/usage.d.ts +19 -0
  58. package/dist/{server → core}/scan/usage.js +122 -97
  59. package/dist/core/schema.d.ts +8 -0
  60. package/dist/core/schema.js +67 -0
  61. package/dist/core/secrets.d.ts +45 -0
  62. package/dist/core/secrets.js +207 -0
  63. package/dist/core/settings-edit.d.ts +9 -0
  64. package/dist/{server → core}/settings-edit.js +91 -112
  65. package/dist/core/watch.d.ts +45 -0
  66. package/dist/{server → core}/watch.js +54 -17
  67. package/dist/core/write.d.ts +117 -0
  68. package/dist/{server → core}/write.js +61 -8
  69. package/dist/server/args.d.ts +32 -0
  70. package/dist/server/args.js +106 -0
  71. package/dist/server/cli.d.ts +2 -0
  72. package/dist/server/cli.js +58 -83
  73. package/dist/server/commands.d.ts +8 -0
  74. package/dist/server/commands.js +39 -0
  75. package/dist/server/server.d.ts +12 -0
  76. package/dist/server/server.js +80 -195
  77. package/dist/server/start.d.ts +13 -0
  78. package/dist/server/start.js +21 -0
  79. package/dist/shared/compare.d.ts +25 -0
  80. package/dist/shared/memory.d.ts +6 -0
  81. package/dist/shared/memory.js +18 -2
  82. package/dist/shared/requests.d.ts +104 -0
  83. package/dist/shared/requests.js +73 -0
  84. package/dist/shared/scopes.d.ts +10 -0
  85. package/dist/shared/settings-catalog.json +2199 -0
  86. package/dist/shared/types.d.ts +535 -0
  87. package/dist/shared/types.js +11 -0
  88. package/dist/ui/assets/index-C9qqEI5Z.js +60 -0
  89. package/dist/ui/index.html +1 -1
  90. package/dist/view/diff.d.ts +16 -0
  91. package/dist/view/diff.js +85 -0
  92. package/dist/view/format.d.ts +17 -0
  93. package/dist/view/format.js +47 -0
  94. package/dist/view/index.d.ts +11 -0
  95. package/dist/view/index.js +13 -0
  96. package/dist/view/lines.d.ts +9 -0
  97. package/dist/view/lines.js +12 -0
  98. package/dist/view/path-test.d.ts +3 -0
  99. package/dist/view/path-test.js +7 -0
  100. package/dist/view/sections/catalog.d.ts +9 -0
  101. package/dist/view/sections/catalog.js +47 -0
  102. package/dist/view/sections/extensions.d.ts +1 -0
  103. package/dist/view/sections/extensions.js +48 -0
  104. package/dist/view/sections/hooks.d.ts +1 -0
  105. package/dist/view/sections/hooks.js +32 -0
  106. package/dist/view/sections/index.d.ts +6 -0
  107. package/dist/view/sections/index.js +27 -0
  108. package/dist/view/sections/instructions.d.ts +6 -0
  109. package/dist/view/sections/instructions.js +38 -0
  110. package/dist/view/sections/mcp.d.ts +3 -0
  111. package/dist/view/sections/mcp.js +38 -0
  112. package/dist/view/sections/memory.d.ts +1 -0
  113. package/dist/view/sections/memory.js +31 -0
  114. package/dist/view/sections/permissions.d.ts +17 -0
  115. package/dist/view/sections/permissions.js +87 -0
  116. package/dist/view/sections/settings.d.ts +1 -0
  117. package/dist/view/sections/settings.js +33 -0
  118. package/dist/view/sections/tools.d.ts +6 -0
  119. package/dist/view/sections/tools.js +57 -0
  120. package/dist/view/sections/types.d.ts +123 -0
  121. package/dist/view/sections/types.js +10 -0
  122. package/dist/view/settings.d.ts +12 -0
  123. package/dist/view/settings.js +31 -0
  124. package/dist/view/usage.d.ts +8 -0
  125. package/dist/view/usage.js +18 -0
  126. package/package.json +13 -2
  127. package/dist/server/scan/files.js +0 -480
  128. package/dist/server/scan/path-test.js +0 -136
  129. package/dist/server/schema.js +0 -15
  130. package/dist/server/secrets.js +0 -63
  131. package/dist/ui/assets/index-Dg9dxngR.js +0 -60
@@ -0,0 +1,106 @@
1
+ import { execFile } from "node:child_process";
2
+ import { dirname, normalize, relative } from "node:path";
3
+ import { promisify } from "node:util";
4
+ import {} from "../../shared/types.js";
5
+ import {} from "./env.js";
6
+ import { isWithin } from "./files.js";
7
+ const run = promisify(execFile);
8
+ export async function gitRoot(dir) {
9
+ try {
10
+ const { stdout } = await run("git", ["-C", dir, "rev-parse", "--show-toplevel"], {
11
+ timeout: 5000,
12
+ });
13
+ // Git prints forward slashes on Windows too; normalise so path comparisons hold.
14
+ return stdout.trim() ? normalize(stdout.trim()) : null;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ /** Every checkout of the repository `dir` is in, the main one first; empty outside git. */
21
+ export async function worktrees(dir) {
22
+ try {
23
+ const { stdout } = await run("git", ["-C", dir, "worktree", "list", "--porcelain"], {
24
+ timeout: 5000,
25
+ });
26
+ return stdout
27
+ .split("\n")
28
+ .filter((line) => line.startsWith("worktree "))
29
+ .map((line) => normalize(line.slice("worktree ".length)));
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ }
35
+ /** Main worktree root: worktrees of one repository share Claude Code's auto memory. */
36
+ export async function mainWorktreeRoot(dir) {
37
+ try {
38
+ const { stdout } = await run("git", ["-C", dir, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: 5000 });
39
+ const common = stdout.trim();
40
+ if (!common)
41
+ return null;
42
+ return common.endsWith("/.git") || common.endsWith("\\.git")
43
+ ? normalize(dirname(common))
44
+ : null;
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
50
+ /**
51
+ * The key ~/.claude.json keeps a folder's trust answers and local MCP servers under: the main
52
+ * checkout's root, else the repository root, else the folder itself outside git. The scan reads
53
+ * under it and `claude mcp add --scope local` writes under it, so both take it from here.
54
+ */
55
+ export function trustKey(dir, mainRoot, root) {
56
+ return mainRoot ?? root ?? dir;
57
+ }
58
+ /**
59
+ * A folder as ~/.claude.json names it under `projects`: Claude Code writes Windows paths with
60
+ * forward slashes (observed in v2.1.296; the docs do not say), so lookups go through this.
61
+ */
62
+ export function claudeJsonKey(env, path) {
63
+ return env.platform === "win32" ? path.replaceAll("\\", "/") : path;
64
+ }
65
+ /** Git visibility of each path, batched into two git calls per repository. */
66
+ export async function visibilityOf(root, paths) {
67
+ const result = new Map();
68
+ if (paths.length === 0)
69
+ return result;
70
+ if (!root) {
71
+ for (const p of paths)
72
+ result.set(p, "no-git");
73
+ return result;
74
+ }
75
+ const inside = paths.filter((p) => p !== root && isWithin(root, p));
76
+ for (const p of paths)
77
+ if (!inside.includes(p))
78
+ result.set(p, "no-git");
79
+ if (inside.length === 0)
80
+ return result;
81
+ const rels = new Map(inside.map((p) => [p, relative(root, p).replaceAll("\\", "/")]));
82
+ const tracked = await gitLines(root, ["ls-files", "--full-name", "-z", "--", ...inside], "\0");
83
+ // check-ignore echoes the paths it matched, and on Windows an absolute path does not come back
84
+ // as written, so it gets relative ones. It accepts -z only with --stdin, so its output is split
85
+ // by line.
86
+ const ignored = await gitLines(root, ["check-ignore", "--no-index", "--", ...rels.values()], "\n");
87
+ for (const [p, rel] of rels) {
88
+ if (tracked.has(rel))
89
+ result.set(p, "tracked");
90
+ else if (ignored.has(rel))
91
+ result.set(p, "ignored");
92
+ else
93
+ result.set(p, "untracked");
94
+ }
95
+ return result;
96
+ }
97
+ // Failures (including check-ignore's exit 1 for "nothing ignored") read as an empty set.
98
+ async function gitLines(root, args, separator) {
99
+ try {
100
+ const { stdout } = await run("git", ["-C", root, ...args], { timeout: 5000 });
101
+ return new Set(stdout.split(separator).filter(Boolean));
102
+ }
103
+ catch {
104
+ return new Set();
105
+ }
106
+ }
@@ -0,0 +1,5 @@
1
+ import { type DirectoryView, type Overview, type SessionProfile } from "../../shared/types.ts";
2
+ import { type ScanEnv } from "./env.ts";
3
+ export declare function scanOverview(env: ScanEnv, launchDir: string): Promise<Overview>;
4
+ /** Everything Claude Code reads when started in `dir`. */
5
+ export declare function scanDirectory(env: ScanEnv, dir: string, profile?: SessionProfile): Promise<DirectoryView>;
@@ -4,20 +4,19 @@ import { storeDirName } from "../../shared/memory.js";
4
4
  import {} from "../../shared/types.js";
5
5
  import { buildContext } from "./context.js";
6
6
  import { isConfigHome } from "./env.js";
7
- import { gitRoot, isDir, isFile, isRecord, mainWorktreeRoot, readJsonObject, stringsIn, visibilityOf, } from "./files.js";
7
+ import { isDir, isFile, readJsonObject, stringsIn } from "./files.js";
8
+ import { isRecord } from "./data.js";
9
+ import { claudeJsonKey, gitRoot, mainWorktreeRoot, trustKey, visibilityOf } from "./git.js";
8
10
  import { AGENTS_MD_MODES, resolveInstructions } from "./instructions.js";
9
11
  import { readInventory } from "./inventory.js";
10
12
  import { readMemoryStore } from "./memory.js";
11
13
  import { permissionsOf, trustWaits } from "./permissions.js";
12
14
  import { listProjects } from "./projects.js";
13
- import { effectiveSettings, readSettingsLayers } from "./settings.js";
15
+ import { effectiveSettings, readManagedSettings, readSettingsLayers } from "./settings.js";
14
16
  import { builtinTools } from "./tools.js";
15
- async function readClaudeJson(env) {
16
- const result = await readJsonObject(env, env.claudeJson);
17
- return result.ok ? result.data : null;
18
- }
19
17
  export async function scanOverview(env, launchDir) {
20
- const claudeJson = await readClaudeJson(env);
18
+ const result = await readJsonObject(env, env.claudeJson);
19
+ const claudeJson = result.ok ? result.data : null;
21
20
  const projects = await listProjects(env, claudeJson, launchDir);
22
21
  const hashesByName = new Map();
23
22
  const memoryCatalog = [];
@@ -53,26 +52,30 @@ export async function scanOverview(env, launchDir) {
53
52
  export async function scanDirectory(env, dir, profile = "interactive") {
54
53
  const path = resolve(dir);
55
54
  const exists = await isDir(path);
56
- const claudeJson = await readClaudeJson(env);
55
+ const problems = [];
56
+ const claudeJson = await readClaudeJson(env, problems);
57
57
  const root = exists ? await gitRoot(path) : null;
58
- const settings = await readSettingsLayers(env, path);
58
+ const managed = await readManagedSettings(env);
59
+ const settings = await readSettingsLayers(env, path, managed);
60
+ for (const l of settings)
61
+ if (l.error)
62
+ problems.push({
63
+ path: l.path,
64
+ error: l.error,
65
+ ...(l.blocksStart ? { blocksStart: true } : {}),
66
+ });
59
67
  const effective = effectiveSettings(settings);
60
68
  const mode = agentsMdMode(settings);
61
69
  const excludes = effective.find((s) => s.key === "claudeMdExcludes")?.value;
62
70
  const mainRoot = exists ? await mainWorktreeRoot(path) : null;
63
- const trust = trustOf(env, claudeJson, path, mainRoot ?? root);
64
- const managedLayer = settings.find((l) => l.scope === "managed");
65
- const managedClaudeMd = managedLayer?.data?.claudeMd;
66
- const problems = [];
71
+ const trust = trustOf(env, claudeJson, path, mainRoot, root);
67
72
  const instructions = await resolveInstructions({
68
73
  env,
69
74
  cwd: path,
70
75
  mode,
71
76
  excludes: stringsIn(excludes),
72
77
  externalImports: trust.externalImports,
73
- managedClaudeMd: managedLayer && typeof managedClaudeMd === "string"
74
- ? { path: `${managedLayer.path}#claudeMd`, text: managedClaudeMd }
75
- : null,
78
+ managedClaudeMd: managedClaudeMd(settings),
76
79
  problems,
77
80
  });
78
81
  const inventory = await readInventory({
@@ -80,67 +83,23 @@ export async function scanDirectory(env, dir, profile = "interactive") {
80
83
  projectDir: path,
81
84
  gitRoot: root,
82
85
  layers: settings,
86
+ effective,
87
+ managed: managed.data,
83
88
  claudeJson,
84
89
  trust,
85
90
  profile,
91
+ problems,
86
92
  });
87
- // Every project file that shapes Claude here, so the UI can say who sees each one.
88
- const mcpJson = join(path, ".mcp.json");
89
- const projectFiles = [
90
- ...instructions.filter((i) => i.scope === "project" || i.scope === "local").map((i) => i.path),
91
- ...settings
92
- .filter((s) => s.exists && (s.scope === "project" || s.scope === "local"))
93
- .map((s) => s.path),
94
- ...((await isFile(mcpJson)) ? [mcpJson] : []),
95
- ...[...inventory.agents, ...inventory.skills, ...inventory.commands, ...inventory.outputStyles]
96
- .filter((e) => e.scope === "project" || e.scope === "local")
97
- .map((e) => e.path),
98
- ];
99
- const visibility = await visibilityOf(root, projectFiles);
100
- for (const item of [...instructions, ...settings]) {
101
- const v = visibility.get(item.path);
102
- if (v)
103
- item.visibility = v;
104
- }
105
- let sharing = null;
106
- if (root) {
107
- sharing = { tracked: [], untracked: [], ignored: [] };
108
- for (const file of new Set(projectFiles)) {
109
- const v = visibility.get(file);
110
- // Shown as repository paths, with forward slashes on every platform.
111
- const rel = relative(root, file).replaceAll("\\", "/");
112
- if (v === "tracked")
113
- sharing.tracked.push(rel);
114
- else if (v === "untracked")
115
- sharing.untracked.push(rel);
116
- else if (v === "ignored")
117
- sharing.ignored.push(rel);
118
- }
119
- }
120
- // A local file tracked in git, or in a .claude that is a link, counts as the repository's
121
- // (docs: permissions#when-your-local-settings-file-needs-trust).
122
- const localFromRepository = (await Promise.all(settings
123
- .filter((l) => l.scope === "local" && l.exists)
124
- .map(async (l) => l.visibility === "tracked" ||
125
- ((await lstat(dirname(l.path)).catch(() => null))?.isSymbolicLink() ?? false)))).some(Boolean);
93
+ const sharing = await shareProjectFiles(path, root, instructions, settings, inventory);
126
94
  const trustOptions = {
127
95
  trust,
128
96
  inConfigHome: isConfigHome(env, path),
129
- localFromRepository,
97
+ // Reads the visibility shareProjectFiles gave each layer.
98
+ localFromRepository: await localFromRepository(settings),
130
99
  profile,
131
100
  };
132
- const memory = await readMemory(env, mainRoot, path, effective, trustOptions);
101
+ const memory = await readMemory(env, mainRoot, path, effective, trustOptions, problems);
133
102
  const permissions = permissionsOf(settings, trustOptions);
134
- for (const l of settings)
135
- if (l.error)
136
- problems.push({
137
- path: l.path,
138
- error: l.error,
139
- ...(l.blocksStart ? { blocksStart: true } : {}),
140
- });
141
- const mcp = await readJsonObject(env, mcpJson);
142
- if (!mcp.ok && !mcp.missing)
143
- problems.push({ path: mcpJson, error: mcp.error });
144
103
  return {
145
104
  path,
146
105
  profile,
@@ -163,32 +122,98 @@ export async function scanDirectory(env, dir, profile = "interactive") {
163
122
  permissions,
164
123
  tools: builtinTools(permissions, inventory.hooks),
165
124
  trust,
166
- problems,
125
+ // One line per file: a file read twice, such as a rule that a CLAUDE.md also imports, is one problem.
126
+ problems: problems.filter((p, i) => problems.findIndex((q) => q.path === p.path) === i),
167
127
  sharing,
168
128
  };
169
129
  }
130
+ /** ~/.claude.json, or null when it is missing or broken; a broken one is a problem. */
131
+ async function readClaudeJson(env, problems) {
132
+ const file = await readJsonObject(env, env.claudeJson);
133
+ if (file.ok)
134
+ return file.data;
135
+ // Without it the folder looks untrusted and your servers missing, so the problem says why.
136
+ if (!file.missing)
137
+ problems.push({
138
+ path: env.claudeJson,
139
+ error: `${file.error}, so ccdesk cannot show your trust answers or your user and local MCP servers`,
140
+ });
141
+ return null;
142
+ }
143
+ /** The CLAUDE.md text managed settings give in their claudeMd key. */
144
+ function managedClaudeMd(settings) {
145
+ const managed = settings.find((l) => l.scope === "managed");
146
+ const text = managed?.data?.claudeMd;
147
+ return managed && typeof text === "string" ? { path: `${managed.path}#claudeMd`, text } : null;
148
+ }
149
+ /**
150
+ * Says who sees each project file that shapes Claude here: marks the instructions and settings
151
+ * with their git visibility, and returns the repository's files by visibility, or null outside one.
152
+ */
153
+ async function shareProjectFiles(path, root, instructions, settings, inventory) {
154
+ const inProject = (scope) => scope === "project" || scope === "local";
155
+ const mcpJson = join(path, ".mcp.json");
156
+ const projectFiles = [
157
+ ...instructions.filter((i) => inProject(i.scope)).map((i) => i.path),
158
+ ...settings.filter((s) => s.exists && inProject(s.scope)).map((s) => s.path),
159
+ ...((await isFile(mcpJson)) ? [mcpJson] : []),
160
+ ...[...inventory.agents, ...inventory.skills, ...inventory.commands, ...inventory.outputStyles]
161
+ .filter((e) => inProject(e.scope))
162
+ .map((e) => e.path),
163
+ ];
164
+ const visibility = await visibilityOf(root, projectFiles);
165
+ for (const item of [...instructions, ...settings]) {
166
+ const v = visibility.get(item.path);
167
+ if (v)
168
+ item.visibility = v;
169
+ }
170
+ if (!root)
171
+ return null;
172
+ const sharing = { tracked: [], untracked: [], ignored: [] };
173
+ for (const file of new Set(projectFiles)) {
174
+ const v = visibility.get(file);
175
+ // Shown as repository paths, with forward slashes on every platform.
176
+ const rel = relative(root, file).replaceAll("\\", "/");
177
+ if (v === "tracked")
178
+ sharing.tracked.push(rel);
179
+ else if (v === "untracked")
180
+ sharing.untracked.push(rel);
181
+ else if (v === "ignored")
182
+ sharing.ignored.push(rel);
183
+ }
184
+ return sharing;
185
+ }
186
+ /**
187
+ * Whether the local settings file counts as the repository's: tracked in git, or in a .claude
188
+ * that is a link (docs: permissions#when-your-local-settings-file-needs-trust).
189
+ */
190
+ async function localFromRepository(settings) {
191
+ const fromRepository = await Promise.all(settings
192
+ .filter((l) => l.scope === "local" && l.exists)
193
+ .map(async (l) => l.visibility === "tracked" ||
194
+ ((await lstat(dirname(l.path)).catch(() => null))?.isSymbolicLink() ?? false)));
195
+ return fromRepository.some(Boolean);
196
+ }
170
197
  /**
171
198
  * Your answers to Claude Code's trust dialogs, from ~/.claude.json (docs: permissions#project-allow-rules-and-workspace-trust).
172
199
  * In a repository the key is the main checkout's root; outside one it is the folder, and a trusted
173
200
  * parent folder covers it. In your home directory trust lasts one session and is never saved.
174
201
  */
175
- function trustOf(env, claudeJson, path, repositoryRoot) {
202
+ function trustOf(env, claudeJson, path, mainRoot, repoRoot) {
176
203
  const projects = isRecord(claudeJson?.projects) ? claudeJson.projects : {};
177
- const entry = (key) => (isRecord(projects[key]) ? projects[key] : {});
178
- const root = repositoryRoot ?? path;
204
+ const entry = (dir) => {
205
+ const found = projects[claudeJsonKey(env, dir)];
206
+ return isRecord(found) ? found : {};
207
+ };
208
+ const root = trustKey(path, mainRoot, repoRoot);
179
209
  const own = entry(root);
180
210
  const trust = {
181
211
  root,
182
212
  trusted: own.hasTrustDialogAccepted === true,
183
213
  sessionOnly: path === env.home,
184
- // These two keys are not documented; they are what Claude Code writes for the import dialog.
185
- externalImports: own.hasClaudeMdExternalIncludesApproved === true
186
- ? "approved"
187
- : own.hasClaudeMdExternalIncludesWarningShown === true
188
- ? "declined"
189
- : "not-asked",
214
+ externalImports: importAnswer(own),
190
215
  };
191
- if (!trust.trusted && !repositoryRoot)
216
+ if (!trust.trusted && !mainRoot && !repoRoot)
192
217
  for (let d = dirname(path); d !== dirname(d); d = dirname(d))
193
218
  if (entry(d).hasTrustDialogAccepted === true) {
194
219
  trust.inheritedFrom = d;
@@ -196,12 +221,23 @@ function trustOf(env, claudeJson, path, repositoryRoot) {
196
221
  }
197
222
  return trust;
198
223
  }
224
+ /**
225
+ * Your answer to the dialog about imports from outside the project. These two keys are not
226
+ * documented; they are what Claude Code writes for that dialog.
227
+ */
228
+ function importAnswer(project) {
229
+ if (project.hasClaudeMdExternalIncludesApproved === true)
230
+ return "approved";
231
+ if (project.hasClaudeMdExternalIncludesWarningShown === true)
232
+ return "declined";
233
+ return "not-asked";
234
+ }
199
235
  /**
200
236
  * The auto memory Claude Code uses here (docs: memory#storage-location, settings-reference#automemoryenabled).
201
237
  * Worktrees and subdirectories of one repository share one store. The store is returned even
202
238
  * when its folder does not exist yet, so a note about memory being off still shows.
203
239
  */
204
- async function readMemory(env, mainRoot, path, effective, trustOptions) {
240
+ async function readMemory(env, mainRoot, path, effective, trustOptions, problems) {
205
241
  const key = mainRoot ?? path;
206
242
  const setting = effective.find((s) => s.key === "autoMemoryDirectory");
207
243
  // A value from a repository file counts once the folder is trusted, like hooks (docs:
@@ -216,7 +252,7 @@ async function readMemory(env, mainRoot, path, effective, trustOptions) {
216
252
  const customDir = typeof custom?.value === "string" ? expandHome(env, custom.value) : null;
217
253
  const storeName = env.projectDirName ?? storeDirName(key);
218
254
  const dir = customDir ?? join(env.claudeDir, "projects", storeName, "memory");
219
- const store = (await readMemoryStore(env, dir, key)) ?? {
255
+ const store = (await readMemoryStore(env, dir, key, problems)) ?? {
220
256
  dir,
221
257
  loaded: true,
222
258
  projectPath: key,
@@ -272,8 +308,9 @@ function agentsMdMode(layers) {
272
308
  for (const id of ["cc-plugin-agents-md@builtin", "agents-md@builtin"]) {
273
309
  const entry = configs[id];
274
310
  const value = isRecord(entry) && isRecord(entry.options) ? entry.options.instructionFiles : undefined;
275
- if (typeof value === "string" && AGENTS_MD_MODES.includes(value))
276
- return value;
311
+ const mode = AGENTS_MD_MODES.find((m) => m === value);
312
+ if (mode)
313
+ return mode;
277
314
  }
278
315
  }
279
316
  return "claude-md-or-agents-md";
@@ -0,0 +1,54 @@
1
+ import { type AgentsMdMode, type Instruction, type Problem, type TextFile, type Trust } from "../../shared/types.ts";
2
+ import { type ScanEnv } from "./env.ts";
3
+ export declare const AGENTS_MD_MODES: AgentsMdMode[];
4
+ /** The CLAUDE.md and AGENTS.md files each folder can hold, in the order Claude Code reads them. */
5
+ export declare const CLAUDE_MD_NAMES: string[];
6
+ export declare const AGENTS_MD_NAMES: string[];
7
+ /**
8
+ * Whether AGENTS.md loads, given the instruction mode and whether a CLAUDE.md or CLAUDE.local.md
9
+ * is there (docs: memory#choose-which-instruction-files-load).
10
+ */
11
+ export declare function agentsMdLoads(mode: AgentsMdMode, hasClaudeMd: boolean): boolean;
12
+ /**
13
+ * Whether a claudeMdExcludes glob matches a file by its own path or, for a link, its target's
14
+ * (docs: memory#exclude-specific-claude-md-files).
15
+ */
16
+ export declare function isExcluded(excludes: string[], path: string, realPath?: string): boolean;
17
+ /**
18
+ * Where a rule in `dir` really is when that is outside both the project and `dir`: a link's
19
+ * target, or the file a linked folder leads to. Undefined for a rule that stays inside.
20
+ */
21
+ export declare function ruleTarget(file: TextFile, dir: string, realCwd: string): Promise<string | undefined>;
22
+ /**
23
+ * Why a project rule linked from outside the project does not load, or null when it does: it waits
24
+ * for your approval of outside imports, and once approved loads only without a paths field
25
+ * (docs: memory#share-rules-across-projects-with-symlinks).
26
+ */
27
+ export declare function linkedRuleBlock(target: string | undefined, realCwd: string, approved: boolean, hasPaths: boolean): "needs-approval" | "has-paths" | null;
28
+ /** Directories from the filesystem root down to `cwd`, the order Claude Code concatenates them in. */
29
+ export declare function ancestorsRootFirst(cwd: string): string[];
30
+ interface ResolveOptions {
31
+ env: ScanEnv;
32
+ cwd: string;
33
+ mode: AgentsMdMode;
34
+ /** Merged `claudeMdExcludes` globs from every settings layer. */
35
+ excludes: string[];
36
+ /** Your answer to Claude Code's dialog about imports from outside the project. */
37
+ externalImports: Trust["externalImports"];
38
+ /** The managed settings' `claudeMd` text, which loads like a managed CLAUDE.md. */
39
+ managedClaudeMd: {
40
+ path: string;
41
+ text: string;
42
+ } | null;
43
+ /** Receives instruction files and imports that exist but cannot be read. */
44
+ problems: Problem[];
45
+ }
46
+ /**
47
+ * The instruction files Claude Code reads in `cwd`, in load order, each with whether it loads at
48
+ * session start, when Claude works with matching files, or not at all, and why.
49
+ * Follows code.claude.com/docs/en/memory.
50
+ */
51
+ export declare function resolveInstructions(options: ResolveOptions): Promise<Instruction[]>;
52
+ /** An invalid pattern matches nothing. */
53
+ export declare function safeMatch(path: string, glob: string): boolean;
54
+ export {};