aiwcli 0.9.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/README.md +1248 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +16 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +19 -0
- package/dist/commands/branch.d.ts +45 -0
- package/dist/commands/branch.js +488 -0
- package/dist/commands/clean.d.ts +34 -0
- package/dist/commands/clean.js +186 -0
- package/dist/commands/clear.d.ts +51 -0
- package/dist/commands/clear.js +835 -0
- package/dist/commands/init/index.d.ts +107 -0
- package/dist/commands/init/index.js +565 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/base-command.d.ts +114 -0
- package/dist/lib/base-command.js +153 -0
- package/dist/lib/bmad-installer.d.ts +38 -0
- package/dist/lib/bmad-installer.js +145 -0
- package/dist/lib/claude-settings-types.d.ts +102 -0
- package/dist/lib/claude-settings-types.js +5 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/debug.d.ts +39 -0
- package/dist/lib/debug.js +74 -0
- package/dist/lib/env-compat.d.ts +26 -0
- package/dist/lib/env-compat.js +35 -0
- package/dist/lib/errors.d.ts +126 -0
- package/dist/lib/errors.js +145 -0
- package/dist/lib/generic-merge.d.ts +74 -0
- package/dist/lib/generic-merge.js +105 -0
- package/dist/lib/git/branch.d.ts +67 -0
- package/dist/lib/git/branch.js +155 -0
- package/dist/lib/git/index.d.ts +11 -0
- package/dist/lib/git/index.js +13 -0
- package/dist/lib/git/safety-checks.d.ts +44 -0
- package/dist/lib/git/safety-checks.js +102 -0
- package/dist/lib/git/types.d.ts +31 -0
- package/dist/lib/git/types.js +6 -0
- package/dist/lib/git/worktree.d.ts +67 -0
- package/dist/lib/git/worktree.js +220 -0
- package/dist/lib/gitignore-manager.d.ts +10 -0
- package/dist/lib/gitignore-manager.js +60 -0
- package/dist/lib/hooks-merger.d.ts +28 -0
- package/dist/lib/hooks-merger.js +94 -0
- package/dist/lib/ide-path-resolver.d.ts +102 -0
- package/dist/lib/ide-path-resolver.js +129 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.js +22 -0
- package/dist/lib/output.d.ts +51 -0
- package/dist/lib/output.js +76 -0
- package/dist/lib/paths.d.ts +66 -0
- package/dist/lib/paths.js +136 -0
- package/dist/lib/quiet.d.ts +12 -0
- package/dist/lib/quiet.js +17 -0
- package/dist/lib/settings-hierarchy.d.ts +42 -0
- package/dist/lib/settings-hierarchy.js +105 -0
- package/dist/lib/spawn.d.ts +105 -0
- package/dist/lib/spawn.js +157 -0
- package/dist/lib/spinner.d.ts +19 -0
- package/dist/lib/spinner.js +34 -0
- package/dist/lib/stdin.d.ts +48 -0
- package/dist/lib/stdin.js +60 -0
- package/dist/lib/template-installer.d.ts +92 -0
- package/dist/lib/template-installer.js +375 -0
- package/dist/lib/template-linter.d.ts +49 -0
- package/dist/lib/template-linter.js +173 -0
- package/dist/lib/template-merger.d.ts +47 -0
- package/dist/lib/template-merger.js +173 -0
- package/dist/lib/template-resolver.d.ts +20 -0
- package/dist/lib/template-resolver.js +60 -0
- package/dist/lib/terminal.d.ts +102 -0
- package/dist/lib/terminal.js +245 -0
- package/dist/lib/tty-detection.d.ts +62 -0
- package/dist/lib/tty-detection.js +83 -0
- package/dist/lib/user-utils.d.ts +5 -0
- package/dist/lib/user-utils.js +23 -0
- package/dist/lib/version.d.ts +99 -0
- package/dist/lib/version.js +144 -0
- package/dist/lib/watch-templates.d.ts +6 -0
- package/dist/lib/watch-templates.js +73 -0
- package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
- package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
- package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
- package/dist/lib/windsurf-hooks-merger.js +53 -0
- package/dist/lib/windsurf-hooks-types.d.ts +33 -0
- package/dist/lib/windsurf-hooks-types.js +5 -0
- package/dist/templates/CLAUDE.md +174 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
- package/dist/templates/_shared/.claude/settings.json +61 -0
- package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
- package/dist/templates/_shared/hooks/__init__.py +16 -0
- package/dist/templates/_shared/hooks/archive_plan.py +270 -0
- package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
- package/dist/templates/_shared/hooks/context_monitor.py +322 -0
- package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
- package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
- package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
- package/dist/templates/_shared/lib/__init__.py +1 -0
- package/dist/templates/_shared/lib/base/__init__.py +49 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
- package/dist/templates/_shared/lib/base/constants.py +299 -0
- package/dist/templates/_shared/lib/base/inference.py +189 -0
- package/dist/templates/_shared/lib/base/utils.py +216 -0
- package/dist/templates/_shared/lib/context/__init__.py +119 -0
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/cache.py +446 -0
- package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
- package/dist/templates/_shared/lib/context/discovery.py +486 -0
- package/dist/templates/_shared/lib/context/event_log.py +308 -0
- package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
- package/dist/templates/_shared/lib/context/task_sync.py +367 -0
- package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
- package/dist/templates/_shared/lib/templates/README.md +215 -0
- package/dist/templates/_shared/lib/templates/__init__.py +40 -0
- package/dist/templates/_shared/lib/templates/formatters.py +147 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
- package/dist/templates/_shared/scripts/save_handoff.py +99 -0
- package/dist/templates/_shared/workflows/handoff.md +212 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
- package/dist/templates/cc-native/.claude/settings.json +119 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
- package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
- package/dist/templates/cc-native/MIGRATION.md +86 -0
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
- package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
- package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
- package/dist/types/exit-codes.d.ts +11 -0
- package/dist/types/exit-codes.js +10 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/oclif.manifest.json +405 -0
- package/package.json +109 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { IdePathResolver } from './ide-path-resolver.js';
|
|
4
|
+
import { mergeTemplateContent } from './template-merger.js';
|
|
5
|
+
/**
|
|
6
|
+
* Deep merge two settings objects, combining hook arrays.
|
|
7
|
+
* Used to merge _shared/settings.json into .claude/settings.json
|
|
8
|
+
*/
|
|
9
|
+
function deepMergeSettings(target, source) {
|
|
10
|
+
const result = { ...target };
|
|
11
|
+
for (const key of Object.keys(source)) {
|
|
12
|
+
// Skip comment fields
|
|
13
|
+
if (key.startsWith('$') || key.startsWith('_')) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const sourceValue = source[key];
|
|
17
|
+
const targetValue = result[key];
|
|
18
|
+
if (key === 'hooks' && typeof sourceValue === 'object' && sourceValue !== null) {
|
|
19
|
+
// Special handling for hooks - merge by event type
|
|
20
|
+
result[key] = mergeHooks(targetValue || {}, sourceValue);
|
|
21
|
+
}
|
|
22
|
+
else if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
|
|
23
|
+
// Concatenate arrays
|
|
24
|
+
result[key] = [...targetValue, ...sourceValue];
|
|
25
|
+
}
|
|
26
|
+
else if (typeof sourceValue === 'object' && sourceValue !== null && typeof targetValue === 'object' && targetValue !== null) {
|
|
27
|
+
// Recursively merge objects
|
|
28
|
+
result[key] = deepMergeSettings(targetValue, sourceValue);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// Override with source value
|
|
32
|
+
result[key] = sourceValue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Merge hook configurations, combining arrays for each event type.
|
|
39
|
+
*/
|
|
40
|
+
function mergeHooks(target, source) {
|
|
41
|
+
const result = { ...target };
|
|
42
|
+
for (const eventType of Object.keys(source)) {
|
|
43
|
+
const targetHooks = result[eventType];
|
|
44
|
+
const sourceHooks = source[eventType];
|
|
45
|
+
if (targetHooks && sourceHooks) {
|
|
46
|
+
// Append source hooks to existing event type
|
|
47
|
+
result[eventType] = [...targetHooks, ...sourceHooks];
|
|
48
|
+
}
|
|
49
|
+
else if (sourceHooks) {
|
|
50
|
+
// New event type
|
|
51
|
+
result[eventType] = sourceHooks;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Merge settings from a source settings.json file into the IDE settings file.
|
|
58
|
+
* Reads from the provided source path and merges into .claude/settings.json at project root.
|
|
59
|
+
*
|
|
60
|
+
* @param targetDir - Project root directory
|
|
61
|
+
* @param sourceSettingsPath - Absolute path to source settings.json file
|
|
62
|
+
* @returns true if merge successful, false otherwise
|
|
63
|
+
*/
|
|
64
|
+
async function mergeSharedSettingsFromSource(targetDir, sourceSettingsPath) {
|
|
65
|
+
const resolver = new IdePathResolver(targetDir);
|
|
66
|
+
const ideSettingsPath = resolver.getClaudeSettings();
|
|
67
|
+
// Check if source settings exists
|
|
68
|
+
if (!(await pathExists(sourceSettingsPath))) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// Read source settings
|
|
73
|
+
const sourceContent = await fs.readFile(sourceSettingsPath, 'utf8');
|
|
74
|
+
const sourceSettings = JSON.parse(sourceContent);
|
|
75
|
+
// Read IDE settings (create empty object if doesn't exist)
|
|
76
|
+
let ideSettings = {};
|
|
77
|
+
if (await pathExists(ideSettingsPath)) {
|
|
78
|
+
const ideContent = await fs.readFile(ideSettingsPath, 'utf8');
|
|
79
|
+
ideSettings = JSON.parse(ideContent);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Create .claude directory if it doesn't exist
|
|
83
|
+
await fs.mkdir(dirname(ideSettingsPath), { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
// Merge source settings into IDE settings
|
|
86
|
+
const mergedSettings = deepMergeSettings(ideSettings, sourceSettings);
|
|
87
|
+
// Write merged settings back
|
|
88
|
+
await fs.writeFile(ideSettingsPath, JSON.stringify(mergedSettings, null, 4) + '\n', 'utf8');
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Silently fail on parse/write errors
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if a path exists
|
|
98
|
+
*/
|
|
99
|
+
async function pathExists(path) {
|
|
100
|
+
try {
|
|
101
|
+
await fs.access(path);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check template installation status for a method.
|
|
110
|
+
* Returns which items exist and which are missing.
|
|
111
|
+
*
|
|
112
|
+
* @param templatePath - Path to the template directory
|
|
113
|
+
* @param targetDir - Target directory to check
|
|
114
|
+
* @param ides - List of IDEs to check (for dot folders)
|
|
115
|
+
* @param templateName - Name of the template (for identifying workflow folder)
|
|
116
|
+
* @returns Status of template items
|
|
117
|
+
*/
|
|
118
|
+
export async function checkTemplateStatus(templatePath, targetDir, ides, templateName) {
|
|
119
|
+
const existing = [];
|
|
120
|
+
const missing = [];
|
|
121
|
+
// Scan template directory
|
|
122
|
+
const entries = await fs.readdir(templatePath, { withFileTypes: true });
|
|
123
|
+
// Identify workflow folder based on template name
|
|
124
|
+
// Convention: _templatename (e.g., _gsd, _bmad)
|
|
125
|
+
const workflowFolderName = `_${templateName}`;
|
|
126
|
+
let workflowFolder = null;
|
|
127
|
+
let workflowFolderExists = false;
|
|
128
|
+
// Filter entries to only include relevant items (skip non-selected IDE folders and excluded patterns)
|
|
129
|
+
const relevantEntries = entries.filter((entry) => {
|
|
130
|
+
// Skip excluded patterns (test files, cache, etc.)
|
|
131
|
+
if (shouldExclude(entry.name)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (entry.name.startsWith('.') && entry.isDirectory()) {
|
|
135
|
+
const ideName = entry.name.slice(1);
|
|
136
|
+
return ides.includes(ideName);
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
});
|
|
140
|
+
// Check all entries in parallel
|
|
141
|
+
// Non-dot folders go into .aiwcli/, dot folders stay at project root
|
|
142
|
+
const resolver = new IdePathResolver(targetDir);
|
|
143
|
+
const containerDir = resolver.getAiwcliContainer();
|
|
144
|
+
const statusChecks = relevantEntries.map(async (entry) => {
|
|
145
|
+
// Dot folders (IDE folders) are at project root, non-dot folders are in .aiwcli/
|
|
146
|
+
const targetPath = entry.name.startsWith('.')
|
|
147
|
+
? resolver.getIdeDir(entry.name.slice(1))
|
|
148
|
+
: join(containerDir, entry.name);
|
|
149
|
+
const exists = await pathExists(targetPath);
|
|
150
|
+
return {
|
|
151
|
+
name: entry.name,
|
|
152
|
+
isDirectory: entry.isDirectory(),
|
|
153
|
+
exists,
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
const statuses = await Promise.all(statusChecks);
|
|
157
|
+
for (const status of statuses) {
|
|
158
|
+
if (status.exists) {
|
|
159
|
+
existing.push(status);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
missing.push(status);
|
|
163
|
+
}
|
|
164
|
+
// Track workflow folder
|
|
165
|
+
if (status.name === workflowFolderName) {
|
|
166
|
+
workflowFolder = workflowFolderName;
|
|
167
|
+
workflowFolderExists = status.exists;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
existing,
|
|
172
|
+
missing,
|
|
173
|
+
workflowFolder,
|
|
174
|
+
workflowFolderExists,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Patterns to exclude when copying template directories.
|
|
179
|
+
* These are development/test artifacts that shouldn't be packaged.
|
|
180
|
+
*/
|
|
181
|
+
const EXCLUDED_PATTERNS = [
|
|
182
|
+
'_output',
|
|
183
|
+
'__pycache__',
|
|
184
|
+
'.pytest_cache',
|
|
185
|
+
'conftest.py',
|
|
186
|
+
/^test_.*\.py$/,
|
|
187
|
+
/.*\.pyc$/,
|
|
188
|
+
];
|
|
189
|
+
/**
|
|
190
|
+
* Check if a filename should be excluded from copying
|
|
191
|
+
*/
|
|
192
|
+
function shouldExclude(name) {
|
|
193
|
+
return EXCLUDED_PATTERNS.some((pattern) => {
|
|
194
|
+
if (typeof pattern === 'string') {
|
|
195
|
+
return name === pattern;
|
|
196
|
+
}
|
|
197
|
+
return pattern.test(name);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Copy directory recursively with proper error handling.
|
|
202
|
+
* Excludes test files, cache directories, and output folders.
|
|
203
|
+
*
|
|
204
|
+
* @param src - Source directory path
|
|
205
|
+
* @param dest - Destination directory path
|
|
206
|
+
* @param excludeIdeFolders - If true, exclude IDE config folders (.claude, .windsurf, etc.)
|
|
207
|
+
*/
|
|
208
|
+
export async function copyDir(src, dest, excludeIdeFolders = false) {
|
|
209
|
+
await fs.mkdir(dest, { recursive: true });
|
|
210
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
211
|
+
const operations = entries
|
|
212
|
+
.filter((entry) => {
|
|
213
|
+
// Standard exclusions (test files, cache, etc.)
|
|
214
|
+
if (shouldExclude(entry.name)) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
// Exclude IDE config folders if requested (used for _shared folder)
|
|
218
|
+
// These folders are used for settings merging, not direct installation
|
|
219
|
+
if (excludeIdeFolders && entry.isDirectory() && entry.name.startsWith('.')) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
})
|
|
224
|
+
.map(async (entry) => {
|
|
225
|
+
const srcPath = join(src, entry.name);
|
|
226
|
+
const destPath = join(dest, entry.name);
|
|
227
|
+
try {
|
|
228
|
+
return entry.isDirectory() ? await copyDir(srcPath, destPath, excludeIdeFolders) : await fs.copyFile(srcPath, destPath);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const err = error;
|
|
232
|
+
throw new Error(`Failed to copy ${srcPath} to ${destPath}: ${err.message}`);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
await Promise.all(operations);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Install template with IDE-specific folder selection.
|
|
239
|
+
* Supports selective installation - only installs items that don't already exist.
|
|
240
|
+
*
|
|
241
|
+
* Template structure:
|
|
242
|
+
* - Non-dot folders (e.g., _bmad/, GSR/) are installed if not already present
|
|
243
|
+
* - Dot folders (e.g., .claude/, .windsurf/) are installed only if matching IDE flag and not already present
|
|
244
|
+
*
|
|
245
|
+
* @param config - Installation configuration
|
|
246
|
+
* @param skipExisting - If true, skip items that already exist (default: true for regeneration support)
|
|
247
|
+
* @returns Installation result with list of installed and skipped folders
|
|
248
|
+
* @throws Error if template doesn't exist or requested IDE folder not found
|
|
249
|
+
*/
|
|
250
|
+
export async function installTemplate(config, skipExisting = true) {
|
|
251
|
+
const { templateName, targetDir, ides, templatePath } = config;
|
|
252
|
+
// Verify template exists
|
|
253
|
+
try {
|
|
254
|
+
await fs.access(templatePath);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
throw new Error(`Template '${templateName}' not found at ${templatePath}. ` +
|
|
258
|
+
`This indicates a corrupted installation. Please reinstall aiwcli.`);
|
|
259
|
+
}
|
|
260
|
+
// Scan template directory to classify folders (excluding test/cache patterns)
|
|
261
|
+
const entries = await fs.readdir(templatePath, { withFileTypes: true });
|
|
262
|
+
const directories = entries.filter((entry) => entry.isDirectory() && !shouldExclude(entry.name));
|
|
263
|
+
const nonDotFolders = [];
|
|
264
|
+
const dotFolders = new Map(); // ide name -> folder name
|
|
265
|
+
for (const dir of directories) {
|
|
266
|
+
if (dir.name.startsWith('.')) {
|
|
267
|
+
// Extract IDE name from dot folder (e.g., '.claude' -> 'claude')
|
|
268
|
+
const ideName = dir.name.slice(1);
|
|
269
|
+
dotFolders.set(ideName, dir.name);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
nonDotFolders.push(dir.name);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Validate requested IDE folders exist in template
|
|
276
|
+
const availableIdes = [...dotFolders.keys()];
|
|
277
|
+
const missingIdes = ides.filter((ide) => !dotFolders.has(ide));
|
|
278
|
+
if (missingIdes.length > 0) {
|
|
279
|
+
throw new Error(`IDE '${missingIdes[0]}' not available for template '${templateName}'. ` +
|
|
280
|
+
`Available: ${availableIdes.join(', ')}`);
|
|
281
|
+
}
|
|
282
|
+
const installedFolders = [];
|
|
283
|
+
const skippedFolders = [];
|
|
284
|
+
const mergedFolders = [];
|
|
285
|
+
let mergedFileCount = 0;
|
|
286
|
+
// Create .aiwcli container folder for method-specific files
|
|
287
|
+
const resolver = new IdePathResolver(targetDir);
|
|
288
|
+
const containerDir = resolver.getAiwcliContainer();
|
|
289
|
+
await fs.mkdir(containerDir, { recursive: true });
|
|
290
|
+
// Install non-dot folders into .aiwcli/ container (skip if already exist and skipExisting is true)
|
|
291
|
+
const nonDotInstalls = nonDotFolders.map(async (folder) => {
|
|
292
|
+
const srcPath = join(templatePath, folder);
|
|
293
|
+
// Destination is inside .aiwcli/ container
|
|
294
|
+
const destPath = join(containerDir, folder);
|
|
295
|
+
if (skipExisting && (await pathExists(destPath))) {
|
|
296
|
+
return { folder, skipped: true };
|
|
297
|
+
}
|
|
298
|
+
await copyDir(srcPath, destPath);
|
|
299
|
+
return { folder, skipped: false };
|
|
300
|
+
});
|
|
301
|
+
const nonDotResults = await Promise.all(nonDotInstalls);
|
|
302
|
+
for (const result of nonDotResults) {
|
|
303
|
+
if (result.skipped) {
|
|
304
|
+
skippedFolders.push(result.folder);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
installedFolders.push(result.folder);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Install root-level _shared directory (shared across all templates)
|
|
311
|
+
// This is at templates/_shared, not inside the specific template directory
|
|
312
|
+
// Exclude IDE config folders (.claude, .windsurf) - they are used for settings merging only
|
|
313
|
+
const templatesRoot = dirname(templatePath);
|
|
314
|
+
const rootSharedSrc = join(templatesRoot, '_shared');
|
|
315
|
+
const rootSharedDest = join(containerDir, '_shared');
|
|
316
|
+
if (await pathExists(rootSharedSrc)) {
|
|
317
|
+
if (skipExisting && (await pathExists(rootSharedDest))) {
|
|
318
|
+
skippedFolders.push('_shared');
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
await copyDir(rootSharedSrc, rootSharedDest, true); // excludeIdeFolders = true
|
|
322
|
+
installedFolders.push('_shared');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Install matching IDE folders
|
|
326
|
+
// If folder exists, merge content recursively by looking for method name folders
|
|
327
|
+
const ideInstalls = ides.map(async (ide) => {
|
|
328
|
+
const folderName = dotFolders.get(ide);
|
|
329
|
+
if (folderName) {
|
|
330
|
+
const srcPath = join(templatePath, folderName);
|
|
331
|
+
const destPath = resolver.getIdeDir(ide);
|
|
332
|
+
if (await pathExists(destPath)) {
|
|
333
|
+
if (skipExisting) {
|
|
334
|
+
// Folder exists - merge template content by finding method-named folders
|
|
335
|
+
const mergeResult = await mergeTemplateContent(srcPath, destPath, templateName);
|
|
336
|
+
return {
|
|
337
|
+
folder: folderName,
|
|
338
|
+
skipped: false,
|
|
339
|
+
merged: true,
|
|
340
|
+
mergedFiles: mergeResult.copiedFiles.length,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
// skipExisting is false, so overwrite
|
|
344
|
+
await copyDir(srcPath, destPath);
|
|
345
|
+
return { folder: folderName, skipped: false, merged: false, mergedFiles: 0 };
|
|
346
|
+
}
|
|
347
|
+
await copyDir(srcPath, destPath);
|
|
348
|
+
return { folder: folderName, skipped: false, merged: false, mergedFiles: 0 };
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
});
|
|
352
|
+
const ideResults = (await Promise.all(ideInstalls)).filter((result) => result !== null);
|
|
353
|
+
for (const result of ideResults) {
|
|
354
|
+
if (result.merged) {
|
|
355
|
+
mergedFolders.push(result.folder);
|
|
356
|
+
mergedFileCount += result.mergedFiles;
|
|
357
|
+
}
|
|
358
|
+
else if (result.skipped) {
|
|
359
|
+
skippedFolders.push(result.folder);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
installedFolders.push(result.folder);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Settings merging is now handled by the caller via mergeMethodsSettings()
|
|
366
|
+
// This allows unified merging of _shared + method-specific settings
|
|
367
|
+
return {
|
|
368
|
+
installedFolders,
|
|
369
|
+
skippedFolders,
|
|
370
|
+
mergedFolders,
|
|
371
|
+
mergedFileCount,
|
|
372
|
+
sharedSettingsMerged: false, // Deprecated, kept for backwards compatibility
|
|
373
|
+
templatePath,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Linter
|
|
3
|
+
*
|
|
4
|
+
* Validates workflow markdown files within templates for:
|
|
5
|
+
* - Correct output paths (_output/{method}/)
|
|
6
|
+
* - No cross-method contamination (gsd referencing bmad paths)
|
|
7
|
+
* - Proper template references (_{method}/templates/)
|
|
8
|
+
* - Correct workflow references (/method:workflow)
|
|
9
|
+
*/
|
|
10
|
+
export interface LintViolation {
|
|
11
|
+
file: string;
|
|
12
|
+
line: number;
|
|
13
|
+
match: string;
|
|
14
|
+
message: string;
|
|
15
|
+
rule: string;
|
|
16
|
+
}
|
|
17
|
+
export interface LintRule {
|
|
18
|
+
description: string;
|
|
19
|
+
/** Message template - use {match} for the matched text */
|
|
20
|
+
message: string;
|
|
21
|
+
name: string;
|
|
22
|
+
/** Regex to find violations - matches indicate problems */
|
|
23
|
+
pattern: RegExp;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate lint rules for a specific method
|
|
27
|
+
* Rules detect when a method's workflows reference other methods incorrectly
|
|
28
|
+
*/
|
|
29
|
+
export declare function getRulesForMethod(method: string): LintRule[];
|
|
30
|
+
/**
|
|
31
|
+
* Lint a single file's content
|
|
32
|
+
*/
|
|
33
|
+
export declare function lintFileContent(content: string, filePath: string, rules: LintRule[]): LintViolation[];
|
|
34
|
+
/**
|
|
35
|
+
* Get all markdown files in a template method directory
|
|
36
|
+
*/
|
|
37
|
+
export declare function getTemplateMarkdownFiles(templatesDir: string, method: string): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Lint all markdown files for a specific template method
|
|
40
|
+
*/
|
|
41
|
+
export declare function lintTemplateMethod(templatesDir: string, method: string): LintViolation[];
|
|
42
|
+
/**
|
|
43
|
+
* Lint all template methods in a templates directory
|
|
44
|
+
*/
|
|
45
|
+
export declare function lintAllTemplates(templatesDir: string): Map<string, LintViolation[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Format violations for display
|
|
48
|
+
*/
|
|
49
|
+
export declare function formatViolations(violations: LintViolation[], basePath?: string): string;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Linter
|
|
3
|
+
*
|
|
4
|
+
* Validates workflow markdown files within templates for:
|
|
5
|
+
* - Correct output paths (_output/{method}/)
|
|
6
|
+
* - No cross-method contamination (gsd referencing bmad paths)
|
|
7
|
+
* - Proper template references (_{method}/templates/)
|
|
8
|
+
* - Correct workflow references (/method:workflow)
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
/**
|
|
13
|
+
* Escape special regex characters in a string
|
|
14
|
+
*/
|
|
15
|
+
function escapeRegex(str) {
|
|
16
|
+
return str.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw `\$&`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate lint rules for a specific method
|
|
20
|
+
* Rules detect when a method's workflows reference other methods incorrectly
|
|
21
|
+
*/
|
|
22
|
+
export function getRulesForMethod(method) {
|
|
23
|
+
// Known methods to check against
|
|
24
|
+
const allMethods = ['cc-native'];
|
|
25
|
+
const otherMethods = allMethods.filter((m) => m !== method);
|
|
26
|
+
// Create regex pattern that matches other methods but not the current one
|
|
27
|
+
const otherMethodsPattern = otherMethods.map((m) => escapeRegex(m)).join('|');
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
description: `Output paths should use _output/${method}/, not other methods`,
|
|
31
|
+
message: `Wrong method in output path: {match} (should be _output/${method}/)`,
|
|
32
|
+
name: 'wrong-output-path',
|
|
33
|
+
pattern: new RegExp(`_output/(${otherMethodsPattern})/`, 'g'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
description: `Template references should use _${method}/templates/, not other methods`,
|
|
37
|
+
message: `Wrong method in template reference: {match} (should be _${method}/templates/)`,
|
|
38
|
+
name: 'wrong-template-ref',
|
|
39
|
+
pattern: new RegExp(`_(${otherMethodsPattern})/templates/`, 'g'),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
description: `Workflow references should use /${method}:, not other methods`,
|
|
43
|
+
message: `Wrong method in workflow reference: {match} (should be /${method}:)`,
|
|
44
|
+
name: 'wrong-workflow-ref',
|
|
45
|
+
pattern: new RegExp(`/(${otherMethodsPattern}):`, 'g'),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
description: `Method folder references should use _${method}/, not other methods`,
|
|
49
|
+
message: `Wrong method in folder reference: {match} (should be _${method}/workflows/)`,
|
|
50
|
+
name: 'wrong-method-folder',
|
|
51
|
+
pattern: new RegExp(`_(${otherMethodsPattern})/workflows/`, 'g'),
|
|
52
|
+
},
|
|
53
|
+
// Note: bare-output-file rule removed as it produces too many false positives
|
|
54
|
+
// in README documentation that explains workflow outputs.
|
|
55
|
+
// The cross-method contamination rules are the primary value of this linter.
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check a single line for violations of a rule
|
|
60
|
+
*/
|
|
61
|
+
function checkLineForViolations(line, lineNum, filePath, rule) {
|
|
62
|
+
const violations = [];
|
|
63
|
+
// Skip code block fences and comments
|
|
64
|
+
if (line.trim().startsWith('```') || line.trim().startsWith('<!--')) {
|
|
65
|
+
return violations;
|
|
66
|
+
}
|
|
67
|
+
// Reset regex for this line
|
|
68
|
+
rule.pattern.lastIndex = 0;
|
|
69
|
+
let match;
|
|
70
|
+
while ((match = rule.pattern.exec(line)) !== null) {
|
|
71
|
+
violations.push({
|
|
72
|
+
file: filePath,
|
|
73
|
+
line: lineNum + 1,
|
|
74
|
+
match: match[0],
|
|
75
|
+
message: rule.message.replace('{match}', match[0]),
|
|
76
|
+
rule: rule.name,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return violations;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Lint a single file's content
|
|
83
|
+
*/
|
|
84
|
+
export function lintFileContent(content, filePath, rules) {
|
|
85
|
+
const violations = [];
|
|
86
|
+
const lines = content.split('\n');
|
|
87
|
+
for (const rule of rules) {
|
|
88
|
+
// Reset regex state for each rule
|
|
89
|
+
rule.pattern.lastIndex = 0;
|
|
90
|
+
for (const [lineNum, line] of lines.entries()) {
|
|
91
|
+
const lineViolations = checkLineForViolations(line, lineNum, filePath, rule);
|
|
92
|
+
violations.push(...lineViolations);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return violations;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get all markdown files in a template method directory
|
|
99
|
+
*/
|
|
100
|
+
export function getTemplateMarkdownFiles(templatesDir, method) {
|
|
101
|
+
const methodDir = path.join(templatesDir, method);
|
|
102
|
+
const files = [];
|
|
103
|
+
if (!existsSync(methodDir)) {
|
|
104
|
+
return files;
|
|
105
|
+
}
|
|
106
|
+
function walkDir(dir) {
|
|
107
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const fullPath = path.join(dir, entry.name);
|
|
110
|
+
if (entry.isDirectory()) {
|
|
111
|
+
// Skip node_modules and hidden dirs (except .claude, .windsurf, .cursor)
|
|
112
|
+
if (entry.name === 'node_modules' ||
|
|
113
|
+
(entry.name.startsWith('.') &&
|
|
114
|
+
!entry.name.startsWith('.claude') &&
|
|
115
|
+
!entry.name.startsWith('.windsurf') &&
|
|
116
|
+
!entry.name.startsWith('.cursor'))) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
walkDir(fullPath);
|
|
120
|
+
}
|
|
121
|
+
else if (entry.name.endsWith('.md') && !entry.name.endsWith('.template')) {
|
|
122
|
+
// Only lint .md files, not .md.template files (those have placeholders)
|
|
123
|
+
files.push(fullPath);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
walkDir(methodDir);
|
|
128
|
+
return files;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Lint all markdown files for a specific template method
|
|
132
|
+
*/
|
|
133
|
+
export function lintTemplateMethod(templatesDir, method) {
|
|
134
|
+
const rules = getRulesForMethod(method);
|
|
135
|
+
const files = getTemplateMarkdownFiles(templatesDir, method);
|
|
136
|
+
const allViolations = [];
|
|
137
|
+
for (const filePath of files) {
|
|
138
|
+
const content = readFileSync(filePath, 'utf8');
|
|
139
|
+
const violations = lintFileContent(content, filePath, rules);
|
|
140
|
+
allViolations.push(...violations);
|
|
141
|
+
}
|
|
142
|
+
return allViolations;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Lint all template methods in a templates directory
|
|
146
|
+
*/
|
|
147
|
+
export function lintAllTemplates(templatesDir) {
|
|
148
|
+
const results = new Map();
|
|
149
|
+
const methods = ['cc-native'];
|
|
150
|
+
for (const method of methods) {
|
|
151
|
+
const methodDir = path.join(templatesDir, method);
|
|
152
|
+
if (existsSync(methodDir)) {
|
|
153
|
+
results.set(method, lintTemplateMethod(templatesDir, method));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return results;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Format violations for display
|
|
160
|
+
*/
|
|
161
|
+
export function formatViolations(violations, basePath) {
|
|
162
|
+
if (violations.length === 0) {
|
|
163
|
+
return 'No violations found.';
|
|
164
|
+
}
|
|
165
|
+
return violations
|
|
166
|
+
.map((v) => {
|
|
167
|
+
const relativePath = basePath
|
|
168
|
+
? path.relative(basePath, v.file)
|
|
169
|
+
: v.file;
|
|
170
|
+
return `${relativePath}:${v.line} [${v.rule}] ${v.message}`;
|
|
171
|
+
})
|
|
172
|
+
.join('\n');
|
|
173
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content folder types that should be recursively merged rather than skipped.
|
|
3
|
+
* These are the canonical folder names used in templates for organizing content.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CONTENT_FOLDER_TYPES: readonly ["agents", "commands", "workflows", "tasks"];
|
|
6
|
+
/**
|
|
7
|
+
* Result of merging content folders
|
|
8
|
+
*/
|
|
9
|
+
export interface MergeResult {
|
|
10
|
+
/** Files that were copied */
|
|
11
|
+
copiedFiles: string[];
|
|
12
|
+
/** Directories that were created */
|
|
13
|
+
createdDirs: string[];
|
|
14
|
+
/** Files that were skipped (already exist) */
|
|
15
|
+
skippedFiles: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recursively find folders matching the method name within a directory tree.
|
|
19
|
+
* This finds folders like `.claude/commands/bmad/` when methodName is 'bmad'.
|
|
20
|
+
*
|
|
21
|
+
* @param dir - Directory to search
|
|
22
|
+
* @param methodName - Method name to look for (e.g., 'bmad', 'gsd')
|
|
23
|
+
* @returns Array of paths to matching folders
|
|
24
|
+
*/
|
|
25
|
+
export declare function findMethodFolders(dir: string, methodName: string): Promise<string[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Merge template content into an existing IDE folder.
|
|
28
|
+
* Recursively finds folders matching the method name and merges their content.
|
|
29
|
+
*
|
|
30
|
+
* This enables adding new agents, commands, workflows, and tasks from a template
|
|
31
|
+
* even when the IDE folder (e.g., .claude) already exists.
|
|
32
|
+
*
|
|
33
|
+
* @param templateIdePath - Path to the template's IDE folder (e.g., template/.claude)
|
|
34
|
+
* @param targetIdePath - Path to the target's IDE folder (e.g., project/.claude)
|
|
35
|
+
* @param methodName - Method name to look for (e.g., 'bmad', 'gsd')
|
|
36
|
+
* @returns Merge results
|
|
37
|
+
*/
|
|
38
|
+
export declare function mergeTemplateContent(templateIdePath: string, targetIdePath: string, methodName: string): Promise<MergeResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Merge content type folders (agents, commands, workflows, tasks) from template to target.
|
|
41
|
+
* This is an alternative approach that looks for specific folder types rather than method names.
|
|
42
|
+
*
|
|
43
|
+
* @param templateIdePath - Path to the template's IDE folder
|
|
44
|
+
* @param targetIdePath - Path to the target's IDE folder
|
|
45
|
+
* @returns Merge results
|
|
46
|
+
*/
|
|
47
|
+
export declare function mergeContentTypeFolders(templateIdePath: string, targetIdePath: string): Promise<MergeResult>;
|