Merge pull request #1366 from gsd-build/feat/agent-skill-injection
feat: agent skill injection via config (#1355)
This commit is contained in:
@@ -6,3 +6,6 @@
|
||||
# Examples:
|
||||
# tests/fixtures/fake-credentials.json
|
||||
# docs/examples/sample-config.yml
|
||||
|
||||
# plan-phase.md contains illustrative DATABASE_URL/REDIS_URL examples
|
||||
get-shit-done/workflows/plan-phase.md
|
||||
|
||||
@@ -66,7 +66,8 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd:new
|
||||
"safety": {
|
||||
"always_confirm_destructive": true,
|
||||
"always_confirm_external_services": true
|
||||
}
|
||||
},
|
||||
"agent_skills": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -146,6 +147,72 @@ To keep planning artifacts out of git:
|
||||
|
||||
---
|
||||
|
||||
## Agent Skills Injection
|
||||
|
||||
Inject custom skill files into GSD subagent prompts. Skills are read by agents at spawn time, giving them project-specific instructions beyond what CLAUDE.md provides.
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `agent_skills` | object | `{}` | Map of agent types to skill directory paths |
|
||||
|
||||
### Configuration
|
||||
|
||||
Add an `agent_skills` section to `.planning/config.json` mapping agent types to arrays of skill directory paths (relative to project root):
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_skills": {
|
||||
"gsd-executor": ["skills/testing-standards", "skills/api-conventions"],
|
||||
"gsd-planner": ["skills/architecture-rules"],
|
||||
"gsd-verifier": ["skills/acceptance-criteria"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each path must be a directory containing a `SKILL.md` file. Paths are validated for safety (no traversal outside project root).
|
||||
|
||||
### Supported Agent Types
|
||||
|
||||
Any GSD agent type can receive skills. Common types:
|
||||
|
||||
- `gsd-executor` -- executes implementation plans
|
||||
- `gsd-planner` -- creates phase plans
|
||||
- `gsd-checker` -- verifies plan quality
|
||||
- `gsd-verifier` -- post-execution verification
|
||||
- `gsd-researcher` -- phase research
|
||||
- `gsd-project-researcher` -- new-project research
|
||||
- `gsd-debugger` -- diagnostic agents
|
||||
- `gsd-codebase-mapper` -- codebase analysis
|
||||
- `gsd-advisor` -- discuss-phase advisors
|
||||
- `gsd-ui-researcher` -- UI design contract creation
|
||||
- `gsd-ui-checker` -- UI spec verification
|
||||
- `gsd-roadmapper` -- roadmap creation
|
||||
- `gsd-synthesizer` -- research synthesis
|
||||
|
||||
### How It Works
|
||||
|
||||
At spawn time, workflows call `node gsd-tools.cjs agent-skills <type>` to load configured skills. If skills exist for the agent type, they are injected as an `<agent_skills>` block in the Task() prompt:
|
||||
|
||||
```xml
|
||||
<agent_skills>
|
||||
Read these user-configured skills:
|
||||
- @skills/testing-standards/SKILL.md
|
||||
- @skills/api-conventions/SKILL.md
|
||||
</agent_skills>
|
||||
```
|
||||
|
||||
If no skills are configured, the block is omitted (zero overhead).
|
||||
|
||||
### CLI
|
||||
|
||||
Set skills via the CLI:
|
||||
|
||||
```bash
|
||||
node gsd-tools.cjs config-set agent_skills.gsd-executor '["skills/my-skill"]'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parallelization Settings
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
|
||||
@@ -548,6 +548,11 @@ async function runCommand(command, args, cwd, raw) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'agent-skills': {
|
||||
init.cmdAgentSkills(cwd, args[1], raw);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'history-digest': {
|
||||
commands.cmdHistoryDigest(cwd, raw);
|
||||
break;
|
||||
|
||||
@@ -27,6 +27,18 @@ const VALID_CONFIG_KEYS = new Set([
|
||||
'hooks.context_warnings',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check whether a config key path is valid.
|
||||
* Supports exact matches from VALID_CONFIG_KEYS plus dynamic patterns
|
||||
* like `agent_skills.<agent-type>` where the sub-key is freeform.
|
||||
*/
|
||||
function isValidConfigKey(keyPath) {
|
||||
if (VALID_CONFIG_KEYS.has(keyPath)) return true;
|
||||
// Allow agent_skills.<agent-type> with any agent type string
|
||||
if (/^agent_skills\.[a-zA-Z0-9_-]+$/.test(keyPath)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const CONFIG_KEY_SUGGESTIONS = {
|
||||
'workflow.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
||||
'agents.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
||||
@@ -120,6 +132,7 @@ function buildNewProjectConfig(userChoices) {
|
||||
hooks: {
|
||||
context_warnings: true,
|
||||
},
|
||||
agent_skills: {},
|
||||
};
|
||||
|
||||
// Three-level deep merge: hardcoded <- userDefaults <- choices
|
||||
@@ -142,6 +155,11 @@ function buildNewProjectConfig(userChoices) {
|
||||
...(userDefaults.hooks || {}),
|
||||
...(choices.hooks || {}),
|
||||
},
|
||||
agent_skills: {
|
||||
...hardcoded.agent_skills,
|
||||
...(userDefaults.agent_skills || {}),
|
||||
...(choices.agent_skills || {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -298,15 +316,18 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
||||
|
||||
validateKnownConfigKeyPath(keyPath);
|
||||
|
||||
if (!VALID_CONFIG_KEYS.has(keyPath)) {
|
||||
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}`);
|
||||
if (!isValidConfigKey(keyPath)) {
|
||||
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>`);
|
||||
}
|
||||
|
||||
// Parse value (handle booleans and numbers)
|
||||
// Parse value (handle booleans, numbers, and JSON arrays/objects)
|
||||
let parsedValue = value;
|
||||
if (value === 'true') parsedValue = true;
|
||||
else if (value === 'false') parsedValue = false;
|
||||
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
||||
else if (typeof value === 'string' && (value.startsWith('[') || value.startsWith('{'))) {
|
||||
try { parsedValue = JSON.parse(value); } catch { /* keep as string */ }
|
||||
}
|
||||
|
||||
const setConfigValueResult = setConfigValue(cwd, keyPath, parsedValue);
|
||||
output(setConfigValueResult, raw, `${keyPath}=${parsedValue}`);
|
||||
|
||||
@@ -309,6 +309,7 @@ function loadConfig(cwd) {
|
||||
context_window: get('context_window') ?? defaults.context_window,
|
||||
phase_naming: get('phase_naming') ?? defaults.phase_naming,
|
||||
model_overrides: parsed.model_overrides || null,
|
||||
agent_skills: parsed.agent_skills || {},
|
||||
};
|
||||
} catch {
|
||||
return defaults;
|
||||
|
||||
@@ -1342,6 +1342,77 @@ function cmdInitRemoveWorkspace(cwd, name, raw) {
|
||||
output(result, raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a formatted agent skills block for injection into Task() prompts.
|
||||
*
|
||||
* Reads `config.agent_skills[agentType]` and validates each skill path exists
|
||||
* within the project root. Returns a formatted `<agent_skills>` block or empty
|
||||
* string if no skills are configured.
|
||||
*
|
||||
* @param {object} config - Loaded project config
|
||||
* @param {string} agentType - The agent type (e.g., 'gsd-executor', 'gsd-planner')
|
||||
* @param {string} projectRoot - Absolute path to project root (for path validation)
|
||||
* @returns {string} Formatted skills block or empty string
|
||||
*/
|
||||
function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
||||
const { validatePath } = require('./security.cjs');
|
||||
|
||||
if (!config || !config.agent_skills || !agentType) return '';
|
||||
|
||||
let skillPaths = config.agent_skills[agentType];
|
||||
if (!skillPaths) return '';
|
||||
|
||||
// Normalize single string to array
|
||||
if (typeof skillPaths === 'string') skillPaths = [skillPaths];
|
||||
if (!Array.isArray(skillPaths) || skillPaths.length === 0) return '';
|
||||
|
||||
const validPaths = [];
|
||||
for (const skillPath of skillPaths) {
|
||||
if (typeof skillPath !== 'string') continue;
|
||||
|
||||
// Validate path safety — must resolve within project root
|
||||
const pathCheck = validatePath(skillPath, projectRoot);
|
||||
if (!pathCheck.safe) {
|
||||
process.stderr.write(`[agent-skills] WARNING: Skipping unsafe path "${skillPath}": ${pathCheck.error}\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the skill directory and SKILL.md exist
|
||||
const skillMdPath = path.join(projectRoot, skillPath, 'SKILL.md');
|
||||
if (!fs.existsSync(skillMdPath)) {
|
||||
process.stderr.write(`[agent-skills] WARNING: Skill not found at "${skillPath}/SKILL.md" — skipping\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
validPaths.push(skillPath);
|
||||
}
|
||||
|
||||
if (validPaths.length === 0) return '';
|
||||
|
||||
const lines = validPaths.map(p => `- @${p}/SKILL.md`).join('\n');
|
||||
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command: output the agent skills block for a given agent type.
|
||||
* Used by workflows: SKILLS=$(node "$TOOLS" agent-skills gsd-executor 2>/dev/null)
|
||||
*/
|
||||
function cmdAgentSkills(cwd, agentType, raw) {
|
||||
if (!agentType) {
|
||||
// No agent type — output empty string silently
|
||||
output('', raw, '');
|
||||
return;
|
||||
}
|
||||
|
||||
const config = loadConfig(cwd);
|
||||
const block = buildAgentSkillsBlock(config, agentType, cwd);
|
||||
// Output raw text (not JSON) so workflows can embed it directly
|
||||
if (block) {
|
||||
process.stdout.write(block);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cmdInitExecutePhase,
|
||||
cmdInitPlanPhase,
|
||||
@@ -1360,4 +1431,6 @@ module.exports = {
|
||||
cmdInitListWorkspaces,
|
||||
cmdInitRemoveWorkspace,
|
||||
detectChildRepos,
|
||||
buildAgentSkillsBlock,
|
||||
cmdAgentSkills,
|
||||
};
|
||||
|
||||
@@ -39,5 +39,6 @@
|
||||
},
|
||||
"hooks": {
|
||||
"context_warnings": true
|
||||
}
|
||||
},
|
||||
"agent_skills": {}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init milestone-op)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_CHECKER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-integration-checker 2>/dev/null)
|
||||
```
|
||||
|
||||
Extract from init JSON: `milestone_version`, `milestone_name`, `phase_count`, `completed_phases`, `commit_docs`.
|
||||
@@ -78,7 +79,8 @@ Milestone Requirements:
|
||||
|
||||
MUST map each integration finding to affected requirement IDs where applicable.
|
||||
|
||||
Verify cross-phase wiring and E2E user flows.",
|
||||
Verify cross-phase wiring and E2E user flows.
|
||||
${AGENT_SKILLS_CHECKER}",
|
||||
subagent_type="gsd-integration-checker",
|
||||
model="{integration_checker_model}"
|
||||
)
|
||||
|
||||
@@ -78,13 +78,19 @@ This runs in parallel - all gaps investigated simultaneously.
|
||||
</step>
|
||||
|
||||
<step name="spawn_agents">
|
||||
**Load agent skills:**
|
||||
|
||||
```bash
|
||||
AGENT_SKILLS_DEBUGGER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-debugger 2>/dev/null)
|
||||
```
|
||||
|
||||
**Spawn debug agents in parallel:**
|
||||
|
||||
For each gap, fill the debug-subagent-prompt template and spawn:
|
||||
|
||||
```
|
||||
Task(
|
||||
prompt=filled_debug_subagent_prompt + "\n\n<files_to_read>\n- {phase_dir}/{phase_num}-UAT.md\n- .planning/STATE.md\n</files_to_read>",
|
||||
prompt=filled_debug_subagent_prompt + "\n\n<files_to_read>\n- {phase_dir}/{phase_num}-UAT.md\n- .planning/STATE.md\n</files_to_read>\n${AGENT_SKILLS_DEBUGGER}",
|
||||
subagent_type="gsd-debugger",
|
||||
isolation="worktree",
|
||||
description="Debug: {truth_short}"
|
||||
|
||||
@@ -66,6 +66,7 @@ Phase number from argument (required).
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_ANALYZER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-assumptions-analyzer 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`,
|
||||
@@ -270,6 +271,8 @@ Return EXACTLY this structure:
|
||||
## Needs External Research
|
||||
[Topics where codebase alone is insufficient — library version compatibility,
|
||||
ecosystem best practices, etc. Leave empty if codebase provides enough evidence.]
|
||||
|
||||
${AGENT_SKILLS_ANALYZER}
|
||||
""")
|
||||
```
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ Phase number from argument (required).
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_ADVISOR=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-advisor 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `has_verification`, `plan_count`, `roadmap_exists`, `planning_exists`.
|
||||
@@ -498,7 +499,8 @@ After user selects gray areas in present_gray_areas, spawn parallel research age
|
||||
<project_context>{project name and brief description from PROJECT.md}</project_context>
|
||||
<calibration_tier>{resolved calibration tier: full_maturity | standard | minimal_decisive}</calibration_tier>
|
||||
|
||||
Research this gray area and return a structured comparison table with rationale.",
|
||||
Research this gray area and return a structured comparison table with rationale.
|
||||
${AGENT_SKILLS_ADVISOR}",
|
||||
subagent_type="general-purpose",
|
||||
model="{ADVISOR_MODEL}",
|
||||
description="Research: {area_name}"
|
||||
|
||||
@@ -63,6 +63,7 @@ Load all context in one call:
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init execute-phase "${PHASE_ARG}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-executor 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`.
|
||||
@@ -258,6 +259,8 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT
|
||||
- .claude/skills/ or .agents/skills/ (Project skills, if either exists — list skills, read SKILL.md for each, follow relevant rules during implementation)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS}
|
||||
|
||||
<mcp_tools>
|
||||
If CLAUDE.md or project instructions reference MCP tools (e.g. jCodeMunch, context7,
|
||||
or other MCP servers), prefer those tools over Grep/Glob for code navigation when available.
|
||||
@@ -580,6 +583,10 @@ Use AskUserQuestion to present the options.
|
||||
<step name="verify_phase_goal">
|
||||
Verify phase achieved its GOAL, not just completed tasks.
|
||||
|
||||
```bash
|
||||
VERIFIER_SKILLS=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-verifier 2>/dev/null)
|
||||
```
|
||||
|
||||
```
|
||||
Task(
|
||||
prompt="Verify phase {phase_number} goal achievement.
|
||||
@@ -588,7 +595,8 @@ Phase goal: {goal from ROADMAP.md}
|
||||
Phase requirement IDs: {phase_req_ids}
|
||||
Check must_haves against actual codebase.
|
||||
Cross-reference requirement IDs from PLAN frontmatter against REQUIREMENTS.md — every ID MUST be accounted for.
|
||||
Create VERIFICATION.md.",
|
||||
Create VERIFICATION.md.
|
||||
${VERIFIER_SKILLS}",
|
||||
subagent_type="gsd-verifier",
|
||||
model="{verifier_model}"
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ Load codebase mapping context:
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init map-codebase)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_MAPPER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-codebase-mapper 2>/dev/null)
|
||||
```
|
||||
|
||||
Extract from init JSON: `mapper_model`, `commit_docs`, `codebase_dir`, `existing_maps`, `has_maps`, `codebase_dir_exists`.
|
||||
@@ -120,7 +121,8 @@ Write these documents to .planning/codebase/:
|
||||
- STACK.md - Languages, runtime, frameworks, dependencies, configuration
|
||||
- INTEGRATIONS.md - External APIs, databases, auth providers, webhooks
|
||||
|
||||
Explore thoroughly. Write documents directly using templates. Return confirmation only."
|
||||
Explore thoroughly. Write documents directly using templates. Return confirmation only.
|
||||
${AGENT_SKILLS_MAPPER}"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -140,7 +142,8 @@ Write these documents to .planning/codebase/:
|
||||
- ARCHITECTURE.md - Pattern, layers, data flow, abstractions, entry points
|
||||
- STRUCTURE.md - Directory layout, key locations, naming conventions
|
||||
|
||||
Explore thoroughly. Write documents directly using templates. Return confirmation only."
|
||||
Explore thoroughly. Write documents directly using templates. Return confirmation only.
|
||||
${AGENT_SKILLS_MAPPER}"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -160,7 +163,8 @@ Write these documents to .planning/codebase/:
|
||||
- CONVENTIONS.md - Code style, naming, patterns, error handling
|
||||
- TESTING.md - Framework, structure, mocking, coverage
|
||||
|
||||
Explore thoroughly. Write documents directly using templates. Return confirmation only."
|
||||
Explore thoroughly. Write documents directly using templates. Return confirmation only.
|
||||
${AGENT_SKILLS_MAPPER}"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -179,7 +183,8 @@ Analyze this codebase for technical debt, known issues, and areas of concern.
|
||||
Write this document to .planning/codebase/:
|
||||
- CONCERNS.md - Tech debt, bugs, security, performance, fragile areas
|
||||
|
||||
Explore thoroughly. Write document directly using template. Return confirmation only."
|
||||
Explore thoroughly. Write document directly using template. Return confirmation only.
|
||||
${AGENT_SKILLS_MAPPER}"
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -146,6 +146,9 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs: start milesto
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init new-milestone)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_RESEARCHER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-project-researcher 2>/dev/null)
|
||||
AGENT_SKILLS_SYNTHESIZER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-synthesizer 2>/dev/null)
|
||||
AGENT_SKILLS_ROADMAPPER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-roadmapper 2>/dev/null)
|
||||
```
|
||||
|
||||
Extract from init JSON: `researcher_model`, `synthesizer_model`, `roadmapper_model`, `commit_docs`, `research_enabled`, `current_milestone`, `project_exists`, `roadmap_exists`, `latest_completed_milestone`, `phase_dir_count`, `phase_archive_path`.
|
||||
@@ -222,6 +225,8 @@ Focus ONLY on what's needed for the NEW features.
|
||||
- .planning/PROJECT.md (Project context)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_RESEARCHER}
|
||||
|
||||
<downstream_consumer>{CONSUMER}</downstream_consumer>
|
||||
|
||||
<quality_gate>{GATES}</quality_gate>
|
||||
@@ -256,6 +261,8 @@ Synthesize research outputs into SUMMARY.md.
|
||||
- .planning/research/PITFALLS.md
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_SYNTHESIZER}
|
||||
|
||||
Write to: .planning/research/SUMMARY.md
|
||||
Use template: ~/.claude/get-shit-done/templates/research-project/SUMMARY.md
|
||||
Commit after writing.
|
||||
@@ -370,6 +377,9 @@ Task(prompt="
|
||||
- .planning/config.json
|
||||
- .planning/MILESTONES.md
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_ROADMAPPER}
|
||||
|
||||
</planning_context>
|
||||
|
||||
<instructions>
|
||||
|
||||
@@ -59,6 +59,9 @@ The document should describe what you want to build.
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init new-project)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_RESEARCHER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-project-researcher 2>/dev/null)
|
||||
AGENT_SKILLS_SYNTHESIZER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-synthesizer 2>/dev/null)
|
||||
AGENT_SKILLS_ROADMAPPER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-roadmapper 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `researcher_model`, `synthesizer_model`, `roadmapper_model`, `commit_docs`, `project_exists`, `has_codebase_map`, `planning_exists`, `has_existing_code`, `has_package_file`, `is_brownfield`, `needs_codebase_map`, `has_git`, `project_path`.
|
||||
@@ -630,6 +633,8 @@ What's the standard 2025 stack for [domain]?
|
||||
- {project_path} (Project context and goals)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_RESEARCHER}
|
||||
|
||||
<downstream_consumer>
|
||||
Your STACK.md feeds into roadmap creation. Be prescriptive:
|
||||
- Specific libraries with versions
|
||||
@@ -668,6 +673,8 @@ What features do [domain] products have? What's table stakes vs differentiating?
|
||||
- {project_path} (Project context)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_RESEARCHER}
|
||||
|
||||
<downstream_consumer>
|
||||
Your FEATURES.md feeds into requirements definition. Categorize clearly:
|
||||
- Table stakes (must have or users leave)
|
||||
@@ -706,6 +713,8 @@ How are [domain] systems typically structured? What are major components?
|
||||
- {project_path} (Project context)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_RESEARCHER}
|
||||
|
||||
<downstream_consumer>
|
||||
Your ARCHITECTURE.md informs phase structure in roadmap. Include:
|
||||
- Component boundaries (what talks to what)
|
||||
@@ -744,6 +753,8 @@ What do [domain] projects commonly get wrong? Critical mistakes?
|
||||
- {project_path} (Project context)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_RESEARCHER}
|
||||
|
||||
<downstream_consumer>
|
||||
Your PITFALLS.md prevents mistakes in roadmap/planning. For each pitfall:
|
||||
- Warning signs (how to detect early)
|
||||
@@ -779,6 +790,8 @@ Synthesize research outputs into SUMMARY.md.
|
||||
- .planning/research/PITFALLS.md
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_SYNTHESIZER}
|
||||
|
||||
<output>
|
||||
Write to: .planning/research/SUMMARY.md
|
||||
Use template: ~/.claude/get-shit-done/templates/research-project/SUMMARY.md
|
||||
@@ -984,6 +997,8 @@ Task(prompt="
|
||||
- .planning/config.json (Granularity and mode settings)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_ROADMAPPER}
|
||||
|
||||
</planning_context>
|
||||
|
||||
<instructions>
|
||||
@@ -1078,6 +1093,8 @@ Use AskUserQuestion:
|
||||
- .planning/ROADMAP.md (Current roadmap to revise)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_ROADMAPPER}
|
||||
|
||||
Update the roadmap based on feedback. Edit files in place.
|
||||
Return ROADMAP REVISED with changes made.
|
||||
</revision>
|
||||
|
||||
@@ -24,6 +24,9 @@ Load all context in one call (paths only to minimize orchestrator context):
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init plan-phase "$PHASE")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_RESEARCHER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-researcher 2>/dev/null)
|
||||
AGENT_SKILLS_PLANNER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-planner 2>/dev/null)
|
||||
AGENT_SKILLS_CHECKER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-checker 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`, `phase_req_ids`.
|
||||
@@ -298,6 +301,8 @@ Answer: "What do I need to know to PLAN this phase well?"
|
||||
- {state_path} (Project decisions and history)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_RESEARCHER}
|
||||
|
||||
<additional_context>
|
||||
**Phase description:** {phase_description}
|
||||
**Phase requirement IDs (MUST address):** {phase_req_ids}
|
||||
@@ -485,6 +490,8 @@ Planner prompt:
|
||||
- {UI_SPEC_PATH} (UI Design Contract — visual/interaction specs, if exists)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
**Phase requirement IDs (every ID MUST appear in a plan's `requirements` field):** {phase_req_ids}
|
||||
|
||||
**Project instructions:** Read ./CLAUDE.md if exists — follow project-specific guidelines
|
||||
@@ -582,6 +589,8 @@ Checker prompt:
|
||||
- {research_path} (Technical Research — includes Validation Architecture)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_CHECKER}
|
||||
|
||||
**Phase requirement IDs (MUST ALL be covered):** {phase_req_ids}
|
||||
|
||||
**Project instructions:** Read ./CLAUDE.md if exists — verify plans honor project guidelines
|
||||
@@ -628,6 +637,8 @@ Revision prompt:
|
||||
- {context_path} (USER DECISIONS from /gsd:discuss-phase)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
**Checker issues:** {structured_issues_from_checker}
|
||||
</revision_context>
|
||||
|
||||
|
||||
@@ -118,6 +118,10 @@ If `$FULL_MODE` only:
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init quick "$DESCRIPTION")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_PLANNER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-planner 2>/dev/null)
|
||||
AGENT_SKILLS_EXECUTOR=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-executor 2>/dev/null)
|
||||
AGENT_SKILLS_CHECKER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-checker 2>/dev/null)
|
||||
AGENT_SKILLS_VERIFIER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-verifier 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
|
||||
@@ -327,6 +331,8 @@ Task(
|
||||
${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — research should align with these)' : ''}
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
</research_context>
|
||||
|
||||
<focus>
|
||||
@@ -381,6 +387,8 @@ ${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decision
|
||||
${RESEARCH_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-RESEARCH.md (Research findings — use to inform implementation choices)' : ''}
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
**Project skills:** Check .claude/skills/ or .agents/skills/ directory (if either exists) — read SKILL.md files, plans should account for project skill rules
|
||||
|
||||
</planning_context>
|
||||
@@ -438,6 +446,8 @@ Checker prompt:
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan to verify)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_CHECKER}
|
||||
|
||||
**Scope:** This is a quick task, not a full phase. Skip checks that require a ROADMAP phase goal.
|
||||
</verification_context>
|
||||
|
||||
@@ -490,6 +500,8 @@ Revision prompt:
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Existing plan)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
**Checker issues:** ${structured_issues_from_checker}
|
||||
|
||||
</revision_context>
|
||||
@@ -536,6 +548,8 @@ Execute quick task ${quick_id}.
|
||||
- .claude/skills/ or .agents/skills/ (Project skills, if either exists — list skills, read SKILL.md for each, follow relevant rules during implementation)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_EXECUTOR}
|
||||
|
||||
<constraints>
|
||||
- Execute all tasks in the plan
|
||||
- Commit each task atomically
|
||||
@@ -586,6 +600,8 @@ Task goal: ${DESCRIPTION}
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_VERIFIER}
|
||||
|
||||
Check must_haves against actual codebase. Create VERIFICATION.md at ${QUICK_DIR}/${quick_id}-VERIFICATION.md.",
|
||||
subagent_type="gsd-verifier",
|
||||
model="{verifier_model}",
|
||||
|
||||
@@ -42,6 +42,7 @@ If exists: Offer update/view/skip options.
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
# Extract: phase_dir, padded_phase, phase_number, state_path, requirements_path, context_path
|
||||
AGENT_SKILLS_RESEARCHER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-researcher 2>/dev/null)
|
||||
```
|
||||
|
||||
## Step 4: Spawn Researcher
|
||||
@@ -58,6 +59,8 @@ Research implementation approach for Phase {phase}: {name}
|
||||
- {state_path} (Project decisions and history)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_RESEARCHER}
|
||||
|
||||
<additional_context>
|
||||
Phase description: {description}
|
||||
</additional_context>
|
||||
|
||||
@@ -21,6 +21,8 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init plan-phase "$PHASE")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_UI=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-ui-researcher 2>/dev/null)
|
||||
AGENT_SKILLS_UI_CHECKER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-ui-checker 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_context`, `has_research`, `commit_docs`.
|
||||
@@ -122,6 +124,8 @@ Answer: "What visual and interaction contracts does this phase need?"
|
||||
- {research_path} (Technical Research — stack decisions)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_UI}
|
||||
|
||||
<output>
|
||||
Write to: {phase_dir}/{padded_phase}-UI-SPEC.md
|
||||
Template: ~/.claude/get-shit-done/templates/UI-SPEC.md
|
||||
@@ -180,6 +184,8 @@ Check all 6 dimensions. Return APPROVED or BLOCKED.
|
||||
- {research_path} (Technical Research — check stack alignment)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_UI_CHECKER}
|
||||
|
||||
<config>
|
||||
ui_safety_gate: {ui_safety_gate config value}
|
||||
</config>
|
||||
|
||||
@@ -18,6 +18,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE_ARG}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_UI_REVIEWER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-ui-reviewer 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `commit_docs`.
|
||||
@@ -85,6 +86,8 @@ Conduct 6-pillar visual audit of Phase {phase_number}: {phase_name}
|
||||
- {context_path} (User decisions, if exists)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_UI_REVIEWER}
|
||||
|
||||
<config>
|
||||
phase_dir: {phase_dir}
|
||||
padded_phase: {padded_phase}
|
||||
|
||||
@@ -18,6 +18,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE_ARG}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_AUDITOR=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-nyquist-auditor 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`.
|
||||
@@ -95,7 +96,8 @@ Task(
|
||||
"<files_to_read>{PLAN, SUMMARY, impl files, VALIDATION.md}</files_to_read>" +
|
||||
"<gaps>{gap list}</gaps>" +
|
||||
"<test_infrastructure>{framework, config, commands}</test_infrastructure>" +
|
||||
"<constraints>Never modify impl files. Max 3 debug iterations. Escalate impl bugs.</constraints>",
|
||||
"<constraints>Never modify impl files. Max 3 debug iterations. Escalate impl bugs.</constraints>" +
|
||||
"${AGENT_SKILLS_AUDITOR}",
|
||||
subagent_type="gsd-nyquist-auditor",
|
||||
model="{AUDITOR_MODEL}",
|
||||
description="Fill validation gaps for Phase {N}"
|
||||
|
||||
@@ -32,6 +32,8 @@ If $ARGUMENTS contains a phase number, load context:
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init verify-work "${PHASE_ARG}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_PLANNER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-planner 2>/dev/null)
|
||||
AGENT_SKILLS_CHECKER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills gsd-checker 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `has_verification`, `uat_path`.
|
||||
@@ -431,6 +433,8 @@ Task(
|
||||
- .planning/ROADMAP.md (Roadmap)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
</planning_context>
|
||||
|
||||
<downstream_consumer>
|
||||
@@ -477,6 +481,8 @@ Task(
|
||||
- {phase_dir}/*-PLAN.md (Plans to verify)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_CHECKER}
|
||||
|
||||
</verification_context>
|
||||
|
||||
<expected_output>
|
||||
@@ -517,6 +523,8 @@ Task(
|
||||
- {phase_dir}/*-PLAN.md (Existing plans)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
**Checker issues:**
|
||||
{structured_issues_from_checker}
|
||||
|
||||
|
||||
207
tests/agent-skills.test.cjs
Normal file
207
tests/agent-skills.test.cjs
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* GSD Tools Tests - Agent Skills Injection
|
||||
*
|
||||
* CLI integration tests for the `agent-skills` command that reads
|
||||
* `agent_skills` from .planning/config.json and returns a formatted
|
||||
* skills block for injection into Task() prompts.
|
||||
*/
|
||||
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
||||
|
||||
// ─── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function writeConfig(tmpDir, obj) {
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
fs.writeFileSync(configPath, JSON.stringify(obj, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
function readConfig(tmpDir) {
|
||||
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
}
|
||||
|
||||
// ─── agent-skills command ────────────────────────────────────────────────────
|
||||
|
||||
describe('agent-skills command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('returns empty when no config exists', () => {
|
||||
// No config.json at all
|
||||
const result = runGsdTools(['agent-skills', 'gsd-executor'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
// Should succeed with empty output (no skills configured)
|
||||
assert.strictEqual(result.output, '');
|
||||
});
|
||||
|
||||
test('returns empty when config has no agent_skills section', () => {
|
||||
writeConfig(tmpDir, { model_profile: 'balanced' });
|
||||
const result = runGsdTools(['agent-skills', 'gsd-executor'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
assert.strictEqual(result.output, '');
|
||||
});
|
||||
|
||||
test('returns empty for unconfigured agent type', () => {
|
||||
writeConfig(tmpDir, {
|
||||
agent_skills: {
|
||||
'gsd-executor': ['skills/test-skill'],
|
||||
},
|
||||
});
|
||||
const result = runGsdTools(['agent-skills', 'gsd-planner'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
assert.strictEqual(result.output, '');
|
||||
});
|
||||
|
||||
test('returns formatted block for configured agent with array of paths', () => {
|
||||
// Create the skill directories with SKILL.md files
|
||||
const skillDir = path.join(tmpDir, 'skills', 'test-skill');
|
||||
fs.mkdirSync(skillDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), '# Test Skill\n');
|
||||
|
||||
writeConfig(tmpDir, {
|
||||
agent_skills: {
|
||||
'gsd-executor': ['skills/test-skill'],
|
||||
},
|
||||
});
|
||||
|
||||
const result = runGsdTools(['agent-skills', 'gsd-executor'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.ok(result.output.includes('<agent_skills>'), 'Should contain <agent_skills> tag');
|
||||
assert.ok(result.output.includes('</agent_skills>'), 'Should contain closing tag');
|
||||
assert.ok(result.output.includes('skills/test-skill/SKILL.md'), 'Should contain skill path');
|
||||
});
|
||||
|
||||
test('returns formatted block for configured agent with single string path', () => {
|
||||
const skillDir = path.join(tmpDir, 'skills', 'my-skill');
|
||||
fs.mkdirSync(skillDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), '# My Skill\n');
|
||||
|
||||
writeConfig(tmpDir, {
|
||||
agent_skills: {
|
||||
'gsd-executor': 'skills/my-skill',
|
||||
},
|
||||
});
|
||||
|
||||
const result = runGsdTools(['agent-skills', 'gsd-executor'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.ok(result.output.includes('skills/my-skill/SKILL.md'), 'Should contain skill path');
|
||||
});
|
||||
|
||||
test('handles multiple skill paths', () => {
|
||||
const skill1 = path.join(tmpDir, 'skills', 'skill-a');
|
||||
const skill2 = path.join(tmpDir, 'skills', 'skill-b');
|
||||
fs.mkdirSync(skill1, { recursive: true });
|
||||
fs.mkdirSync(skill2, { recursive: true });
|
||||
fs.writeFileSync(path.join(skill1, 'SKILL.md'), '# Skill A\n');
|
||||
fs.writeFileSync(path.join(skill2, 'SKILL.md'), '# Skill B\n');
|
||||
|
||||
writeConfig(tmpDir, {
|
||||
agent_skills: {
|
||||
'gsd-executor': ['skills/skill-a', 'skills/skill-b'],
|
||||
},
|
||||
});
|
||||
|
||||
const result = runGsdTools(['agent-skills', 'gsd-executor'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.ok(result.output.includes('skills/skill-a/SKILL.md'), 'Should contain first skill');
|
||||
assert.ok(result.output.includes('skills/skill-b/SKILL.md'), 'Should contain second skill');
|
||||
});
|
||||
|
||||
test('warns for nonexistent skill path but does not error', () => {
|
||||
writeConfig(tmpDir, {
|
||||
agent_skills: {
|
||||
'gsd-executor': ['skills/nonexistent'],
|
||||
},
|
||||
});
|
||||
|
||||
const result = runGsdTools(['agent-skills', 'gsd-executor'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
// Should not crash — returns empty output (the missing skill is skipped)
|
||||
assert.ok(result.success, 'Command should succeed even with missing skill paths');
|
||||
// Should not include the missing skill in the output
|
||||
assert.ok(!result.output.includes('skills/nonexistent/SKILL.md'),
|
||||
'Should not include nonexistent skill in output');
|
||||
});
|
||||
|
||||
test('validates path safety — rejects traversal attempts', () => {
|
||||
writeConfig(tmpDir, {
|
||||
agent_skills: {
|
||||
'gsd-executor': ['../../../etc/passwd'],
|
||||
},
|
||||
});
|
||||
|
||||
const result = runGsdTools(['agent-skills', 'gsd-executor'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
// Should not include traversal path in output
|
||||
assert.ok(!result.output.includes('/etc/passwd'), 'Should not include traversal path');
|
||||
});
|
||||
|
||||
test('returns empty when no agent type argument provided', () => {
|
||||
const result = runGsdTools(['agent-skills'], tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
// Should succeed with empty output — no agent type means no skills to return
|
||||
assert.ok(result.success, 'Command should succeed');
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed, '', 'Should return empty string');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── config-ensure-section includes agent_skills ────────────────────────────
|
||||
|
||||
describe('config-ensure-section with agent_skills', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('new configs include agent_skills key', () => {
|
||||
const result = runGsdTools('config-ensure-section', tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const config = readConfig(tmpDir);
|
||||
assert.ok('agent_skills' in config, 'config should have agent_skills key');
|
||||
assert.deepStrictEqual(config.agent_skills, {}, 'agent_skills should default to empty object');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── config-set agent_skills ─────────────────────────────────────────────────
|
||||
|
||||
describe('config-set agent_skills', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
// Ensure config exists first
|
||||
runGsdTools('config-ensure-section', tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('can set agent_skills via dot notation', () => {
|
||||
const result = runGsdTools(
|
||||
['config-set', 'agent_skills.gsd-executor', '["skills/my-skill"]'],
|
||||
tmpDir,
|
||||
{ HOME: tmpDir, USERPROFILE: tmpDir }
|
||||
);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const config = readConfig(tmpDir);
|
||||
assert.deepStrictEqual(
|
||||
config.agent_skills['gsd-executor'],
|
||||
['skills/my-skill'],
|
||||
'Should store array of skill paths'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user