@dsh-cc/claude-code-agents 0.5.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/lib/index.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Load Claude Code's `.claude/agents` sub-agent definitions as dsh agent
3
+ * presets: a pure, filesystem-backed translation that discovers the user and
4
+ * project layers, parses and validates every `.md`/`.json` agent file, and
5
+ * returns one {@link AgentDefinition} per agent — ready to be mounted by a
6
+ * preset row or subagent driver.
7
+ *
8
+ * The loader is deliberately integration-free on purpose it is CLI-domain pure:
9
+ * it produces typed definitions and leaves their consumption (scoped tool
10
+ * restriction, request rewriting, permission selection) to the caller, so the
11
+ * model-facing parts can be reused by a Claude Code plugin loader elsewhere
12
+ * without dragging in the harness runtime.
13
+ *
14
+ * @module @dsh-cc/claude-code-agents
15
+ */
16
+ import type { AgentDefinition } from './types.ts';
17
+ /** Options controlling where the loader discovers agent definitions from. */
18
+ export interface LoadOptions {
19
+ /**
20
+ * The user `.claude/agents` directory. Defaults to the OS home's
21
+ * `.claude/agents`; passing your own makes the loader hermetic in tests and
22
+ * lets a harness with a non-default home point it at the right layer.
23
+ */
24
+ readonly userDir?: string;
25
+ }
26
+ /**
27
+ * Load every Claude Code agent definition visible from a project root.
28
+ *
29
+ * The project layer is the nearest `.claude/agents` directory found by walking
30
+ * up from `root`; it shadows the user layer on a name collision. A discovered
31
+ * agent file that fails to parse throws with its path, so a broken agent is
32
+ * reported, not skipped.
33
+ * @param root - the project directory the project layer resolves from.
34
+ * @param options - explicit user-layer override.
35
+ * @returns the merged agent definitions, project shadowing user.
36
+ * @throws when a discovered agent file cannot be read or parsed.
37
+ */
38
+ export declare function loadClaudeCodeAgents(root: string, options?: LoadOptions): Promise<AgentDefinition[]>;
39
+ export * from './types.ts';
40
+ export { discoverBundledAgents } from './bundled/index.ts';
41
+ export { loadAgentsDir, findProjectAgentsDir, discoverAgents, AGENTS_DIR, CLAUDE_DIR } from './discovery.ts';
42
+ export { parseAgentMarkdown, parseAgentJson, splitFrontmatter } from './parse.ts';
43
+ export type { ParsedMarkdown } from './parse.ts';
44
+ export { resolveToolRestriction, normalizeModel } from './restrict.ts';
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,eAAe,EAAE,CAAC,CAG5B;AAED,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACjF,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Load Claude Code's `.claude/agents` sub-agent definitions as dsh agent
3
+ * presets: a pure, filesystem-backed translation that discovers the user and
4
+ * project layers, parses and validates every `.md`/`.json` agent file, and
5
+ * returns one {@link AgentDefinition} per agent — ready to be mounted by a
6
+ * preset row or subagent driver.
7
+ *
8
+ * The loader is deliberately integration-free on purpose it is CLI-domain pure:
9
+ * it produces typed definitions and leaves their consumption (scoped tool
10
+ * restriction, request rewriting, permission selection) to the caller, so the
11
+ * model-facing parts can be reused by a Claude Code plugin loader elsewhere
12
+ * without dragging in the harness runtime.
13
+ *
14
+ * @module @dsh-cc/claude-code-agents
15
+ */
16
+ import { homedir } from 'node:os';
17
+ import { join } from 'node:path';
18
+ import { discoverAgents } from "./discovery.js";
19
+ /**
20
+ * Load every Claude Code agent definition visible from a project root.
21
+ *
22
+ * The project layer is the nearest `.claude/agents` directory found by walking
23
+ * up from `root`; it shadows the user layer on a name collision. A discovered
24
+ * agent file that fails to parse throws with its path, so a broken agent is
25
+ * reported, not skipped.
26
+ * @param root - the project directory the project layer resolves from.
27
+ * @param options - explicit user-layer override.
28
+ * @returns the merged agent definitions, project shadowing user.
29
+ * @throws when a discovered agent file cannot be read or parsed.
30
+ */
31
+ export async function loadClaudeCodeAgents(root, options = {}) {
32
+ const userDir = options.userDir ?? join(homedir(), '.claude', 'agents');
33
+ return await discoverAgents(root, userDir);
34
+ }
35
+ export * from "./types.js";
36
+ export { discoverBundledAgents } from "./bundled/index.js";
37
+ export { loadAgentsDir, findProjectAgentsDir, discoverAgents, AGENTS_DIR, CLAUDE_DIR } from "./discovery.js";
38
+ export { parseAgentMarkdown, parseAgentJson, splitFrontmatter } from "./parse.js";
39
+ export { resolveToolRestriction, normalizeModel } from "./restrict.js";
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAa/C;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAY,EACZ,UAAuB,EAAE;IAEzB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;IACvE,OAAO,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC5C,CAAC;AAED,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAEjF,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@dsh-cc/claude-code-agents`.
3
+ * @module @dsh-cc/claude-code-agents/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "claude-code-agents-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant.d.ts","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,oCAAoC;AACpC,eAAO,MAAM,IAAI,iCAAiC,CAAA;AAClD,2EAA2E;AAC3E,eAAO,MAAM,MAAM,UAAiB,CAAA;AAQpC;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAG,OAAO,CAAC,MAAM,IAAI,CACU,CAAA"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Package-owned invariant companion for `@dsh-cc/claude-code-agents`.
3
+ * @module @dsh-cc/claude-code-agents/invariant
4
+ */
5
+ const PACKAGE_NAME = '@dsh-cc/claude-code-agents';
6
+ /** Cordis companion plugin name. */
7
+ export const name = 'claude-code-agents-invariant';
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export const inject = ['invariants'];
10
+ /**
11
+ * No runtime invariant: the loader is a pure filesystem translation owning no
12
+ * session or event stream; parse/discovery/restrict unit tests cover it.
13
+ */
14
+ const install = () => { };
15
+ /**
16
+ * Register this package's invariant companion.
17
+ * @param ctx - Cordis context carrying the invariant service.
18
+ * @returns the installed registration's disposer after setup succeeds.
19
+ */
20
+ export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
21
+ /* jscpd:ignore-end */
22
+ //# sourceMappingURL=invariant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant.js","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,YAAY,GAAG,4BAA4B,CAAA;AAEjD,oCAAoC;AACpC,MAAM,CAAC,MAAM,IAAI,GAAG,8BAA8B,CAAA;AAClD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAY,CAAC,CAAA;AAEpC;;;GAGG;AACH,MAAM,OAAO,GAAuB,GAAG,EAAE,GAAE,CAAC,CAAA;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAY,EAAuB,EAAE,CACzD,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;AACjE,sBAAsB"}
package/lib/parse.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Parse one Claude Code agent file (`.md` frontmatter + markdown body, or
3
+ * `.json`) into an {@link AgentDefinition}. Every bad known-frontmatter value
4
+ * throws at load time with the file path and the field name, so a broken agent
5
+ * surfaces loud instead of silently degrading; unknown fields are ignored.
6
+ *
7
+ * Discovery reads the file by name, so this module parses from an in-memory
8
+ * string: it stays synchronous, dependency-light, and unit-testable, and the
9
+ * caller owns reading the bytes.
10
+ *
11
+ * @module @dsh-cc/claude-code-agents/parse
12
+ */
13
+ import type { AgentDefinition, AgentSource } from './types.ts';
14
+ /** A `frontmatter` split out of a raw markdown string. */
15
+ export interface ParsedMarkdown {
16
+ /** The YAML metadata block, decoded; empty when the file had none. */
17
+ readonly frontmatter: Readonly<Record<string, unknown>>;
18
+ /** The markdown body after the closing `---`, without leading blank lines. */
19
+ readonly content: string;
20
+ }
21
+ /**
22
+ * Split the leading YAML frontmatter block out of a markdown string.
23
+ * A `---`-delimited block at the very start is metadata; anything else (no
24
+ * leading delimiter, or no closing one) means the whole text is body.
25
+ * @param text - the file's full markdown text.
26
+ * @returns the frontmatter record and the trailing body.
27
+ * @throws when the leading block is delimited but not valid YAML.
28
+ */
29
+ export declare function splitFrontmatter(text: string): ParsedMarkdown;
30
+ /**
31
+ * Parse one `.md` agent: the frontmatter supplies the fields and the markdown
32
+ * body (or the `prompt` override) supplies the system prompt.
33
+ * @param filePath - the agent file path; its basename becomes `agentType`.
34
+ * @param text - the full markdown text.
35
+ * @param source - the layer the file was discovered under.
36
+ * @returns the parsed, validated agent definition.
37
+ * @throws when required fields are missing or a known field has a bad value.
38
+ */
39
+ export declare function parseAgentMarkdown(filePath: string, text: string, source: AgentSource): AgentDefinition;
40
+ /**
41
+ * Parse one `.json` agent: a single object whose keys are frontmatter fields
42
+ * and whose optional `prompt` supplies the system prompt.
43
+ * @param filePath - the agent file path; its basename becomes `agentType`.
44
+ * @param text - the raw JSON text.
45
+ * @param source - the layer the file was discovered under.
46
+ * @returns the parsed, validated agent definition.
47
+ * @throws when JSON is malformed, required fields are missing, or a known field
48
+ * has a bad value.
49
+ */
50
+ export declare function parseAgentJson(filePath: string, text: string, source: AgentSource): AgentDefinition;
51
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EAEZ,MAAM,YAAY,CAAA;AAEnB,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACvD,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAiB7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,eAAe,CAIvG;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,eAAe,CAcnG"}
package/lib/parse.js ADDED
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Parse one Claude Code agent file (`.md` frontmatter + markdown body, or
3
+ * `.json`) into an {@link AgentDefinition}. Every bad known-frontmatter value
4
+ * throws at load time with the file path and the field name, so a broken agent
5
+ * surfaces loud instead of silently degrading; unknown fields are ignored.
6
+ *
7
+ * Discovery reads the file by name, so this module parses from an in-memory
8
+ * string: it stays synchronous, dependency-light, and unit-testable, and the
9
+ * caller owns reading the bytes.
10
+ *
11
+ * @module @dsh-cc/claude-code-agents/parse
12
+ */
13
+ import { load as loadYaml } from 'js-yaml';
14
+ import { basename, dirname, extname } from 'node:path';
15
+ import { normalizeModel, resolveToolRestriction } from "./restrict.js";
16
+ import { EFFORT_LEVELS, ISOLATION_MODES, MEMORY_SCOPES, PERMISSION_MODES, } from "./types.js";
17
+ /**
18
+ * Split the leading YAML frontmatter block out of a markdown string.
19
+ * A `---`-delimited block at the very start is metadata; anything else (no
20
+ * leading delimiter, or no closing one) means the whole text is body.
21
+ * @param text - the file's full markdown text.
22
+ * @returns the frontmatter record and the trailing body.
23
+ * @throws when the leading block is delimited but not valid YAML.
24
+ */
25
+ export function splitFrontmatter(text) {
26
+ if (!text.startsWith('---'))
27
+ return { frontmatter: {}, content: text.trim() };
28
+ const end = text.indexOf('\n---', 3);
29
+ if (end === -1)
30
+ return { frontmatter: {}, content: text.trim() };
31
+ const raw = text.slice(3, end);
32
+ const content = text.slice(end + 4).replace(/^\s*\n/, '').trim();
33
+ let frontmatter;
34
+ try {
35
+ frontmatter = loadYaml(raw);
36
+ }
37
+ catch (error) {
38
+ throw new Error(`invalid YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`);
39
+ }
40
+ if (frontmatter === undefined)
41
+ return { frontmatter: {}, content };
42
+ if (typeof frontmatter !== 'object' || Array.isArray(frontmatter) || frontmatter === null) {
43
+ throw new Error('frontmatter must be a YAML object, not a scalar or list');
44
+ }
45
+ return { frontmatter: frontmatter, content };
46
+ }
47
+ /**
48
+ * Parse one `.md` agent: the frontmatter supplies the fields and the markdown
49
+ * body (or the `prompt` override) supplies the system prompt.
50
+ * @param filePath - the agent file path; its basename becomes `agentType`.
51
+ * @param text - the full markdown text.
52
+ * @param source - the layer the file was discovered under.
53
+ * @returns the parsed, validated agent definition.
54
+ * @throws when required fields are missing or a known field has a bad value.
55
+ */
56
+ export function parseAgentMarkdown(filePath, text, source) {
57
+ const agentType = basename(filePath, '.md');
58
+ const { frontmatter, content } = splitFrontmatter(text);
59
+ return buildAgent(filePath, agentType, frontmatter, content, source);
60
+ }
61
+ /**
62
+ * Parse one `.json` agent: a single object whose keys are frontmatter fields
63
+ * and whose optional `prompt` supplies the system prompt.
64
+ * @param filePath - the agent file path; its basename becomes `agentType`.
65
+ * @param text - the raw JSON text.
66
+ * @param source - the layer the file was discovered under.
67
+ * @returns the parsed, validated agent definition.
68
+ * @throws when JSON is malformed, required fields are missing, or a known field
69
+ * has a bad value.
70
+ */
71
+ export function parseAgentJson(filePath, text, source) {
72
+ const agentType = basename(filePath, '.json');
73
+ let decoded;
74
+ try {
75
+ decoded = JSON.parse(text);
76
+ }
77
+ catch (error) {
78
+ throw new Error(`${filePath}: invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
79
+ }
80
+ if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) {
81
+ throw new Error(`${filePath}: agent JSON must be an object`);
82
+ }
83
+ const frontmatter = decoded;
84
+ const content = requireString(filePath, 'prompt', frontmatter['prompt']);
85
+ return buildAgent(filePath, agentType, frontmatter, content, source, true);
86
+ }
87
+ /**
88
+ * Validate required metadata and translate the split frontmatter fields into
89
+ * one {@link AgentDefinition}. The `prompt` key is consumed as the prompt
90
+ * override for `.md` and forwarded through for `.json` via `fromJson`.
91
+ * @param filePath - origin path for errors and the fallback agent name.
92
+ * @param agentType - the resolved agent type name.
93
+ * @param frontmatter - the decoded frontmatter record.
94
+ * @param promptDefault - the markdown body (or the JSON `prompt`, pre-resolved).
95
+ * @param source - the discovery layer.
96
+ * @param promptIsOverride - whether `prompt` was taken from frontmatter.
97
+ * @returns the validated agent definition.
98
+ * @throws on missing description, missing prompt, or any bad known field.
99
+ */
100
+ function buildAgent(filePath, agentType, frontmatter, promptDefault, source, promptIsOverride = false) {
101
+ const whenToUse = requireString(filePath, 'description', frontmatter['description']);
102
+ const display = frontmatter['name'];
103
+ if (display !== undefined && typeof display !== 'string') {
104
+ throw new Error(`${filePath}: name must be a string`);
105
+ }
106
+ const tools = optionalToolList(filePath, 'tools', frontmatter['tools']);
107
+ const disallowedTools = optionalToolList(filePath, 'disallowedTools', frontmatter['disallowedTools']);
108
+ const toolRestriction = resolveToolRestriction(tools, disallowedTools);
109
+ const model = normalizeModel(frontmatter['model']);
110
+ const effort = parseEffort(filePath, frontmatter['effort']);
111
+ const permissionMode = parseEnum(filePath, 'permissionMode', frontmatter['permissionMode'], PERMISSION_MODES);
112
+ const maxTurns = parsePositiveInt(filePath, 'maxTurns', frontmatter['maxTurns']);
113
+ const memory = parseEnum(filePath, 'memory', frontmatter['memory'], MEMORY_SCOPES);
114
+ const isolation = parseEnum(filePath, 'isolation', frontmatter['isolation'], ISOLATION_MODES);
115
+ const prompt = promptIsOverride ? undefined : optionalString(filePath, 'prompt', frontmatter['prompt']);
116
+ const initialPrompt = optionalString(filePath, 'initialPrompt', frontmatter['initialPrompt']);
117
+ const background = optionalBoolean(filePath, 'background', frontmatter['background']);
118
+ const systemPrompt = prompt ?? promptDefault;
119
+ const definition = {
120
+ agentType,
121
+ whenToUse,
122
+ systemPrompt,
123
+ source,
124
+ baseDir: dirname(filePath),
125
+ filename: basename(filePath, extname(filePath)),
126
+ ...toolRestriction !== undefined ? { toolRestriction } : {},
127
+ };
128
+ addOptional(definition, 'skills', optionalStringArray(filePath, 'skills', frontmatter['skills']));
129
+ addOptional(definition, 'mcpServers', optionalStringArray(filePath, 'mcpServers', frontmatter['mcpServers']));
130
+ addOptional(definition, 'hooks', optionalRecordArray(filePath, 'hooks', frontmatter['hooks']));
131
+ addOptional(definition, 'model', model);
132
+ addOptional(definition, 'effort', effort);
133
+ addOptional(definition, 'permissionMode', permissionMode);
134
+ addOptional(definition, 'maxTurns', maxTurns);
135
+ addOptional(definition, 'initialPrompt', initialPrompt);
136
+ addOptional(definition, 'background', background);
137
+ addOptional(definition, 'memory', memory);
138
+ addOptional(definition, 'isolation', isolation);
139
+ return definition;
140
+ }
141
+ /** Assign `value` onto `target[key]` unless it is undefined. */
142
+ function addOptional(target, key, value) {
143
+ if (value !== undefined)
144
+ target[key] = value;
145
+ }
146
+ // ── field readers ───────────────────────────────────────────────────────────
147
+ /** Read a required string field, throwing with the file path when absent. */
148
+ function requireString(filePath, key, value) {
149
+ if (typeof value !== 'string' || value.trim().length === 0) {
150
+ throw new Error(`${filePath}: missing required "${key}" (a non-empty string)`);
151
+ }
152
+ return value;
153
+ }
154
+ /** Read an optional string field. */
155
+ function optionalString(filePath, key, value) {
156
+ if (value === undefined)
157
+ return undefined;
158
+ if (typeof value !== 'string' || value.trim().length === 0) {
159
+ throw new Error(`${filePath}: ${key} must be a non-empty string`);
160
+ }
161
+ return value;
162
+ }
163
+ /** Read an optional boolean field. */
164
+ function optionalBoolean(filePath, key, value) {
165
+ if (value === undefined)
166
+ return undefined;
167
+ if (typeof value !== 'boolean') {
168
+ throw new Error(`${filePath}: ${key} must be a boolean`);
169
+ }
170
+ return value;
171
+ }
172
+ /** Read an optional array of strings. */
173
+ function optionalStringArray(filePath, key, value) {
174
+ if (value === undefined)
175
+ return undefined;
176
+ if (!Array.isArray(value)) {
177
+ throw new Error(`${filePath}: ${key} must be an array of strings`);
178
+ }
179
+ for (const item of value) {
180
+ if (typeof item !== 'string') {
181
+ throw new Error(`${filePath}: ${key} must name strings, got ${String(item)}`);
182
+ }
183
+ }
184
+ return value;
185
+ }
186
+ /**
187
+ * Read an optional tool list: a comma-separated string (Claude Code's
188
+ * frontmatter shorthand, e.g. `tools: Bash, Read`) or an array of strings.
189
+ * String entries are split on commas and trimmed; empty entries are dropped.
190
+ */
191
+ function optionalToolList(filePath, key, value) {
192
+ if (typeof value === 'string') {
193
+ return value.split(',').map(item => item.trim()).filter(item => item.length > 0);
194
+ }
195
+ return optionalStringArray(filePath, key, value);
196
+ }
197
+ /** Read an optional record of arbitrary values (hooks). */
198
+ function optionalRecordArray(filePath, key, value) {
199
+ if (value === undefined)
200
+ return undefined;
201
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
202
+ throw new Error(`${filePath}: ${key} must be an object`);
203
+ }
204
+ return value;
205
+ }
206
+ /** Read an optional positive integer (maxTurns). */
207
+ function parsePositiveInt(filePath, key, value) {
208
+ if (value === undefined)
209
+ return undefined;
210
+ if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
211
+ throw new Error(`${filePath}: ${key} must be a positive integer`);
212
+ }
213
+ return value;
214
+ }
215
+ /** Read an optional effort: a named level or a positive integer. */
216
+ function parseEffort(filePath, value) {
217
+ if (value === undefined)
218
+ return undefined;
219
+ if (typeof value === 'string' && EFFORT_LEVELS.includes(value)) {
220
+ return value;
221
+ }
222
+ if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
223
+ return value;
224
+ }
225
+ throw new Error(`${filePath}: effort must be one of ${EFFORT_LEVELS.join(', ')} or a positive integer`);
226
+ }
227
+ /** Read an optional enum value from a closed tuple. */
228
+ function parseEnum(filePath, key, value, allowed) {
229
+ if (value === undefined)
230
+ return undefined;
231
+ if (typeof value !== 'string' || !allowed.includes(value)) {
232
+ throw new Error(`${filePath}: ${key} must be one of ${allowed.join(', ')}`);
233
+ }
234
+ return value;
235
+ }
236
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Effective tool restriction from Claude Code's `tools` allow-list and
3
+ * `disallowedTools` deny-list, and the `model: inherit` normalization.
4
+ *
5
+ * A Claude Code agent's `tools` narrows the visible global tool set while its
6
+ * `disallowedTools` removes specific members — including members the `tools`
7
+ * list names. In harness terms both compile to one scoped
8
+ * {@link ToolRestriction} (`allow` from `tools`, `deny` from
9
+ * `disallowedTools`) passed to a scoped `ctx.tools.restrict()`. Because
10
+ * restrictions INTERSECT with every sibling and inherited restriction, a name
11
+ * in both `allow` and `deny` is denied: the agent sees only tools its `allow`
12
+ * admits that no `deny` removes.
13
+ *
14
+ * Claude Code tool names are translated to harness tool names at this
15
+ * boundary via `translateToolNames`. The mapping is one-to-many (e.g. `Read`
16
+ * → `read` + `read_image`, so denying `Read` denies both). Unknown names pass
17
+ * through verbatim so `tools.restrict()` fails loudly at agent-load time with
18
+ * its own clear error rather than silently dropping a typo.
19
+ *
20
+ * @module @dsh-cc/claude-code-agents/restrict
21
+ */
22
+ import type { ToolRestriction } from './types.ts';
23
+ /**
24
+ * Combine a sorted unique tool allow/deny pair into one effective restriction.
25
+ * `deny` wins over `allow` when a name appears in both (restrictions
26
+ * intersect). Omission of both yields no restriction (`undefined`).
27
+ * @param tools - the `tools`/`disallowedTools` normalize root this came from.
28
+ * Absent when neither key was declared.
29
+ * @returns the effective restriction, or `undefined` when neither key existed.
30
+ * @throws when `tools` or `disallowedTools` held an element that is not a string.
31
+ */
32
+ export declare function resolveToolRestriction(tools: readonly string[] | undefined, disallowedTools: readonly string[] | undefined): ToolRestriction | undefined;
33
+ /**
34
+ * Normalize a `model` frontmatter value: trim it and lowercase the sentinel so
35
+ * `Inherit`, `INHERIT`, and `inherit` all mean the same thing, exactly as
36
+ * Claude Code does.
37
+ * @param model - the raw `model` value, or `undefined`.
38
+ * @returns the normalized model, or `undefined` when absent or blank.
39
+ * @throws when `model` is present but not a string.
40
+ */
41
+ export declare function normalizeModel(model: unknown): string | undefined;
42
+ //# sourceMappingURL=restrict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restrict.d.ts","sourceRoot":"","sources":["../src/restrict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACpC,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAC7C,eAAe,GAAG,SAAS,CAU7B;AAkBD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOjE"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Effective tool restriction from Claude Code's `tools` allow-list and
3
+ * `disallowedTools` deny-list, and the `model: inherit` normalization.
4
+ *
5
+ * A Claude Code agent's `tools` narrows the visible global tool set while its
6
+ * `disallowedTools` removes specific members — including members the `tools`
7
+ * list names. In harness terms both compile to one scoped
8
+ * {@link ToolRestriction} (`allow` from `tools`, `deny` from
9
+ * `disallowedTools`) passed to a scoped `ctx.tools.restrict()`. Because
10
+ * restrictions INTERSECT with every sibling and inherited restriction, a name
11
+ * in both `allow` and `deny` is denied: the agent sees only tools its `allow`
12
+ * admits that no `deny` removes.
13
+ *
14
+ * Claude Code tool names are translated to harness tool names at this
15
+ * boundary via `translateToolNames`. The mapping is one-to-many (e.g. `Read`
16
+ * → `read` + `read_image`, so denying `Read` denies both). Unknown names pass
17
+ * through verbatim so `tools.restrict()` fails loudly at agent-load time with
18
+ * its own clear error rather than silently dropping a typo.
19
+ *
20
+ * @module @dsh-cc/claude-code-agents/restrict
21
+ */
22
+ import { translateToolNames } from '@dsh-cc/tools';
23
+ /**
24
+ * Combine a sorted unique tool allow/deny pair into one effective restriction.
25
+ * `deny` wins over `allow` when a name appears in both (restrictions
26
+ * intersect). Omission of both yields no restriction (`undefined`).
27
+ * @param tools - the `tools`/`disallowedTools` normalize root this came from.
28
+ * Absent when neither key was declared.
29
+ * @returns the effective restriction, or `undefined` when neither key existed.
30
+ * @throws when `tools` or `disallowedTools` held an element that is not a string.
31
+ */
32
+ export function resolveToolRestriction(tools, disallowedTools) {
33
+ if (tools === undefined && disallowedTools === undefined)
34
+ return undefined;
35
+ if (tools !== undefined)
36
+ assertStringArray(tools, 'tools');
37
+ if (disallowedTools !== undefined)
38
+ assertStringArray(disallowedTools, 'disallowedTools');
39
+ const allow = tools !== undefined ? translateToolNames(tools, 'strict') : undefined;
40
+ const deny = disallowedTools !== undefined ? translateToolNames(disallowedTools, 'strict') : undefined;
41
+ return {
42
+ ...allow !== undefined ? { allow } : {},
43
+ ...deny !== undefined ? { deny } : {},
44
+ };
45
+ }
46
+ /**
47
+ * Assert a tool name array is non-empty when present, so an agent that claims
48
+ * a restriction actually names one — a materialized-empty config almost always
49
+ * hides an authoring mistake, and an empty split-set equals no restriction.
50
+ * @param names - the array to validate.
51
+ * @param key - the frontmatter key the array came from, for the error.
52
+ * @throws when the array holds a non-string element.
53
+ */
54
+ function assertStringArray(names, key) {
55
+ for (const name of names) {
56
+ if (typeof name !== 'string') {
57
+ throw new Error(`${key} must name tools as strings, got ${String(name)}`);
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Normalize a `model` frontmatter value: trim it and lowercase the sentinel so
63
+ * `Inherit`, `INHERIT`, and `inherit` all mean the same thing, exactly as
64
+ * Claude Code does.
65
+ * @param model - the raw `model` value, or `undefined`.
66
+ * @returns the normalized model, or `undefined` when absent or blank.
67
+ * @throws when `model` is present but not a string.
68
+ */
69
+ export function normalizeModel(model) {
70
+ if (model === undefined)
71
+ return undefined;
72
+ if (typeof model !== 'string' || model.trim().length === 0) {
73
+ throw new Error('model must be a non-empty string');
74
+ }
75
+ const trimmed = model.trim();
76
+ return trimmed.toLowerCase() === 'inherit' ? 'inherit' : trimmed;
77
+ }
78
+ //# sourceMappingURL=restrict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restrict.js","sourceRoot":"","sources":["../src/restrict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAGlD;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAoC,EACpC,eAA8C;IAE9C,IAAI,KAAK,KAAK,SAAS,IAAI,eAAe,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC1E,IAAI,KAAK,KAAK,SAAS;QAAE,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC1D,IAAI,eAAe,KAAK,SAAS;QAAE,iBAAiB,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAA;IACxF,MAAM,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACnF,MAAM,IAAI,GAAG,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACtG,OAAO;QACL,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE;QACvC,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;KACtC,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,KAAwB,EAAE,GAAW;IAC9D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,oCAAoC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC3E,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAA;IACrD,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAC5B,OAAO,OAAO,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAA;AAClE,CAAC"}