aiwcli 0.10.1 → 0.10.3
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/dist/commands/clean.js +1 -0
- package/dist/commands/clear.d.ts +19 -2
- package/dist/commands/clear.js +351 -160
- package/dist/commands/init/index.d.ts +1 -17
- package/dist/commands/init/index.js +19 -104
- package/dist/lib/gitignore-manager.d.ts +9 -0
- package/dist/lib/gitignore-manager.js +121 -0
- package/dist/lib/template-installer.d.ts +7 -12
- package/dist/lib/template-installer.js +69 -193
- package/dist/lib/template-settings-reconstructor.d.ts +35 -0
- package/dist/lib/template-settings-reconstructor.js +130 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +10 -2
- package/dist/templates/_shared/hooks/session_end.py +37 -29
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/hook_utils.py +8 -10
- package/dist/templates/_shared/lib/base/inference.py +51 -62
- package/dist/templates/_shared/lib/base/logger.py +35 -21
- package/dist/templates/_shared/lib/base/stop_words.py +8 -0
- package/dist/templates/_shared/lib/base/utils.py +29 -8
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/plan_manager.py +101 -2
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
- package/dist/templates/_shared/lib-ts/base/constants.ts +299 -0
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +360 -0
- package/dist/templates/_shared/lib-ts/base/inference.ts +245 -0
- package/dist/templates/_shared/lib-ts/base/logger.ts +234 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +114 -0
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +23 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +432 -0
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +497 -0
- package/dist/templates/_shared/lib-ts/context/context-store.ts +679 -0
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +292 -0
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +181 -0
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +215 -0
- package/dist/templates/_shared/lib-ts/package.json +21 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -0
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +65 -0
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
- package/dist/templates/_shared/lib-ts/types.ts +151 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
- package/dist/templates/_shared/scripts/status_line.py +17 -2
- package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -1
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
- package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
- package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
- package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
- package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -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/cc-native-plan-review.py +125 -40
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +57 -13
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +11 -7
- package/oclif.manifest.json +17 -2
- package/package.json +1 -1
- package/dist/lib/template-merger.d.ts +0 -47
- package/dist/lib/template-merger.js +0 -162
- package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
- package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
- package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
- package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
- package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
- package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
- package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
- package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
- package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
|
@@ -4,16 +4,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { checkbox, confirm, input, select } from '@inquirer/prompts';
|
|
5
5
|
import { Flags } from '@oclif/core';
|
|
6
6
|
import BaseCommand from '../../lib/base-command.js';
|
|
7
|
-
import { detectUsername } from '../../lib/user-utils.js';
|
|
8
7
|
import { updateGitignore } from '../../lib/gitignore-manager.js';
|
|
9
|
-
import { mergeClaudeSettings } from '../../lib/hooks-merger.js';
|
|
10
8
|
import { IdePathResolver } from '../../lib/ide-path-resolver.js';
|
|
11
9
|
import { pathExists } from '../../lib/paths.js';
|
|
12
10
|
import { getTargetSettingsFile, readClaudeSettings, writeClaudeSettings } from '../../lib/settings-hierarchy.js';
|
|
13
11
|
import { checkTemplateStatus, installTemplate, shouldExclude } from '../../lib/template-installer.js';
|
|
14
12
|
import { getAvailableTemplates, getTemplatePath } from '../../lib/template-resolver.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { reconstructIdeSettings } from '../../lib/template-settings-reconstructor.js';
|
|
14
|
+
import { detectUsername } from '../../lib/user-utils.js';
|
|
17
15
|
import { EXIT_CODES } from '../../types/exit-codes.js';
|
|
18
16
|
/**
|
|
19
17
|
* Available IDEs for configuration
|
|
@@ -93,7 +91,10 @@ export default class Init extends BaseCommand {
|
|
|
93
91
|
await fs.mkdir(containerDir, { recursive: true });
|
|
94
92
|
const sharedDestPath = resolver.getSharedFolder();
|
|
95
93
|
const sharedExists = await pathExists(sharedDestPath);
|
|
96
|
-
if (
|
|
94
|
+
if (sharedExists) {
|
|
95
|
+
this.logInfo('✓ _shared folder already exists - skipping');
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
97
98
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
98
99
|
const currentDir = dirname(currentFilePath);
|
|
99
100
|
const templatesRoot = join(dirname(dirname(currentDir)), 'templates');
|
|
@@ -106,11 +107,8 @@ export default class Init extends BaseCommand {
|
|
|
106
107
|
await this.copyDirectory(sharedSrcPath, sharedDestPath, true);
|
|
107
108
|
this.logSuccess('✓ Installed _shared folder');
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
// Merge settings from _shared template
|
|
113
|
-
await this.mergeMethodsSettings(targetDir, ['_shared'], ['claude']);
|
|
110
|
+
// Reconstruct settings from _shared template
|
|
111
|
+
await reconstructIdeSettings(targetDir, [], ['claude']);
|
|
114
112
|
// Update .gitignore if git repository exists
|
|
115
113
|
if (hasGit) {
|
|
116
114
|
await updateGitignore(targetDir, ['.aiwcli']);
|
|
@@ -169,7 +167,7 @@ export default class Init extends BaseCommand {
|
|
|
169
167
|
// Still update gitignore and merge hooks if needed
|
|
170
168
|
}
|
|
171
169
|
this.log('');
|
|
172
|
-
// Install template
|
|
170
|
+
// Install template (always overwrites method-owned content)
|
|
173
171
|
const result = await installTemplate({
|
|
174
172
|
templateName: method,
|
|
175
173
|
targetDir,
|
|
@@ -177,7 +175,7 @@ export default class Init extends BaseCommand {
|
|
|
177
175
|
username,
|
|
178
176
|
projectName,
|
|
179
177
|
templatePath,
|
|
180
|
-
}
|
|
178
|
+
});
|
|
181
179
|
// Collect all folders that need gitignore entries
|
|
182
180
|
// The .aiwcli/ container holds all template infrastructure and runtime data
|
|
183
181
|
const foldersForGitignore = ['.aiwcli'];
|
|
@@ -185,12 +183,6 @@ export default class Init extends BaseCommand {
|
|
|
185
183
|
if (result.installedFolders.length > 0) {
|
|
186
184
|
this.logSuccess(`✓ Installed: ${result.installedFolders.join(', ')}`);
|
|
187
185
|
}
|
|
188
|
-
if (result.mergedFolders.length > 0) {
|
|
189
|
-
this.logSuccess(`✓ Merged content into: ${result.mergedFolders.join(', ')} (${result.mergedFileCount} files)`);
|
|
190
|
-
}
|
|
191
|
-
if (result.skippedFolders.length > 0) {
|
|
192
|
-
this.logInfo(`✓ Skipped (already exist): ${result.skippedFolders.join(', ')}`);
|
|
193
|
-
}
|
|
194
186
|
// Perform post-installation actions (settings tracking, hook merging, gitignore updates)
|
|
195
187
|
await this.performPostInstallActions({
|
|
196
188
|
targetDir,
|
|
@@ -271,94 +263,12 @@ export default class Init extends BaseCommand {
|
|
|
271
263
|
};
|
|
272
264
|
return descriptions[template] || 'Custom template';
|
|
273
265
|
}
|
|
274
|
-
/**
|
|
275
|
-
* Merge settings from multiple method templates into project settings.
|
|
276
|
-
* Processes methods in order, allowing later methods to override earlier ones.
|
|
277
|
-
*
|
|
278
|
-
* @param targetDir - Project directory
|
|
279
|
-
* @param methods - Array of method names to merge (e.g., ['_shared', 'cc-native'])
|
|
280
|
-
* @param ides - IDEs being configured (for IDE-specific merging)
|
|
281
|
-
*/
|
|
282
|
-
async mergeMethodsSettings(targetDir, methods, ides) {
|
|
283
|
-
const targetSettingsPath = getTargetSettingsFile(targetDir);
|
|
284
|
-
let projectSettings = (await readClaudeSettings(targetSettingsPath)) || {};
|
|
285
|
-
for (const method of methods) {
|
|
286
|
-
try {
|
|
287
|
-
// Get template path for this method
|
|
288
|
-
let templatePath;
|
|
289
|
-
if (method === '_shared') {
|
|
290
|
-
// Special case: _shared is at templates/_shared
|
|
291
|
-
const currentFilePath = fileURLToPath(import.meta.url);
|
|
292
|
-
const currentDir = dirname(currentFilePath);
|
|
293
|
-
const templatesRoot = join(dirname(dirname(currentDir)), 'templates');
|
|
294
|
-
templatePath = join(templatesRoot, '_shared');
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
// Named method templates
|
|
298
|
-
templatePath = await getTemplatePath(method);
|
|
299
|
-
}
|
|
300
|
-
// Merge Claude settings if claude IDE is selected
|
|
301
|
-
if (ides.includes('claude')) {
|
|
302
|
-
const templateSettingsPath = join(templatePath, '.claude', 'settings.json');
|
|
303
|
-
const templateSettings = await readClaudeSettings(templateSettingsPath);
|
|
304
|
-
if (templateSettings) {
|
|
305
|
-
projectSettings = mergeClaudeSettings(projectSettings, templateSettings);
|
|
306
|
-
this.logSuccess(`✓ Merged ${method} settings into .claude/settings.json`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
// Merge Windsurf hooks if windsurf IDE is selected
|
|
310
|
-
if (ides.includes('windsurf')) {
|
|
311
|
-
await this.mergeWindsurfTemplateHooks(targetDir, templatePath);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
const err = error;
|
|
316
|
-
this.warn(`Failed to merge ${method} settings: ${err.message}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
// Write merged Claude settings
|
|
320
|
-
if (ides.includes('claude')) {
|
|
321
|
-
await writeClaudeSettings(targetSettingsPath, projectSettings);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Merge Windsurf template hooks into project hooks
|
|
326
|
-
*
|
|
327
|
-
* @param targetDir - Project directory
|
|
328
|
-
* @param templatePath - Template source path
|
|
329
|
-
*/
|
|
330
|
-
async mergeWindsurfTemplateHooks(targetDir, templatePath) {
|
|
331
|
-
try {
|
|
332
|
-
// Read template hooks
|
|
333
|
-
const templateHooksPath = join(templatePath, '.windsurf', 'hooks.json');
|
|
334
|
-
const templateHooks = await readWindsurfHooks(templateHooksPath);
|
|
335
|
-
// If template has no hooks, nothing to merge
|
|
336
|
-
if (!templateHooks || !templateHooks.hooks || Object.keys(templateHooks.hooks).length === 0) {
|
|
337
|
-
this.logInfo('No Windsurf hooks in template to merge');
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
// Get target hooks file path
|
|
341
|
-
const targetHooksPath = getTargetHooksFile(targetDir);
|
|
342
|
-
// Read existing project hooks
|
|
343
|
-
const existingHooks = await readWindsurfHooks(targetHooksPath);
|
|
344
|
-
// Merge hooks
|
|
345
|
-
const mergedHooks = mergeWindsurfHooks(existingHooks, templateHooks);
|
|
346
|
-
// Write merged hooks
|
|
347
|
-
await writeWindsurfHooks(targetHooksPath, mergedHooks);
|
|
348
|
-
this.logSuccess('✓ Windsurf template hooks merged into project hooks');
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
const err = error;
|
|
352
|
-
this.warn(`Failed to merge Windsurf template hooks: ${err.message}`);
|
|
353
|
-
// Don't fail the entire installation if hook merging fails
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
266
|
/**
|
|
357
267
|
* Perform post-installation actions.
|
|
358
268
|
*
|
|
359
269
|
* Handles:
|
|
360
270
|
* - Method tracking in settings.json
|
|
361
|
-
* - Settings
|
|
271
|
+
* - Settings reconstruction from all active templates
|
|
362
272
|
* - .gitignore updates
|
|
363
273
|
*
|
|
364
274
|
* @param config - Post-install configuration
|
|
@@ -370,10 +280,15 @@ export default class Init extends BaseCommand {
|
|
|
370
280
|
*/
|
|
371
281
|
async performPostInstallActions(config) {
|
|
372
282
|
const { targetDir, method, ides, hasGit, foldersForGitignore } = config;
|
|
373
|
-
// Track method installation in settings.json
|
|
283
|
+
// Track method installation in settings.json first (so reconstructor can read methods list)
|
|
374
284
|
await this.trackMethodInstallation(targetDir, method, ides);
|
|
375
|
-
//
|
|
376
|
-
|
|
285
|
+
// Read installed methods to build active templates list
|
|
286
|
+
const settingsPath = getTargetSettingsFile(targetDir);
|
|
287
|
+
const settings = await readClaudeSettings(settingsPath);
|
|
288
|
+
const activeTemplates = settings?.methods ? Object.keys(settings.methods) : [method];
|
|
289
|
+
// Reconstruct IDE settings from all active templates
|
|
290
|
+
await reconstructIdeSettings(targetDir, activeTemplates, ides);
|
|
291
|
+
this.logSuccess('✓ Reconstructed IDE settings from active templates');
|
|
377
292
|
// Update .gitignore if git repository exists
|
|
378
293
|
if (hasGit) {
|
|
379
294
|
await updateGitignore(targetDir, foldersForGitignore);
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prune stale entries from the AIW Installation section in .gitignore.
|
|
3
|
+
* Checks each entry against disk existence and removes entries whose paths don't exist.
|
|
4
|
+
* Removes the entire section if no entries remain after pruning.
|
|
5
|
+
*
|
|
6
|
+
* @param targetDir - Directory containing .gitignore
|
|
7
|
+
* @returns True if any entries were pruned
|
|
8
|
+
*/
|
|
9
|
+
export declare function pruneGitignoreStaleEntries(targetDir: string): Promise<boolean>;
|
|
1
10
|
/**
|
|
2
11
|
* Update .gitignore with patterns for installed folders.
|
|
3
12
|
*
|
|
@@ -1,5 +1,126 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { pathExists } from './paths.js';
|
|
4
|
+
/**
|
|
5
|
+
* AIW gitignore section header marker
|
|
6
|
+
*/
|
|
7
|
+
const AIW_GITIGNORE_HEADER = '# AIW Installation';
|
|
8
|
+
/**
|
|
9
|
+
* Prune stale entries from the AIW Installation section in .gitignore.
|
|
10
|
+
* Checks each entry against disk existence and removes entries whose paths don't exist.
|
|
11
|
+
* Removes the entire section if no entries remain after pruning.
|
|
12
|
+
*
|
|
13
|
+
* @param targetDir - Directory containing .gitignore
|
|
14
|
+
* @returns True if any entries were pruned
|
|
15
|
+
*/
|
|
16
|
+
export async function pruneGitignoreStaleEntries(targetDir) {
|
|
17
|
+
const gitignorePath = join(targetDir, '.gitignore');
|
|
18
|
+
try {
|
|
19
|
+
const content = await fs.readFile(gitignorePath, 'utf8');
|
|
20
|
+
if (!content.includes(AIW_GITIGNORE_HEADER)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const lines = content.split('\n');
|
|
24
|
+
const newLines = [];
|
|
25
|
+
let inAiwSection = false;
|
|
26
|
+
const aiwSectionLines = [];
|
|
27
|
+
let pruned = false;
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
if (line === AIW_GITIGNORE_HEADER) {
|
|
30
|
+
inAiwSection = true;
|
|
31
|
+
aiwSectionLines.push(line);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (inAiwSection) {
|
|
35
|
+
// AIW section ends at empty line or another comment header
|
|
36
|
+
if (line === '' || (line.startsWith('#') && line !== AIW_GITIGNORE_HEADER)) {
|
|
37
|
+
inAiwSection = false;
|
|
38
|
+
const { lines: filtered, pruned: sectionPruned } = await pruneSection(aiwSectionLines, targetDir);
|
|
39
|
+
if (sectionPruned)
|
|
40
|
+
pruned = true;
|
|
41
|
+
newLines.push(...filtered, line);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
aiwSectionLines.push(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
newLines.push(line);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Handle case where AIW section is at end of file
|
|
52
|
+
if (inAiwSection) {
|
|
53
|
+
const { lines: filtered, pruned: sectionPruned } = await pruneSection(aiwSectionLines, targetDir);
|
|
54
|
+
if (sectionPruned)
|
|
55
|
+
pruned = true;
|
|
56
|
+
newLines.push(...filtered);
|
|
57
|
+
}
|
|
58
|
+
if (!pruned) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// Clean up: remove AIW section entirely if only header remains
|
|
62
|
+
let result = cleanupEmptySections(newLines.join('\n'));
|
|
63
|
+
// Ensure file ends properly
|
|
64
|
+
result = result.replace(/\n+$/, '\n');
|
|
65
|
+
if (result.trim() === '') {
|
|
66
|
+
result = '';
|
|
67
|
+
}
|
|
68
|
+
await fs.writeFile(gitignorePath, result, 'utf8');
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Prune stale entries from a parsed AIW section.
|
|
77
|
+
* Checks each gitignore pattern against disk existence.
|
|
78
|
+
*/
|
|
79
|
+
async function pruneSection(sectionLines, targetDir) {
|
|
80
|
+
let pruned = false;
|
|
81
|
+
const filtered = [];
|
|
82
|
+
for (const line of sectionLines) {
|
|
83
|
+
// Always keep the header
|
|
84
|
+
if (line === AIW_GITIGNORE_HEADER) {
|
|
85
|
+
filtered.push(line);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Check if the path exists on disk
|
|
89
|
+
const cleanPath = line.replace(/^\//, '').replace(/\/$/, '');
|
|
90
|
+
const absPath = join(targetDir, cleanPath);
|
|
91
|
+
if (await pathExists(absPath)) {
|
|
92
|
+
filtered.push(line);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
pruned = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { lines: filtered, pruned };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Remove empty AIW sections (header with no patterns following).
|
|
102
|
+
*/
|
|
103
|
+
function cleanupEmptySections(content) {
|
|
104
|
+
const lines = content.split('\n');
|
|
105
|
+
const newLines = [];
|
|
106
|
+
for (let i = 0; i < lines.length; i++) {
|
|
107
|
+
const line = lines[i];
|
|
108
|
+
if (line === AIW_GITIGNORE_HEADER) {
|
|
109
|
+
// Look ahead to see if there are any patterns
|
|
110
|
+
const nextLine = lines[i + 1];
|
|
111
|
+
if (nextLine === undefined || nextLine === '' || nextLine.startsWith('#')) {
|
|
112
|
+
// Skip the header — section is empty
|
|
113
|
+
// Also remove trailing empty lines before the header
|
|
114
|
+
while (newLines.length > 0 && newLines.at(-1) === '') {
|
|
115
|
+
newLines.pop();
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
newLines.push(line);
|
|
121
|
+
}
|
|
122
|
+
return newLines.join('\n');
|
|
123
|
+
}
|
|
3
124
|
/**
|
|
4
125
|
* Update .gitignore with patterns for installed folders.
|
|
5
126
|
*
|
|
@@ -45,14 +45,8 @@ export interface TemplateInstallationStatus {
|
|
|
45
45
|
export interface InstallationResult {
|
|
46
46
|
/** List of folder names that were installed (for gitignore) */
|
|
47
47
|
installedFolders: string[];
|
|
48
|
-
/** Number of files that were merged into existing folders */
|
|
49
|
-
mergedFileCount: number;
|
|
50
|
-
/** List of folder names that had content merged */
|
|
51
|
-
mergedFolders: string[];
|
|
52
48
|
/** Whether shared settings were merged into IDE settings */
|
|
53
49
|
sharedSettingsMerged: boolean;
|
|
54
|
-
/** List of folder names that were skipped (already exist) */
|
|
55
|
-
skippedFolders: string[];
|
|
56
50
|
/** Absolute path to the template that was installed */
|
|
57
51
|
templatePath: string;
|
|
58
52
|
}
|
|
@@ -87,15 +81,16 @@ export declare function shouldExclude(name: string): boolean;
|
|
|
87
81
|
export declare function copyDir(src: string, dest: string, excludeIdeFolders?: boolean): Promise<void>;
|
|
88
82
|
/**
|
|
89
83
|
* Install template with IDE-specific folder selection.
|
|
90
|
-
* Supports selective installation - only installs items that don't already exist.
|
|
91
84
|
*
|
|
92
85
|
* Template structure:
|
|
93
|
-
* - Non-dot folders (e.g., _bmad/, GSR/)
|
|
94
|
-
* -
|
|
86
|
+
* - Non-dot folders (e.g., _bmad/, GSR/) → .aiwcli/ (always overwritten)
|
|
87
|
+
* - _shared/ → .aiwcli/_shared/ (always overwritten)
|
|
88
|
+
* - IDE dot folders (e.g., .claude/) → decomposed into method-owned subdirs
|
|
89
|
+
*
|
|
90
|
+
* Settings reconstruction is handled separately by the caller via reconstructIdeSettings().
|
|
95
91
|
*
|
|
96
92
|
* @param config - Installation configuration
|
|
97
|
-
* @
|
|
98
|
-
* @returns Installation result with list of installed and skipped folders
|
|
93
|
+
* @returns Installation result with list of installed folders
|
|
99
94
|
* @throws Error if template doesn't exist or requested IDE folder not found
|
|
100
95
|
*/
|
|
101
|
-
export declare function installTemplate(config: TemplateInstallConfig
|
|
96
|
+
export declare function installTemplate(config: TemplateInstallConfig): Promise<InstallationResult>;
|