claude-code: migrate memory to context & skills{,Dir} to skills
This commit is contained in:
parent
ffa740c0df
commit
13df3b4e44
8 changed files with 152 additions and 83 deletions
|
|
@ -7,6 +7,7 @@
|
|||
let
|
||||
inherit (lib)
|
||||
literalExpression
|
||||
mkChangedOptionModule
|
||||
mkOption
|
||||
nameValuePair
|
||||
optionalAttrs
|
||||
|
|
@ -55,6 +56,24 @@ in
|
|||
{
|
||||
meta.maintainers = [ lib.maintainers.khaneliman ];
|
||||
|
||||
imports = [
|
||||
(mkChangedOptionModule
|
||||
[ "programs" "claude-code" "memory" "text" ]
|
||||
[ "programs" "claude-code" "context" ]
|
||||
(config: lib.getAttrFromPath [ "programs" "claude-code" "memory" "text" ] config)
|
||||
)
|
||||
(mkChangedOptionModule
|
||||
[ "programs" "claude-code" "memory" "source" ]
|
||||
[ "programs" "claude-code" "context" ]
|
||||
(config: lib.getAttrFromPath [ "programs" "claude-code" "memory" "source" ] config)
|
||||
)
|
||||
(mkChangedOptionModule
|
||||
[ "programs" "claude-code" "skillsDir" ]
|
||||
[ "programs" "claude-code" "skills" ]
|
||||
(config: lib.getAttrFromPath [ "programs" "claude-code" "skillsDir" ] config)
|
||||
)
|
||||
];
|
||||
|
||||
options.programs.claude-code = {
|
||||
enable = lib.mkEnableOption "Claude Code, Anthropic's official CLI";
|
||||
|
||||
|
|
@ -137,6 +156,22 @@ in
|
|||
description = "JSON configuration for Claude Code settings.json";
|
||||
};
|
||||
|
||||
context = mkOption {
|
||||
type = lib.types.either lib.types.lines lib.types.path;
|
||||
default = "";
|
||||
description = ''
|
||||
Global context for Claude Code.
|
||||
|
||||
The value is either:
|
||||
- Inline content as a string
|
||||
- A path to a file containing the content
|
||||
|
||||
The configured content is written to
|
||||
{file}`~/.claude/CLAUDE.md`.
|
||||
'';
|
||||
example = literalExpression "./claude-memory.md";
|
||||
};
|
||||
|
||||
plugins = lib.mkOption {
|
||||
type = with lib.types; listOf (either package path);
|
||||
default = [ ];
|
||||
|
|
@ -266,36 +301,6 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
memory = {
|
||||
text = mkOption {
|
||||
type = lib.types.nullOr lib.types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Inline memory content for CLAUDE.md.
|
||||
This option is mutually exclusive with memory.source.
|
||||
'';
|
||||
example = ''
|
||||
# Project Memory
|
||||
|
||||
## Current Task
|
||||
Implementing enhanced claude-code module for home-manager.
|
||||
|
||||
## Key Files
|
||||
- claude-code.nix: Main module implementation
|
||||
'';
|
||||
};
|
||||
|
||||
source = mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to a file containing memory content for CLAUDE.md.
|
||||
This option is mutually exclusive with memory.text.
|
||||
'';
|
||||
example = literalExpression "./claude-memory.md";
|
||||
};
|
||||
};
|
||||
|
||||
rules = mkContentOption {
|
||||
description = ''
|
||||
Modular rule files for Claude Code.
|
||||
|
|
@ -378,13 +383,34 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
skills = mkContentOption {
|
||||
skills = mkOption {
|
||||
type = lib.types.either (lib.types.attrsOf (
|
||||
lib.types.oneOf [
|
||||
lib.types.lines
|
||||
lib.types.path
|
||||
lib.types.str
|
||||
]
|
||||
)) lib.types.path;
|
||||
default = { };
|
||||
description = ''
|
||||
Custom skills for Claude Code.
|
||||
The attribute name becomes the skill directory name, and the value is either:
|
||||
|
||||
This option can be either:
|
||||
- An attribute set defining skills
|
||||
- A path to a directory containing skill folders
|
||||
|
||||
If an attribute set is used, the attribute name becomes the
|
||||
skill directory name, and the value is either:
|
||||
- Inline content as a string (creates .claude/skills/<name>/SKILL.md)
|
||||
- A path to a file (creates .claude/skills/<name>/SKILL.md)
|
||||
- A path to a directory (creates .claude/skills/<name>/ with all files)
|
||||
|
||||
This also accepts Nix store paths, for example a skill directory
|
||||
from a package.
|
||||
|
||||
If a path is used, it is expected to contain one folder per
|
||||
skill name, each containing a {file}`SKILL.md`. The directory is
|
||||
symlinked to {file}`~/.claude/skills/`.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
|
|
@ -409,19 +435,13 @@ in
|
|||
text = pdf.pages[0].extract_text()
|
||||
```
|
||||
''';
|
||||
|
||||
# A skill can also be a subdirectory within a package source (store path)
|
||||
beads = "''${pkgs.beads.src}/claude-plugin/skills/beads";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
skillsDir = mkDirOption {
|
||||
description = ''
|
||||
Path to a directory containing skill directories for Claude Code.
|
||||
Each skill directory should contain a SKILL.md entrypoint file.
|
||||
Skill directories from this path will be symlinked to .claude/skills/.
|
||||
'';
|
||||
example = literalExpression "./skills";
|
||||
};
|
||||
|
||||
lspServers = mkOption {
|
||||
type = lib.types.attrsOf jsonFormat.type;
|
||||
default = { };
|
||||
|
|
@ -494,6 +514,9 @@ in
|
|||
let
|
||||
mkSourceEntry = content: if lib.isPath content then { source = content; } else { text = content; };
|
||||
|
||||
isStorePathString = content: builtins.isString content && lib.hasPrefix builtins.storeDir content;
|
||||
isPathLikeContent = content: lib.isPath content || isStorePathString content;
|
||||
|
||||
mkMarkdownEntries =
|
||||
subdir: attrs:
|
||||
lib.mapAttrs' (
|
||||
|
|
@ -515,13 +538,15 @@ in
|
|||
|
||||
mkSkillEntry =
|
||||
name: content:
|
||||
if lib.isPath content && lib.pathIsDirectory content then
|
||||
if isPathLikeContent content && lib.pathIsDirectory content then
|
||||
nameValuePair ".claude/skills/${name}" {
|
||||
source = content;
|
||||
recursive = true;
|
||||
}
|
||||
else
|
||||
nameValuePair ".claude/skills/${name}/SKILL.md" (mkSourceEntry content);
|
||||
nameValuePair ".claude/skills/${name}/SKILL.md" (
|
||||
if isPathLikeContent content then { source = content; } else { text = content; }
|
||||
);
|
||||
|
||||
mkMarketplaceEntry = name: content: {
|
||||
source = {
|
||||
|
|
@ -547,7 +572,6 @@ in
|
|||
"agents"
|
||||
"commands"
|
||||
"hooks"
|
||||
"skills"
|
||||
];
|
||||
|
||||
mkExclusiveAssertion = inline: {
|
||||
|
|
@ -563,8 +587,8 @@ in
|
|||
message = "`programs.claude-code.package` cannot be null when `mcpServers`, `lspServers`, `enableMcpIntegration`, or `plugins` is configured";
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.memory.text != null && cfg.memory.source != null);
|
||||
message = "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`";
|
||||
assertion = !lib.isPath cfg.skills || lib.pathIsDirectory cfg.skills;
|
||||
message = "`programs.claude-code.skills` must be a directory when set to a path";
|
||||
}
|
||||
]
|
||||
++ map mkExclusiveAssertion exclusiveInlineDirNames;
|
||||
|
|
@ -633,12 +657,16 @@ in
|
|||
}
|
||||
);
|
||||
})
|
||||
(lib.mkIf (cfg.memory.text != null) {
|
||||
".claude/CLAUDE.md".text = cfg.memory.text;
|
||||
})
|
||||
(lib.mkIf (cfg.memory.source != null) {
|
||||
".claude/CLAUDE.md".source = cfg.memory.source;
|
||||
})
|
||||
(
|
||||
if lib.isPath cfg.context then
|
||||
{
|
||||
".claude/CLAUDE.md".source = cfg.context;
|
||||
}
|
||||
else
|
||||
(lib.mkIf (cfg.context != "") {
|
||||
".claude/CLAUDE.md".text = cfg.context;
|
||||
})
|
||||
)
|
||||
(lib.mkIf (cfg.marketplaces != { }) {
|
||||
".claude/plugins/known_marketplaces.json".source =
|
||||
jsonFormat.generate "claude-code-known-marketplaces.json" (
|
||||
|
|
@ -652,9 +680,14 @@ in
|
|||
(mkRecursiveDirAttrs "commands" cfg.commandsDir)
|
||||
(mkRecursiveDirAttrs "hooks" cfg.hooksDir)
|
||||
(mkRecursiveDirAttrs "rules" cfg.rulesDir)
|
||||
(mkRecursiveDirAttrs "skills" cfg.skillsDir)
|
||||
(lib.mkIf (lib.isPath cfg.skills) {
|
||||
".claude/skills" = {
|
||||
source = cfg.skills;
|
||||
recursive = true;
|
||||
};
|
||||
})
|
||||
(mkTextEntries "hooks" cfg.hooks)
|
||||
(lib.mapAttrs' mkSkillEntry cfg.skills)
|
||||
(lib.optionalAttrs (builtins.isAttrs cfg.skills) (lib.mapAttrs' mkSkillEntry cfg.skills))
|
||||
(mkMarkdownEntries "output-styles" cfg.outputStyles)
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,12 +15,6 @@
|
|||
};
|
||||
};
|
||||
|
||||
# assert fail: cannot set text and source at the same time.
|
||||
memory = {
|
||||
text = "Some text content";
|
||||
source = ./expected-memory.md;
|
||||
};
|
||||
|
||||
# assert fail: cannot set agents and agentsDir at the same time.
|
||||
agents = {
|
||||
test-agent = "test content";
|
||||
|
|
@ -39,19 +33,12 @@
|
|||
};
|
||||
hooksDir = ./hooks;
|
||||
|
||||
# assert fail: cannot set skills and skillsDir at the same time.
|
||||
skills = {
|
||||
test-skill = "test content";
|
||||
};
|
||||
skillsDir = ./skills;
|
||||
};
|
||||
|
||||
test.asserts.assertions.expected = [
|
||||
"`programs.claude-code.package` cannot be null when `mcpServers`, `lspServers`, `enableMcpIntegration`, or `plugins` is configured"
|
||||
"Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`"
|
||||
"Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`"
|
||||
"Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`"
|
||||
"Cannot specify both `programs.claude-code.hooks` and `programs.claude-code.hooksDir`"
|
||||
"Cannot specify both `programs.claude-code.skills` and `programs.claude-code.skillsDir`"
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
claude-code-mcp-lsp = ./mcp-lsp.nix;
|
||||
claude-code-mcp-integration = ./mcp-integration.nix;
|
||||
claude-code-assertion = ./assertion.nix;
|
||||
claude-code-memory-management = ./memory-management.nix;
|
||||
claude-code-memory-from-source = ./memory-from-source.nix;
|
||||
claude-code-context-inline = ./memory-management.nix;
|
||||
claude-code-context-path = ./memory-from-source.nix;
|
||||
claude-code-rules-dir = ./rules-dir.nix;
|
||||
claude-code-rules-path = ./rules-path.nix;
|
||||
claude-code-agents-dir = ./agents-dir.nix;
|
||||
|
|
@ -18,6 +18,8 @@
|
|||
claude-code-agents-path = ./agents-path.nix;
|
||||
claude-code-commands-path = ./commands-path.nix;
|
||||
claude-code-skills-path = ./skills-path.nix;
|
||||
claude-code-legacy-memory-text = ./legacy-memory-text.nix;
|
||||
claude-code-legacy-memory-source-and-skills-dir = ./legacy-memory-source-and-skills-dir.nix;
|
||||
claude-code-mixed-content = ./mixed-content.nix;
|
||||
claude-code-output-styles = ./output-styles.nix;
|
||||
claude-code-output-styles-not-set = ./output-styles-not-set.nix;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
{ lib, options, ... }:
|
||||
|
||||
{
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
memory.source = ./expected-memory.md;
|
||||
skillsDir = ./skills;
|
||||
};
|
||||
|
||||
test.asserts.warnings.expected = [
|
||||
"The option `programs.claude-code.skillsDir' defined in ${lib.showFiles options.programs.claude-code.skillsDir.files} has been changed to `programs.claude-code.skills' that has a different type. Please read `programs.claude-code.skills' documentation and update your configuration accordingly."
|
||||
"The option `programs.claude-code.memory.source' defined in ${lib.showFiles options.programs.claude-code.memory.source.files} has been changed to `programs.claude-code.context' that has a different type. Please read `programs.claude-code.context' documentation and update your configuration accordingly."
|
||||
];
|
||||
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.claude/CLAUDE.md
|
||||
assertFileContent home-files/.claude/CLAUDE.md ${./expected-memory.md}
|
||||
|
||||
assertFileExists home-files/.claude/skills/test-skill/SKILL.md
|
||||
assertLinkExists home-files/.claude/skills/test-skill/SKILL.md
|
||||
assertFileContent \
|
||||
home-files/.claude/skills/test-skill/SKILL.md \
|
||||
${./skills/test-skill/SKILL.md}
|
||||
'';
|
||||
}
|
||||
26
tests/modules/programs/claude-code/legacy-memory-text.nix
Normal file
26
tests/modules/programs/claude-code/legacy-memory-text.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{ lib, options, ... }:
|
||||
|
||||
{
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
memory.text = ''
|
||||
# Project Memory
|
||||
|
||||
This configuration still uses the legacy memory.text option.
|
||||
'';
|
||||
};
|
||||
|
||||
test.asserts.warnings.expected = [
|
||||
"The option `programs.claude-code.memory.text' defined in ${lib.showFiles options.programs.claude-code.memory.text.files} has been changed to `programs.claude-code.context' that has a different type. Please read `programs.claude-code.context' documentation and update your configuration accordingly."
|
||||
];
|
||||
|
||||
nmt.script = ''
|
||||
assertFileExists home-files/.claude/CLAUDE.md
|
||||
assertFileContent home-files/.claude/CLAUDE.md \
|
||||
${builtins.toFile "expected-legacy-memory-text.md" ''
|
||||
# Project Memory
|
||||
|
||||
This configuration still uses the legacy memory.text option.
|
||||
''}
|
||||
'';
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
{
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
memory = {
|
||||
source = ./expected-memory.md;
|
||||
};
|
||||
context = ./expected-memory.md;
|
||||
};
|
||||
|
||||
nmt.script = ''
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
{
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
memory = {
|
||||
text = ''
|
||||
# Project Memory
|
||||
context = ''
|
||||
# Project Memory
|
||||
|
||||
## Current Task
|
||||
Test implementation of memory management.
|
||||
## Current Task
|
||||
Test implementation of memory management.
|
||||
|
||||
## Key Context
|
||||
- This is a test configuration
|
||||
- Memory should be created at ~/.claude/CLAUDE.md
|
||||
'';
|
||||
};
|
||||
## Key Context
|
||||
- This is a test configuration
|
||||
- Memory should be created at ~/.claude/CLAUDE.md
|
||||
'';
|
||||
};
|
||||
|
||||
nmt.script = ''
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
skillsDir = ./skills;
|
||||
skills = ./skills;
|
||||
};
|
||||
|
||||
nmt.script = ''
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue