@bastani/atomic 0.5.14 → 0.5.15-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.claude/settings.json +24 -0
  2. package/.opencode/opencode.json +10 -0
  3. package/README.md +10 -58
  4. package/assets/settings.schema.json +29 -0
  5. package/dist/sdk/runtime/executor.d.ts.map +1 -1
  6. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +4 -1
  7. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +1 -1
  8. package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +4 -1
  9. package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +1 -1
  10. package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +4 -1
  11. package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -1
  12. package/dist/services/config/atomic-config.d.ts +44 -0
  13. package/dist/services/config/atomic-config.d.ts.map +1 -0
  14. package/dist/services/config/definitions.d.ts +18 -13
  15. package/dist/services/config/definitions.d.ts.map +1 -1
  16. package/dist/services/config/index.d.ts +7 -0
  17. package/dist/services/config/index.d.ts.map +1 -0
  18. package/dist/services/config/settings-schema.d.ts +2 -0
  19. package/dist/services/config/settings-schema.d.ts.map +1 -0
  20. package/dist/services/system/copy.d.ts +8 -1
  21. package/dist/services/system/copy.d.ts.map +1 -1
  22. package/package.json +3 -1
  23. package/src/cli.ts +1 -30
  24. package/src/commands/cli/chat/index.ts +21 -6
  25. package/src/commands/cli/init/index.ts +78 -323
  26. package/src/commands/cli/init/onboarding.ts +4 -10
  27. package/src/commands/cli/init/scm.ts +3 -34
  28. package/src/lib/common-ignore.ts +46 -0
  29. package/src/lib/merge.ts +28 -1
  30. package/src/sdk/runtime/executor.ts +85 -52
  31. package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +9 -4
  32. package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +12 -7
  33. package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +12 -7
  34. package/src/services/config/atomic-config.ts +95 -1
  35. package/src/services/config/atomic-global-config.ts +8 -21
  36. package/src/services/config/definitions.ts +41 -44
  37. package/src/services/config/settings.ts +2 -1
  38. package/src/services/system/agents.ts +2 -1
  39. package/src/services/system/copy.ts +18 -7
  40. package/src/services/system/skills.ts +3 -1
@@ -2,6 +2,9 @@
2
2
  * Agent configuration definitions for atomic CLI
3
3
  */
4
4
 
5
+ import { stat } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+
5
8
  export interface AgentConfig {
6
9
  /** Display name for the agent */
7
10
  name: string;
@@ -36,12 +39,10 @@ export const AGENT_CONFIG: Record<AgentKey, AgentConfig> = {
36
39
  "--allow-dangerously-skip-permissions",
37
40
  "--dangerously-skip-permissions",
38
41
  ],
39
- env_vars: {
40
- CLAUDE_CODE_NO_FLICKER: "1",
41
- },
42
+ env_vars: {},
42
43
  folder: ".claude",
43
44
  install_url: "https://code.claude.com/docs/en/setup",
44
- exclude: [".DS_Store", "settings.json"],
45
+ exclude: [],
45
46
  onboarding_files: [
46
47
  {
47
48
  source: ".mcp.json",
@@ -62,14 +63,7 @@ export const AGENT_CONFIG: Record<AgentKey, AgentConfig> = {
62
63
  env_vars: { OPENCODE_EXPERIMENTAL_LSP_TOOL: "true" },
63
64
  folder: ".opencode",
64
65
  install_url: "https://opencode.ai",
65
- exclude: [
66
- "node_modules",
67
- ".gitignore",
68
- "bun.lock",
69
- "package.json",
70
- ".DS_Store",
71
- "opencode.json",
72
- ],
66
+ exclude: [".gitignore", "package.json"],
73
67
  onboarding_files: [
74
68
  {
75
69
  source: ".opencode/opencode.json",
@@ -81,30 +75,37 @@ export const AGENT_CONFIG: Record<AgentKey, AgentConfig> = {
81
75
  copilot: {
82
76
  name: "GitHub Copilot CLI",
83
77
  cmd: "copilot",
84
- chat_flags: [
85
- "--add-dir",
86
- ".",
87
- "--yolo",
88
- "--experimental",
89
- "--no-auto-update",
90
- ],
78
+ chat_flags: ["--add-dir", ".", "--yolo", "--experimental"],
91
79
  env_vars: {
92
80
  COPILOT_ALLOW_ALL: "true",
93
81
  },
94
82
  folder: ".github",
95
83
  install_url:
96
84
  "https://github.com/github/copilot-cli?tab=readme-ov-file#installation",
97
- exclude: ["workflows", "dependabot.yml", ".DS_Store"],
85
+ exclude: ["workflows", "dependabot.yml"],
98
86
  onboarding_files: [
99
87
  {
100
- source: ".vscode/mcp.json",
101
- destination: ".vscode/mcp.json",
88
+ source: ".mcp.json",
89
+ destination: ".mcp.json",
102
90
  merge: true,
103
91
  },
104
92
  ],
105
93
  },
106
94
  };
107
95
 
96
+ /**
97
+ * Per-provider overrides that users can set in `.atomic/settings.json`
98
+ * (local) or `~/.atomic/settings.json` (global).
99
+ *
100
+ * - `chatFlags`: when set, replaces the agent's default `chat_flags` entirely.
101
+ * - `envVars`: environment variables merged on top of the agent's default
102
+ * `env_vars` (user values win on conflict).
103
+ */
104
+ export interface ProviderOverrides {
105
+ chatFlags?: string[];
106
+ envVars?: Record<string, string>;
107
+ }
108
+
108
109
  export function isValidAgent(key: string): key is AgentKey {
109
110
  return key in AGENT_CONFIG;
110
111
  }
@@ -128,49 +129,31 @@ const SCM_KEYS = ["github", "sapling"] as const;
128
129
  export type SourceControlType = (typeof SCM_KEYS)[number];
129
130
 
130
131
  export interface ScmConfig {
131
- /** Internal identifier */
132
- name: string;
133
132
  /** Display name for prompts */
134
133
  displayName: string;
135
134
  /** Primary CLI tool (git or sl) */
136
135
  cliTool: string;
137
- /** Code review tool (gh, jf submit, arc diff, etc.) */
138
- reviewTool: string;
139
136
  /** Code review system (github, phabricator) */
140
137
  reviewSystem: string;
141
- /** Directory marker for potential future auto-detection */
138
+ /** Directory marker used to detect this SCM in a repo (e.g. `.git`, `.sl`) */
142
139
  detectDir: string;
143
- /** Code review command file name */
144
- reviewCommandFile: string;
145
- /** Required configuration files */
146
- requiredConfigFiles?: string[];
147
140
  }
148
141
 
149
142
  export const SCM_CONFIG: Record<SourceControlType, ScmConfig> = {
150
143
  github: {
151
- name: "github",
152
144
  displayName: "GitHub / Git",
153
145
  cliTool: "git",
154
- reviewTool: "gh",
155
146
  reviewSystem: "github",
156
147
  detectDir: ".git",
157
- reviewCommandFile: "create-gh-pr.md",
158
148
  },
159
149
  sapling: {
160
- name: "sapling",
161
150
  displayName: "Sapling + Phabricator",
162
151
  cliTool: "sl",
163
- reviewTool: "jf submit",
164
152
  reviewSystem: "phabricator",
165
153
  detectDir: ".sl",
166
- reviewCommandFile: "submit-diff.md",
167
- requiredConfigFiles: [".arcconfig", "~/.arcrc"],
168
154
  },
169
155
  };
170
156
 
171
- /** Commands that have SCM-specific variants */
172
- export const SCM_SPECIFIC_COMMANDS = ["commit"];
173
-
174
157
  /**
175
158
  * SCM-variant skill names, grouped by source control type.
176
159
  *
@@ -207,8 +190,22 @@ export function isValidScm(key: string): key is SourceControlType {
207
190
  }
208
191
 
209
192
  /**
210
- * Get the configuration for a specific SCM type
193
+ * Detect the SCM type by looking for marker directories in `projectRoot`.
194
+ *
195
+ * Checks each {@link ScmConfig.detectDir} (e.g. `.git`, `.sl`) and returns
196
+ * the first match. Returns `null` when no known marker is found.
211
197
  */
212
- export function getScmConfig(key: SourceControlType): ScmConfig {
213
- return SCM_CONFIG[key];
198
+ export async function detectScmType(
199
+ projectRoot: string,
200
+ ): Promise<SourceControlType | null> {
201
+ for (const key of getScmKeys()) {
202
+ const markerPath = join(projectRoot, SCM_CONFIG[key].detectDir);
203
+ try {
204
+ await stat(markerPath);
205
+ return key;
206
+ } catch {
207
+ // marker not found — try next
208
+ }
209
+ }
210
+ return null;
214
211
  }
@@ -14,7 +14,7 @@ import { homedir } from "node:os";
14
14
  import { SETTINGS_SCHEMA_URL } from "./settings-schema.ts";
15
15
  import { ensureDir } from "../system/copy.ts";
16
16
  import { errorMessage } from "../../sdk/errors.ts";
17
- import type { AgentKey, SourceControlType } from "./definitions.ts";
17
+ import type { AgentKey, ProviderOverrides, SourceControlType } from "./definitions.ts";
18
18
 
19
19
  export interface TrustedPathEntry {
20
20
  workspacePath: string;
@@ -28,6 +28,7 @@ interface AtomicSettings {
28
28
  lastUpdated?: string;
29
29
  trustedPaths?: TrustedPathEntry[];
30
30
  telemetryEnabled?: boolean;
31
+ providers?: Partial<Record<AgentKey, ProviderOverrides>>;
31
32
  }
32
33
 
33
34
  /** Runtime guard for parsed JSON to ensure it's a plain object. */
@@ -25,6 +25,7 @@ import {
25
25
  ensureDir,
26
26
  pathExists,
27
27
  } from "./copy.ts";
28
+ import { createCommonIgnoreFilter } from "../../lib/common-ignore.ts";
28
29
 
29
30
  /**
30
31
  * Locate the package root by walking up from this module. Both in installed
@@ -72,7 +73,7 @@ export async function installGlobalAgents(): Promise<void> {
72
73
  continue;
73
74
  }
74
75
 
75
- await copyDir(src, dest);
76
+ await copyDir(src, dest, { ignoreFilter: createCommonIgnoreFilter() });
76
77
  }
77
78
 
78
79
  // Surface skipped sources via a non-fatal thrown error only if ALL sources
@@ -5,6 +5,7 @@
5
5
  import { readdir, mkdir, stat, readFile } from "node:fs/promises";
6
6
  import { mkdirSync } from "node:fs";
7
7
  import { join, extname, relative, resolve } from "node:path";
8
+ import type { Ignore } from "ignore";
8
9
  import { getOppositeScriptExtension } from "./detect.ts";
9
10
  import {
10
11
  assertPathWithinRoot,
@@ -77,6 +78,8 @@ export function isPathSafe(basePath: string, targetPath: string): boolean {
77
78
  interface CopyOptions {
78
79
  /** Paths to exclude (relative to source root or base names) */
79
80
  exclude?: string[];
81
+ /** Gitignore-style filter for common junk patterns (via the `ignore` package) */
82
+ ignoreFilter?: Ignore;
80
83
  /** Whether to skip scripts for the opposite platform */
81
84
  skipOppositeScripts?: boolean;
82
85
  }
@@ -158,21 +161,29 @@ async function copySymlinkAsFileWithOverwriteOption(
158
161
  * Check if a path should be excluded based on exclusion rules.
159
162
  * Uses normalized paths (forward slashes) to ensure consistent matching
160
163
  * on both Windows and Unix systems.
164
+ *
165
+ * When an {@link Ignore} filter is provided, gitignore-style glob patterns
166
+ * are evaluated first so common junk files (.DS_Store, node_modules, etc.)
167
+ * are filtered automatically without polluting the explicit `exclude` list.
161
168
  */
162
169
  export function shouldExclude(
163
170
  relativePath: string,
164
171
  name: string,
165
- exclude: string[]
172
+ exclude: string[],
173
+ ignoreFilter?: Ignore,
166
174
  ): boolean {
175
+ const normalizedPath = normalizePath(relativePath);
176
+
177
+ // Gitignore-style patterns take precedence
178
+ if (ignoreFilter?.ignores(normalizedPath)) {
179
+ return true;
180
+ }
181
+
167
182
  // Check if the name matches any exclusion
168
183
  if (exclude.includes(name)) {
169
184
  return true;
170
185
  }
171
186
 
172
- // Normalize the relative path for cross-platform comparison
173
- // This ensures Windows backslash paths match forward-slash exclusion patterns
174
- const normalizedPath = normalizePath(relativePath);
175
-
176
187
  // Check if the relative path starts with any exclusion
177
188
  for (const ex of exclude) {
178
189
  const normalizedExclusion = normalizePath(ex);
@@ -205,7 +216,7 @@ async function copyDirInternal(
205
216
  overwriteExisting: boolean = true,
206
217
  ): Promise<void> {
207
218
  try {
208
- const { exclude = [], skipOppositeScripts = true } = options;
219
+ const { exclude = [], ignoreFilter, skipOppositeScripts = true } = options;
209
220
  const root = rootSrc ?? src;
210
221
  const destinationRoot = rootDest ?? dest;
211
222
 
@@ -245,7 +256,7 @@ async function copyDirInternal(
245
256
  }
246
257
 
247
258
  // Check if this path should be excluded
248
- if (shouldExclude(relativePath, entry.name, exclude)) {
259
+ if (shouldExclude(relativePath, entry.name, exclude, ignoreFilter)) {
249
260
  continue;
250
261
  }
251
262
 
@@ -18,6 +18,7 @@
18
18
  import { join } from "node:path";
19
19
  import { homedir } from "node:os";
20
20
  import { ALL_SCM_SKILLS } from "../config/index.ts";
21
+ import { createCommonIgnoreFilter } from "../../lib/common-ignore.ts";
21
22
  import { copyDir, pathExists } from "./copy.ts";
22
23
 
23
24
  /**
@@ -64,10 +65,11 @@ export async function installGlobalSkills(): Promise<void> {
64
65
 
65
66
  // Build the exclusion list from SCM skill names
66
67
  const exclude = [...SCM_SKILL_SET];
68
+ const ignoreFilter = createCommonIgnoreFilter();
67
69
 
68
70
  await Promise.all(
69
71
  SKILL_DEST_DIRS.map((rel) =>
70
- copyDir(src, join(home, rel), { exclude }),
72
+ copyDir(src, join(home, rel), { exclude, ignoreFilter }),
71
73
  ),
72
74
  );
73
75
  }