diff --git a/README.md b/README.md index e92bdb7..d3d104d 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ npx @sentry/dotagents install All commands accept `--user` to operate on user scope (`~/.agents/`) instead of the current project. +`install --frozen` is a deprecated compatibility no-op. It warns, performs a normal install, and will be removed in the next major release. Use explicit `ref` values in `agents.toml` to pin sources. + ## Source Formats Skills can come from GitHub, GitLab, any git server, well-known HTTPS skill sources, or local directories: diff --git a/docs/public/llms.txt b/docs/public/llms.txt index 8d6a292..e493d2f 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -268,7 +268,7 @@ Generated subagent files: - Codex: `.codex/agents/.toml`, or `~/.codex/agents/.toml` for user scope - OpenCode: `.opencode/agents/.md`, or `~/.config/opencode/agents/.md` for user scope -Generated files include a dotagents header marker. `install` and `sync` overwrite stale managed files and prune removed managed files, but they do not overwrite hand-written files without the generated header marker. In `--frozen` mode, `install` loads subagents from existing installed files, preserves managed subagent files and lock entries instead of pruning removed subagents, and does not resolve subagent sources. They also avoid creating duplicate runtime identities when an unmanaged file in the same agent directory already declares the same subagent. +Generated files include a dotagents header marker. `install` and `sync` overwrite stale managed files and prune removed managed files, but they do not overwrite hand-written files without the generated header marker. The deprecated `--frozen` option is a warned compatibility no-op and does not change this behavior. They also avoid creating duplicate runtime identities when an unmanaged file in the same agent directory already declares the same subagent. ### Trust @@ -314,7 +314,7 @@ Create `agents.toml` and `.agents/skills/` directory. Automatically includes the npx @sentry/dotagents install ``` -Install and refresh dependencies from `agents.toml`. Resolves sources, copies skills, writes the lockfile, creates symlinks, and generates MCP, hook, and subagent configs. There is no separate update command. With `--frozen`, declared skills and subagents must already exist in `agents.lock`, subagents are loaded from existing installed files without resolving sources, the lockfile is not updated, and existing managed subagent files are not pruned. +Install and refresh dependencies from `agents.toml`. Resolves sources, copies skills, writes the lockfile, creates symlinks, and generates MCP, hook, and subagent configs. There is no separate update command. The deprecated `--frozen` option prints a warning and performs the same normal install. It is ignored and will be removed in the next major release; use explicit `ref` values to pin sources. ### add diff --git a/docs/src/content/docs/cli.mdx b/docs/src/content/docs/cli.mdx index 8f01f9d..a9520c4 100644 --- a/docs/src/content/docs/cli.mdx +++ b/docs/src/content/docs/cli.mdx @@ -57,9 +57,9 @@ dotagents install Install and refresh skill and subagent dependencies from `agents.toml`. Resolves sources, copies skills, writes the lockfile, creates symlinks, and generates MCP, hook, and subagent configs. There is no separate update command. With -`--frozen`, declared skills and subagents must already exist in `agents.lock`, -subagents are loaded from existing installed files without resolving sources, -the lockfile is not updated, and existing managed subagent files are not pruned. +`--frozen`, dotagents prints a compatibility warning and performs the same normal +install. The flag is ignored and will be removed in the next major release; use +explicit `ref` values in `agents.toml` to pin sources. Example: @@ -405,7 +405,7 @@ Generated files: - Codex: `.codex/agents/.toml` - OpenCode: `.opencode/agents/.md` -Generated files include a dotagents header marker. `install` and `sync` update managed files and do not overwrite hand-written files without the generated header marker. In `--frozen` mode, `install` loads subagents from existing installed files, preserves managed subagent files and lock entries instead of pruning removed subagents, and does not resolve subagent sources. They also avoid creating duplicate runtime identities when an unmanaged file in the same agent directory already declares the same subagent. +Generated files include a dotagents header marker. `install` and `sync` update managed files and do not overwrite hand-written files without the generated header marker. They also avoid creating duplicate runtime identities when an unmanaged file in the same agent directory already declares the same subagent. The deprecated `--frozen` option does not change this behavior. ## Scopes diff --git a/packages/dotagents/src/cli/commands/install.test.ts b/packages/dotagents/src/cli/commands/install.test.ts index dbe1fc5..9310e91 100644 --- a/packages/dotagents/src/cli/commands/install.test.ts +++ b/packages/dotagents/src/cli/commands/install.test.ts @@ -1,9 +1,9 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { mkdtemp, mkdir, readFile, writeFile, rm, lstat, access, chmod } from "node:fs/promises"; import { existsSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { runInstall as runInstallCommand, InstallError, type InstallOptions, type InstallResult } from "./install.js"; +import install, { runInstall as runInstallCommand, InstallError, type InstallOptions, type InstallResult } from "./install.js"; import { runSync } from "./sync.js"; import { exec } from "@sentry/dotagents-lib"; import { loadLockfile } from "../../lockfile/loader.js"; @@ -182,32 +182,31 @@ describe("runInstall", () => { expect(await readFile(mcpPath, "utf-8")).toBe(content); }); - it("fails with --frozen when no lockfile exists", async () => { + it("accepts --frozen as a warned normal install", async () => { await writeFile( join(projectRoot, "agents.toml"), `version = 1\n\n[[skills]]\nname = "pdf"\nsource = "git:${repoDir}"\n`, ); + await ensureGitRepo(); - const scope = resolveScope("project", projectRoot); - await expect( - runInstall({ scope, frozen: true }), - ).rejects.toThrow(InstallError); - }); + const previousCwd = process.cwd(); + const log = vi.spyOn(console, "log").mockImplementation(() => {}); + let output = ""; + try { + process.chdir(projectRoot); + await install(["--frozen"]); + } finally { + output = log.mock.calls.flat().join("\n"); + process.chdir(previousCwd); + log.mockRestore(); + } - it("frozen mode passes when lockfile matches", async () => { - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1\n\n[[skills]]\nname = "pdf"\nsource = "git:${repoDir}"\n`, + expect(output).toContain( + "--frozen is ignored and will be removed in the next major release", ); - - const scope = resolveScope("project", projectRoot); - - // First install to create lockfile - await runInstall({ scope }); - - // Second install with --frozen - const result = await runInstall({ scope, frozen: true }); - expect(result.installed).toContain("pdf"); + expect(existsSync(join(projectRoot, ".agents", "skills", "pdf", "SKILL.md"))).toBe(true); + const lockfile = await loadLockfile(join(projectRoot, "agents.lock")); + expect(lockfile?.skills["pdf"]).toBeDefined(); }); it("creates agent-specific symlinks (cursor shares .claude)", async () => { @@ -374,57 +373,6 @@ path = "code-reviewer.md" expect(await readFile(installedPath, "utf-8")).toBe("hand-written subagent\n"); }); - it("frozen mode fails when a subagent is missing from the lockfile", async () => { - const scope = resolveScope("project", projectRoot); - await writeLockfile(join(projectRoot, "agents.lock"), { - version: 1, - skills: {}, - subagents: {}, - }); - - const sourceDir = join(projectRoot, "agents"); - await mkdir(sourceDir, { recursive: true }); - await writeFile(join(sourceDir, "code-reviewer.md"), SUBAGENT_MD("code-reviewer")); - - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1 -[[subagents]] -name = "code-reviewer" -source = "path:agents" -path = "code-reviewer.md" -`, - ); - - await expect(runInstall({ scope, frozen: true })).rejects.toThrow( - '--frozen: subagent "code-reviewer" is in agents.toml but missing from agents.lock.', - ); - }); - - it("frozen mode passes when subagent lockfile entries match", async () => { - const sourceDir = join(projectRoot, "agents"); - await mkdir(sourceDir, { recursive: true }); - await writeFile(join(sourceDir, "code-reviewer.md"), SUBAGENT_MD("code-reviewer")); - - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1 -[[subagents]] -name = "code-reviewer" -source = "path:agents" -path = "code-reviewer.md" -`, - ); - - const scope = resolveScope("project", projectRoot); - await runInstall({ scope }); - - await rm(sourceDir, { recursive: true }); - - const result = await runInstall({ scope, frozen: true }); - expect(result.subagentWarnings).toEqual([]); - }); - it("clears removed skills from the lockfile when installing subagents", async () => { const scope = resolveScope("project", projectRoot); const skillDir = join(projectRoot, ".agents", "skills", "pdf"); @@ -966,22 +914,6 @@ path = "reviewer.md" expect(lockfile!.skills["review"]).toBeDefined(); }); - it("frozen mode works with wildcard lockfile", async () => { - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1\n\n[[skills]]\nname = "*"\nsource = "git:${repoDir}"\n`, - ); - - const scope = resolveScope("project", projectRoot); - // First install to create lockfile - await runInstall({ scope }); - - // Second install with --frozen - const result = await runInstall({ scope, frozen: true }); - expect(result.installed).toContain("pdf"); - expect(result.installed).toContain("review"); - }); - it("wildcard-expanded skills are gitignored", async () => { await writeFile( join(projectRoot, "agents.toml"), @@ -1117,68 +1049,6 @@ path = "reviewer.md" expect(existsSync(join(projectRoot, ".agents", "skills", "review"))).toBe(false); }); - it("does not prune in frozen mode", async () => { - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1\n\n[[skills]]\nname = "*"\nsource = "git:${repoDir}"\n`, - ); - - const scope = resolveScope("project", projectRoot); - - // First install — gets both "pdf" and "review" - await runInstall({ scope }); - - // Add "review" to the exclude list - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1\n\n[[skills]]\nname = "*"\nsource = "git:${repoDir}"\nexclude = ["review"]\n`, - ); - - // Frozen install should NOT prune (would create disk/lockfile inconsistency) - const result = await runInstall({ scope, frozen: true }); - expect(result.pruned).toHaveLength(0); - // Directory should still exist - expect(existsSync(join(projectRoot, ".agents", "skills", "review", "SKILL.md"))).toBe(true); - }); - - it("does not prune installed subagents in frozen mode", async () => { - const sourceDir = join(projectRoot, "agents"); - await mkdir(sourceDir, { recursive: true }); - await writeFile(join(sourceDir, "code-reviewer.md"), SUBAGENT_MD("code-reviewer")); - - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1 -agents = ["claude"] - -[[subagents]] -name = "code-reviewer" -source = "path:agents" -path = "code-reviewer.md" -`, - ); - - const scope = resolveScope("project", projectRoot); - await runInstall({ scope }); - - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1 -agents = ["claude"] -`, - ); - - const result = await runInstall({ scope, frozen: true }); - - expect(result.pruned).toEqual([]); - expect(existsSync(join(projectRoot, ".agents", "agents", "code-reviewer.md"))).toBe(true); - expect(existsSync(join(projectRoot, ".claude", "agents", "code-reviewer.md"))).toBe(true); - const gitignore = await readFile(join(projectRoot, ".agents", ".gitignore"), "utf-8"); - expect(gitignore).toContain("/agents/code-reviewer.md"); - const lockfile = await loadLockfile(join(projectRoot, "agents.lock")); - expect(lockfile!.subagents["code-reviewer"]).toBeDefined(); - }); - it("wildcard with all skills excluded installs nothing from that source", async () => { await writeFile( join(projectRoot, "agents.toml"), @@ -1190,23 +1060,6 @@ agents = ["claude"] expect(result.installed).toHaveLength(0); }); - it("frozen mode fails when skill missing from lockfile", async () => { - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1\n\n[[skills]]\nname = "pdf"\nsource = "git:${repoDir}"\n`, - ); - const scope = resolveScope("project", projectRoot); - await runInstall({ scope }); - - // Add review to config but lockfile still has only pdf - await writeFile( - join(projectRoot, "agents.toml"), - `version = 1\n\n[[skills]]\nname = "pdf"\nsource = "git:${repoDir}"\n\n[[skills]]\nname = "review"\nsource = "git:${repoDir}"\n`, - ); - - await expect(runInstall({ scope, frozen: true })).rejects.toThrow(InstallError); - }); - it("does not auto-create root .gitignore", async () => { await writeFile( join(projectRoot, "agents.toml"), diff --git a/packages/dotagents/src/cli/commands/install.ts b/packages/dotagents/src/cli/commands/install.ts index 6d8c7cb..12b7009 100644 --- a/packages/dotagents/src/cli/commands/install.ts +++ b/packages/dotagents/src/cli/commands/install.ts @@ -27,7 +27,6 @@ export { InstallError }; // writes have succeeded, then writes runtime projections and CLI output. export interface InstallOptions { scope: ScopeRoot; - frozen?: boolean; } export interface InstallResult { @@ -40,19 +39,19 @@ export interface InstallResult { } export async function runInstall(opts: InstallOptions): Promise { - const { scope, frozen } = opts; + const { scope } = opts; const config = await loadConfig(scope.configPath); const lockfile = await loadLockfile(scope.lockPath); - const skills = await installSkills(config, lockfile, scope, frozen); - const subagents = await installSubagents(config, lockfile, scope, frozen); + const skills = await installSkills(config, lockfile, scope); + const subagents = await installSubagents(config, scope); const newLock: Lockfile = { version: 1, skills: skills.lockEntries, subagents: subagents.lockEntries, }; - await writeCanonicalSubagents(config, scope, subagents.subagents, frozen); - const writeLock = !frozen && ( + await writeCanonicalSubagents(config, scope, subagents.subagents); + const writeLock = ( !!lockfile || config.skills.length > 0 || config.subagents.length > 0 @@ -61,14 +60,14 @@ export async function runInstall(opts: InstallOptions): Promise { await writeLockfile(scope.lockPath, newLock); } - await writeInstallGitignore(config, lockfile, scope, { + await writeInstallGitignore(config, scope, { installedSkillNames: skills.installed, subagents: subagents.subagents, - }, frozen); + }); await writeSkillSymlinks(config, scope); const mcpWarnings = await writeMcpRuntime(config, scope); const hookWarnings = await writeHookRuntime(config, scope); - const subagentWarnings = await writeSubagentRuntime(config, scope, subagents.subagents, frozen); + const subagentWarnings = await writeSubagentRuntime(config, scope, subagents.subagents); return { installed: skills.installed, @@ -93,13 +92,10 @@ export default async function install(args: string[], flags?: { user?: boolean } const scope = flags?.user ? resolveScope("user") : resolveDefaultScope(resolve(".")); await ensureUserScopeBootstrapped(scope); if (values["frozen"]) { - console.log(chalk.yellow("Warning: --frozen is deprecated and will be removed in a future release. Pinning is now managed via agents.toml refs.")); + console.log(chalk.yellow("Warning: --frozen is ignored and will be removed in the next major release. Install now follows normal agents.toml resolution; use explicit refs to pin sources.")); } - const result = await runInstall({ - scope, - frozen: values["frozen"], - }); + const result = await runInstall({ scope }); if (result.installed.length > 0) { console.log( diff --git a/packages/dotagents/src/cli/commands/install/agent-runtime.ts b/packages/dotagents/src/cli/commands/install/agent-runtime.ts index d685ab6..4a3bff6 100644 --- a/packages/dotagents/src/cli/commands/install/agent-runtime.ts +++ b/packages/dotagents/src/cli/commands/install/agent-runtime.ts @@ -64,14 +64,12 @@ export async function writeSubagentRuntime( config: AgentsConfig, scope: ScopeRoot, subagents: SubagentDeclaration[], - frozen?: boolean, ): Promise<{ agent: string; name: string; message: string }[]> { const resolver = scope.scope === "user" ? userSubagentResolver() : projectSubagentResolver(scope.root); const result = await reconcileSubagentConfigs(config.agents, subagents, resolver, { mode: "apply", - prune: !frozen, }); return result.warnings; } diff --git a/packages/dotagents/src/cli/commands/install/gitignore.ts b/packages/dotagents/src/cli/commands/install/gitignore.ts index 9cf8bc4..2677a87 100644 --- a/packages/dotagents/src/cli/commands/install/gitignore.ts +++ b/packages/dotagents/src/cli/commands/install/gitignore.ts @@ -1,6 +1,5 @@ import chalk from "chalk"; import { isWildcardDep, type AgentsConfig } from "../../../config/schema.js"; -import type { Lockfile } from "../../../lockfile/schema.js"; import type { ScopeRoot } from "../../../scope.js"; import { checkRootGitignoreEntries, writeAgentsGitignore } from "../../../gitignore/writer.js"; import { isInPlaceSkill } from "../../../utils/fs.js"; @@ -22,30 +21,18 @@ function managedSkillNames( }); } -function managedSubagentNames( - lockfile: Lockfile | null, - subagents: SubagentDeclaration[], - frozen?: boolean, -): string[] { - return frozen - ? Object.keys(lockfile?.subagents ?? {}) - : subagents.map((subagent) => subagent.name); -} - -/** Regenerates project `.agents/.gitignore` from install results and lockfile state. */ +/** Regenerates project `.agents/.gitignore` from installed canonical artifacts. */ export async function writeInstallGitignore( config: AgentsConfig, - lockfile: Lockfile | null, scope: ScopeRoot, artifacts: InstallGitignoreArtifacts, - frozen?: boolean, ): Promise { if (scope.scope !== "project") {return;} await writeAgentsGitignore( scope.agentsDir, managedSkillNames(config, artifacts.installedSkillNames), - managedSubagentNames(lockfile, artifacts.subagents, frozen), + artifacts.subagents.map((subagent) => subagent.name), ); const missing = await checkRootGitignoreEntries(scope.root); diff --git a/packages/dotagents/src/cli/commands/install/skills.ts b/packages/dotagents/src/cli/commands/install/skills.ts index cca1e75..f7e4937 100644 --- a/packages/dotagents/src/cli/commands/install/skills.ts +++ b/packages/dotagents/src/cli/commands/install/skills.ts @@ -41,8 +41,7 @@ async function expandSkills( trust?: Parameters[1]; defaultRepositorySource: RepositorySource; }, - lockfile: Lockfile | null, - opts: { frozen?: boolean; projectRoot: string; minimumReleaseAge?: number; minimumReleaseAgeExclude?: string[] }, + opts: { projectRoot: string; minimumReleaseAge?: number; minimumReleaseAgeExclude?: string[] }, ): Promise { const regularDeps = config.skills.filter((d) => !isWildcardDep(d)); const wildcardDeps = config.skills.filter(isWildcardDep); @@ -60,18 +59,6 @@ async function expandSkills( config.defaultRepositorySource, ); validateTrustedSource(wildcardSourceForTrust, config.trust); - const excludeSet = new Set(wDep.exclude); - - if (opts.frozen) { - if (!lockfile) {continue;} - for (const [name, locked] of Object.entries(lockfile.skills)) { - if (!sourcesMatch(locked.source, wDep.source)) {continue;} - if (explicitNames.has(name)) {continue;} - if (excludeSet.has(name)) {continue;} - expanded.push({ name, dep: wDep }); - } - continue; - } let named; try { @@ -130,7 +117,6 @@ export async function installSkills( config: AgentsConfig, lockfile: Lockfile | null, scope: ScopeRoot, - frozen?: boolean, ): Promise { const lockEntries: Lockfile["skills"] = {}; const installed: string[] = []; @@ -139,35 +125,19 @@ export async function installSkills( await mkdir(scope.skillsDir, { recursive: true }); if (config.skills.length > 0) { - if (frozen && !lockfile) { - throw new InstallError("--frozen requires agents.lock to exist."); - } - const expanded = await expandSkills( { skills: config.skills, trust: config.trust, defaultRepositorySource: config.defaultRepositorySource, }, - lockfile, { - frozen, projectRoot: scope.root, minimumReleaseAge: config.minimum_release_age, minimumReleaseAgeExclude: config.minimum_release_age_exclude, }, ); - if (frozen) { - for (const { name } of expanded) { - if (!lockfile!.skills[name]) { - throw new InstallError( - `--frozen: skill "${name}" is in agents.toml but missing from agents.lock.`, - ); - } - } - } - for (const item of expanded) { const { name, dep } = item; const sourceForTrust = applyDefaultRepositorySource( @@ -206,7 +176,7 @@ export async function installSkills( } } - if (!frozen && lockfile) { + if (lockfile) { for (const [name, locked] of Object.entries(lockfile.skills)) { if (lockEntries[name]) {continue;} if (isInPlaceSkill(locked.source)) {continue;} diff --git a/packages/dotagents/src/cli/commands/install/subagents.ts b/packages/dotagents/src/cli/commands/install/subagents.ts index 43a3841..a7340ee 100644 --- a/packages/dotagents/src/cli/commands/install/subagents.ts +++ b/packages/dotagents/src/cli/commands/install/subagents.ts @@ -1,12 +1,11 @@ import { join } from "node:path"; -import type { AgentsConfig, SubagentConfig } from "../../../config/schema.js"; +import type { AgentsConfig } from "../../../config/schema.js"; import type { Lockfile } from "../../../lockfile/schema.js"; import type { ScopeRoot } from "../../../scope.js"; import type { SubagentDeclaration } from "../../../subagents/types.js"; import { InstalledSubagentWriteError, lockEntryForSubagent, - loadInstalledSubagents, pruneInstalledSubagents, resolveSubagent, writeInstalledSubagents, @@ -20,47 +19,14 @@ export interface InstallSubagentsResult { lockEntries: Lockfile["subagents"]; } -function validateFrozenSubagents( - subagents: SubagentConfig[], - lockfile: Lockfile | null, -): void { - if (subagents.length === 0) {return;} - if (!lockfile) { - throw new InstallError("--frozen requires agents.lock to exist."); - } - - for (const subagent of subagents) { - if (!lockfile.subagents[subagent.name]) { - throw new InstallError( - `--frozen: subagent "${subagent.name}" is in agents.toml but missing from agents.lock.`, - ); - } - } -} - /** Resolves subagent declarations and lock entries without writing runtime files. */ export async function installSubagents( config: AgentsConfig, - lockfile: Lockfile | null, scope: ScopeRoot, - frozen?: boolean, ): Promise { - const subagentsDir = join(scope.agentsDir, "agents"); const subagents: SubagentDeclaration[] = []; const lockEntries: Lockfile["subagents"] = {}; - if (frozen) { - validateFrozenSubagents(config.subagents, lockfile); - if (config.subagents.length > 0) { - const loaded = await loadInstalledSubagents(subagentsDir, config.subagents); - if (loaded.issues.length > 0) { - throw new InstallError(loaded.issues.map((issue) => issue.issue).join("\n")); - } - subagents.push(...loaded.subagents); - } - return { subagents, lockEntries }; - } - for (const subagentConfig of config.subagents) { let resolved: Awaited>; try { @@ -90,9 +56,7 @@ export async function writeCanonicalSubagents( config: AgentsConfig, scope: ScopeRoot, subagents: SubagentDeclaration[], - frozen?: boolean, ): Promise { - if (frozen) {return;} const subagentsDir = join(scope.agentsDir, "agents"); try { await writeInstalledSubagents(subagentsDir, subagents); diff --git a/specs/SPEC.md b/specs/SPEC.md index 13d45cf..7dc6b9b 100644 --- a/specs/SPEC.md +++ b/specs/SPEC.md @@ -222,7 +222,7 @@ Review the current diff and return findings with file references. dotagents intentionally does not standardize runtime-specific subagent behavior such as model routing, tool permissions, read-only modes, background execution, or reasoning effort. Those controls differ across runtimes and should stay in each tool's native config until there is a maintainable common contract. -Installed and generated files are marked as dotagents-managed with a generated header marker. `install` and `sync` overwrite stale managed files and prune removed managed files, but they do not overwrite hand-written files without the generated header marker. In `--frozen` mode, `install` loads subagents from existing installed files, preserves managed subagent files and lock entries instead of pruning removed subagents, and does not resolve subagent sources. +Installed and generated files are marked as dotagents-managed with a generated header marker. `install` and `sync` overwrite stale managed files and prune removed managed files, but they do not overwrite hand-written files without the generated header marker. The deprecated `--frozen` option is accepted as a warned compatibility no-op and does not change subagent installation or pruning. Generated paths: @@ -450,7 +450,6 @@ dotagents install b. Discover skill within the repo c. Copy skill directory into `.agents/skills//` 3. Write `agents.lock` with the current configured skills and subagents - - In `--frozen` mode, require configured dependencies to already be present in `agents.lock`, load subagents from installed files, do not update the lockfile, and do not prune existing managed subagent files 4. Regenerate `.agents/.gitignore` 5. Warn if `agents.lock` and `.agents/.gitignore` are not in the root `.gitignore` 6. Create/verify symlinks (legacy `[symlinks]` and agent-specific) @@ -459,6 +458,8 @@ dotagents install 9. Write generated subagent files for each declared agent that supports custom subagents 10. Print summary +The deprecated `--frozen` option is accepted for compatibility, prints a warning, and follows this normal install flow. It is ignored and will be removed in the next major release. Use explicit `ref` values to pin sources. + ### `dotagents add ` Add one or more skill dependencies. diff --git a/specs/subagents.md b/specs/subagents.md index d06f0fd..9e30ea1 100644 --- a/specs/subagents.md +++ b/specs/subagents.md @@ -111,7 +111,7 @@ Generated runtime paths: | Codex | `.codex/agents/.toml` | `~/.codex/agents/.toml` | TOML | | OpenCode | `.opencode/agents/.md` | `~/.config/opencode/agents/.md` | Markdown with YAML frontmatter | -Installed and generated files are marked as dotagents-managed with a generated header marker. `install` and `sync` overwrite stale managed files and prune removed managed files, but they do not overwrite hand-written files without the generated header marker. In `--frozen` mode, `install` loads subagents from existing installed files, preserves managed subagent files and lock entries instead of pruning removed subagents, and does not resolve subagent sources. For runtimes whose agent identity can differ from the filename, dotagents also avoids writing a managed file when an unmanaged file in the same runtime directory already declares the same runtime identity. +Installed and generated files are marked as dotagents-managed with a generated header marker. `install` and `sync` overwrite stale managed files and prune removed managed files, but they do not overwrite hand-written files without the generated header marker. The deprecated `--frozen` option is a warned compatibility no-op and does not change this behavior. For runtimes whose agent identity can differ from the filename, dotagents also avoids writing a managed file when an unmanaged file in the same runtime directory already declares the same runtime identity. ## Non-goals