aiwcli 0.10.2 → 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.
Files changed (105) hide show
  1. package/dist/commands/clear.d.ts +11 -6
  2. package/dist/commands/clear.js +200 -249
  3. package/dist/commands/init/index.d.ts +1 -17
  4. package/dist/commands/init/index.js +19 -104
  5. package/dist/lib/template-installer.d.ts +7 -12
  6. package/dist/lib/template-installer.js +69 -193
  7. package/dist/lib/template-settings-reconstructor.d.ts +35 -0
  8. package/dist/lib/template-settings-reconstructor.js +130 -0
  9. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  10. package/dist/templates/_shared/hooks/archive_plan.py +10 -2
  11. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  12. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  13. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  14. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  15. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  16. package/dist/templates/_shared/lib/base/hook_utils.py +8 -10
  17. package/dist/templates/_shared/lib/base/inference.py +51 -62
  18. package/dist/templates/_shared/lib/base/logger.py +35 -21
  19. package/dist/templates/_shared/lib/base/stop_words.py +8 -0
  20. package/dist/templates/_shared/lib/base/utils.py +29 -8
  21. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  22. package/dist/templates/_shared/lib/context/plan_manager.py +101 -2
  23. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
  24. package/dist/templates/_shared/lib-ts/base/constants.ts +299 -0
  25. package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
  26. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +360 -0
  27. package/dist/templates/_shared/lib-ts/base/inference.ts +245 -0
  28. package/dist/templates/_shared/lib-ts/base/logger.ts +234 -0
  29. package/dist/templates/_shared/lib-ts/base/state-io.ts +114 -0
  30. package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
  31. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +23 -0
  32. package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
  33. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +432 -0
  34. package/dist/templates/_shared/lib-ts/context/context-selector.ts +497 -0
  35. package/dist/templates/_shared/lib-ts/context/context-store.ts +679 -0
  36. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +292 -0
  37. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +181 -0
  38. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +215 -0
  39. package/dist/templates/_shared/lib-ts/package.json +21 -0
  40. package/dist/templates/_shared/lib-ts/templates/formatters.ts +102 -0
  41. package/dist/templates/_shared/lib-ts/templates/plan-context.ts +65 -0
  42. package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
  43. package/dist/templates/_shared/lib-ts/types.ts +151 -0
  44. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  45. package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
  46. package/dist/templates/_shared/scripts/status_line.py +17 -2
  47. package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
  48. package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
  49. package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
  50. package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
  51. package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
  52. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -1
  53. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
  54. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
  55. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
  56. package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
  57. package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
  58. package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
  59. package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
  60. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
  61. package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
  62. package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
  63. package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
  64. package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
  65. package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
  66. package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
  67. package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
  68. package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
  69. package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
  70. package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
  71. package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
  72. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
  73. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
  74. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
  75. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
  76. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
  77. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
  78. package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
  79. package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
  80. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  81. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +125 -40
  82. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  83. package/dist/templates/cc-native/_cc-native/lib/utils.py +57 -13
  84. package/dist/templates/cc-native/_cc-native/plan-review.config.json +11 -7
  85. package/oclif.manifest.json +1 -1
  86. package/package.json +1 -1
  87. package/dist/lib/template-merger.d.ts +0 -47
  88. package/dist/lib/template-merger.js +0 -162
  89. package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
  90. package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
  91. package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
  92. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
  93. package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
  94. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
  95. package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
  96. package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
  97. package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
  98. package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
  99. package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
  100. package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
  101. package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
  102. package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
  103. package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
  104. package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
  105. package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
@@ -15,6 +15,14 @@ export default class ClearCommand extends BaseCommand {
15
15
  quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
16
  };
17
17
  run(): Promise<void>;
18
+ /**
19
+ * Clean runtime output artifacts from _output/ at project root.
20
+ * Handles temp files, cache files, log rotation, and archive cleanup.
21
+ *
22
+ * @param targetDir - Project root directory
23
+ * @param flags - Command flags (dry-run, force)
24
+ */
25
+ private cleanRuntimeOutput;
18
26
  /**
19
27
  * Extract method names from workflow folder names (e.g., _gsd -> gsd).
20
28
  *
@@ -53,11 +61,8 @@ export default class ClearCommand extends BaseCommand {
53
61
  */
54
62
  private findWorkflowFolders;
55
63
  /**
56
- * Clean runtime output artifacts from _output/ at project root.
57
- * Handles temp files, cache files, log rotation, and archive cleanup.
58
- *
59
- * @param targetDir - Project root directory
60
- * @param flags - Command flags (dry-run, force)
64
+ * Remove method entries from IDE settings files (methods tracking only).
65
+ * Settings reconstruction handles hooks/permissions; this only strips the methods object.
61
66
  */
62
- private cleanRuntimeOutput;
67
+ private removeMethodEntries;
63
68
  }
@@ -5,6 +5,7 @@ import { Flags } from '@oclif/core';
5
5
  import BaseCommand from '../lib/base-command.js';
6
6
  import { pruneGitignoreStaleEntries } from '../lib/gitignore-manager.js';
7
7
  import { pathExists } from '../lib/paths.js';
8
+ import { reconstructIdeSettings } from '../lib/template-settings-reconstructor.js';
8
9
  import { EXIT_CODES } from '../types/exit-codes.js';
9
10
  /**
10
11
  * Container folder for method-specific files
@@ -306,100 +307,6 @@ function cleanupGitignoreContent(content) {
306
307
  }
307
308
  return result;
308
309
  }
309
- /**
310
- * Update IDE settings file to remove method-specific entries.
311
- * Creates a backup before modifying.
312
- *
313
- * @param targetDir - Directory containing the IDE folder
314
- * @param ideFolder - IDE folder configuration (e.g., IDE_FOLDERS.claude)
315
- * @param ideFolder.root - Root folder name (e.g., '.claude')
316
- * @param ideFolder.settingsFile - Settings file name (e.g., 'settings.json')
317
- * @param methodsToRemove - Method names to remove from settings
318
- */
319
- async function updateIdeSettings(targetDir, ideFolder, methodsToRemove) {
320
- const settingsPath = join(targetDir, ideFolder.root, ideFolder.settingsFile);
321
- const result = { backedUp: false, updated: false };
322
- try {
323
- const content = await fs.readFile(settingsPath, 'utf8');
324
- const settings = JSON.parse(content);
325
- // Create backup before modifying
326
- const backupPath = `${settingsPath}.backup`;
327
- await fs.writeFile(backupPath, content, 'utf8');
328
- result.backedUp = true;
329
- // Remove method-specific entries from methods tracking object
330
- let modified = false;
331
- if (settings.methods && typeof settings.methods === 'object') {
332
- for (const method of methodsToRemove) {
333
- if (method in settings.methods) {
334
- delete settings.methods[method];
335
- modified = true;
336
- }
337
- }
338
- // Remove methods object if empty
339
- if (Object.keys(settings.methods).length === 0) {
340
- delete settings.methods;
341
- modified = true;
342
- }
343
- }
344
- // Remove method-specific hooks from hooks array
345
- if (settings.hooks && typeof settings.hooks === 'object') {
346
- for (const hookType of Object.keys(settings.hooks)) {
347
- const hookArray = settings.hooks[hookType];
348
- if (Array.isArray(hookArray)) {
349
- const filtered = hookArray.filter((hook) => {
350
- // Check if hook command references any of the methods to remove
351
- if (hook.hooks && Array.isArray(hook.hooks)) {
352
- const filteredInner = hook.hooks.filter((innerHook) => {
353
- if (typeof innerHook.command === 'string') {
354
- return !methodsToRemove.some((method) => innerHook.command?.toString().includes(`_${method}/`));
355
- }
356
- return true;
357
- });
358
- if (filteredInner.length !== hook.hooks.length) {
359
- hook.hooks = filteredInner;
360
- modified = true;
361
- }
362
- // Remove hook entry if all inner hooks were removed
363
- if (filteredInner.length === 0) {
364
- return false;
365
- }
366
- }
367
- return true;
368
- });
369
- if (filtered.length !== hookArray.length) {
370
- settings.hooks[hookType] = filtered;
371
- modified = true;
372
- }
373
- // Remove hook type if empty
374
- if (filtered.length === 0) {
375
- delete settings.hooks[hookType];
376
- modified = true;
377
- }
378
- }
379
- }
380
- // Remove hooks object if empty
381
- if (Object.keys(settings.hooks).length === 0) {
382
- delete settings.hooks;
383
- modified = true;
384
- }
385
- }
386
- if (modified) {
387
- // Write updated settings
388
- const newContent = JSON.stringify(settings, null, 2) + '\n';
389
- await fs.writeFile(settingsPath, newContent, 'utf8');
390
- result.updated = true;
391
- }
392
- else {
393
- // No changes needed, remove backup
394
- await fs.unlink(backupPath);
395
- result.backedUp = false;
396
- }
397
- }
398
- catch {
399
- // Settings file doesn't exist or can't be read
400
- }
401
- return result;
402
- }
403
310
  /**
404
311
  * Clear workflow folders, output folders, IDE method folders, and update configurations.
405
312
  */
@@ -664,19 +571,34 @@ export default class ClearCommand extends BaseCommand {
664
571
  if (pruned) {
665
572
  this.logDebug('Pruned stale .gitignore entries');
666
573
  }
667
- // Update IDE settings files to remove method-specific entries
574
+ // Reconstruct IDE settings from remaining templates
668
575
  let updatedClaudeSettings = false;
669
576
  let updatedWindsurfSettings = false;
670
577
  if (methodsToRemove.length > 0) {
671
- const claudeResult = await updateIdeSettings(targetDir, IDE_FOLDERS.claude, methodsToRemove);
672
- if (claudeResult.updated) {
673
- this.logDebug('Updated .claude/settings.json (backup created)');
674
- updatedClaudeSettings = true;
578
+ // Remove method entries from settings files first
579
+ await this.removeMethodEntries(targetDir, methodsToRemove);
580
+ // Get remaining installed methods
581
+ const allMethods = await getInstalledMethodNames(targetDir);
582
+ // Filter out methods being removed (in case disk scan still finds them)
583
+ const remainingTemplates = [...allMethods].filter(m => !methodsToRemove.includes(m));
584
+ // Determine which IDEs need reconstruction
585
+ const ides = [];
586
+ if (await pathExists(join(targetDir, IDE_FOLDERS.claude.root))) {
587
+ ides.push('claude');
675
588
  }
676
- const windsurfResult = await updateIdeSettings(targetDir, IDE_FOLDERS.windsurf, methodsToRemove);
677
- if (windsurfResult.updated) {
678
- this.logDebug('Updated .windsurf/hooks.json (backup created)');
679
- updatedWindsurfSettings = true;
589
+ if (await pathExists(join(targetDir, IDE_FOLDERS.windsurf.root))) {
590
+ ides.push('windsurf');
591
+ }
592
+ if (ides.length > 0) {
593
+ await reconstructIdeSettings(targetDir, remainingTemplates, ides);
594
+ if (ides.includes('claude')) {
595
+ this.logDebug('Reconstructed .claude/settings.json (backup created)');
596
+ updatedClaudeSettings = true;
597
+ }
598
+ if (ides.includes('windsurf')) {
599
+ this.logDebug('Reconstructed .windsurf/hooks.json (backup created)');
600
+ updatedWindsurfSettings = true;
601
+ }
680
602
  }
681
603
  }
682
604
  // Check if IDE folders should be fully deleted (empty settings + empty subfolders)
@@ -755,151 +677,6 @@ export default class ClearCommand extends BaseCommand {
755
677
  });
756
678
  }
757
679
  }
758
- /**
759
- * Extract method names from workflow folder names (e.g., _gsd -> gsd).
760
- *
761
- * @param workflowFolders - Array of workflow folder paths
762
- * @returns Array of method names
763
- */
764
- extractMethodNames(workflowFolders) {
765
- const methods = [];
766
- for (const folder of workflowFolders) {
767
- const folderName = folder.split(/[/\\]/).pop() || '';
768
- if (folderName.startsWith('_')) {
769
- methods.push(folderName.slice(1));
770
- }
771
- }
772
- return methods;
773
- }
774
- /**
775
- * Find all IDE method folders by scanning subdirectories of each IDE root
776
- * for children matching installed method names.
777
- *
778
- * For example, finds .claude/commands/cc-native/, .claude/skills/cc-native/,
779
- * .windsurf/workflows/cc-native/ — without hardcoding which subdirectories exist.
780
- *
781
- * @param targetDir - Directory to search in
782
- * @param template - Optional template/method name to filter by
783
- * @returns Array of IDE method folder paths
784
- */
785
- async findIdeMethodFolders(targetDir, template) {
786
- // Build method set: from --template flag, or from installed methods
787
- const methodNames = template ? new Set([template]) : await getInstalledMethodNames(targetDir);
788
- if (methodNames.size === 0) {
789
- return [];
790
- }
791
- // For each IDE root, scan all subdirectories for children matching method names
792
- const ideRoots = Object.values(IDE_FOLDERS).map((ide) => join(targetDir, ide.root));
793
- const ideResults = await Promise.all(ideRoots.map(async (ideRoot) => {
794
- // Get all subdirectories of IDE root (e.g., .claude/commands/, .claude/skills/)
795
- try {
796
- const topEntries = await fs.readdir(ideRoot, { withFileTypes: true });
797
- const subdirs = topEntries.filter((e) => e.isDirectory());
798
- // For each subdirectory, check for method-named children
799
- const subResults = await Promise.all(subdirs.map(async (subdir) => {
800
- const subdirPath = join(ideRoot, subdir.name);
801
- try {
802
- const entries = await fs.readdir(subdirPath, { withFileTypes: true });
803
- return entries
804
- .filter((entry) => entry.isDirectory() && methodNames.has(entry.name))
805
- .map((entry) => join(subdirPath, entry.name));
806
- }
807
- catch {
808
- return [];
809
- }
810
- }));
811
- return subResults.flat();
812
- }
813
- catch {
814
- return [];
815
- }
816
- }));
817
- return ideResults.flat();
818
- }
819
- /**
820
- * Find all output folders in the target directory.
821
- * Looks for .aiwcli/_output/{method}/ structure.
822
- *
823
- * @param targetDir - Directory to search in
824
- * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
825
- * @returns Array of output folder paths
826
- */
827
- async findOutputFolders(targetDir, template) {
828
- const containerDir = join(targetDir, AIWCLI_CONTAINER);
829
- const outputDir = join(containerDir, OUTPUT_FOLDER_NAME);
830
- // Check if _output folder exists
831
- try {
832
- const stat = await fs.stat(outputDir);
833
- if (!stat.isDirectory()) {
834
- return [];
835
- }
836
- }
837
- catch {
838
- // _output folder doesn't exist
839
- return [];
840
- }
841
- // If template specified, only look for that specific method folder
842
- if (template) {
843
- const methodPath = join(outputDir, template);
844
- try {
845
- const stat = await fs.stat(methodPath);
846
- if (stat.isDirectory()) {
847
- return [methodPath];
848
- }
849
- }
850
- catch {
851
- // Method folder doesn't exist
852
- }
853
- return [];
854
- }
855
- // No template filter - find all method folders within _output
856
- const foundFolders = [];
857
- try {
858
- const entries = await fs.readdir(outputDir, { withFileTypes: true });
859
- for (const entry of entries) {
860
- if (entry.isDirectory()) {
861
- foundFolders.push(join(outputDir, entry.name));
862
- }
863
- }
864
- }
865
- catch {
866
- // Directory can't be read - return empty
867
- }
868
- return foundFolders;
869
- }
870
- /**
871
- * Find all workflow folders in the target directory.
872
- * Looks for .aiwcli/_{method}/ structure (e.g., .aiwcli/_gsd/, .aiwcli/_bmad/).
873
- *
874
- * @param targetDir - Directory to search in
875
- * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
876
- * @returns Array of workflow folder paths
877
- */
878
- async findWorkflowFolders(targetDir, template) {
879
- const foundFolders = [];
880
- const containerDir = join(targetDir, AIWCLI_CONTAINER);
881
- try {
882
- const entries = await fs.readdir(containerDir, { withFileTypes: true });
883
- for (const entry of entries) {
884
- // Look for directories starting with underscore (workflow folders)
885
- if (entry.isDirectory() && entry.name.startsWith('_') && entry.name !== OUTPUT_FOLDER_NAME) {
886
- // If template specified, only include matching folder
887
- if (template) {
888
- if (entry.name === `_${template}`) {
889
- foundFolders.push(join(containerDir, entry.name));
890
- }
891
- }
892
- else {
893
- foundFolders.push(join(containerDir, entry.name));
894
- }
895
- }
896
- }
897
- }
898
- catch {
899
- // Directory can't be read - return empty
900
- }
901
- return foundFolders;
902
- }
903
680
  /**
904
681
  * Clean runtime output artifacts from _output/ at project root.
905
682
  * Handles temp files, cache files, log rotation, and archive cleanup.
@@ -1024,7 +801,7 @@ export default class ClearCommand extends BaseCommand {
1024
801
  const truncated = content.slice(-524_288);
1025
802
  // Find the first complete line
1026
803
  const firstNewline = truncated.indexOf('\n');
1027
- const cleaned = firstNewline >= 0 ? truncated.slice(firstNewline + 1) : truncated;
804
+ const cleaned = firstNewline === -1 ? truncated : truncated.slice(firstNewline + 1);
1028
805
  await fs.writeFile(logAction.path, cleaned, 'utf8');
1029
806
  this.logDebug('Rotated hook-log.jsonl');
1030
807
  }
@@ -1072,4 +849,178 @@ export default class ClearCommand extends BaseCommand {
1072
849
  this.logInfo('No changes made.');
1073
850
  }
1074
851
  }
852
+ /**
853
+ * Extract method names from workflow folder names (e.g., _gsd -> gsd).
854
+ *
855
+ * @param workflowFolders - Array of workflow folder paths
856
+ * @returns Array of method names
857
+ */
858
+ extractMethodNames(workflowFolders) {
859
+ const methods = [];
860
+ for (const folder of workflowFolders) {
861
+ const folderName = folder.split(/[/\\]/).pop() || '';
862
+ if (folderName.startsWith('_')) {
863
+ methods.push(folderName.slice(1));
864
+ }
865
+ }
866
+ return methods;
867
+ }
868
+ /**
869
+ * Find all IDE method folders by scanning subdirectories of each IDE root
870
+ * for children matching installed method names.
871
+ *
872
+ * For example, finds .claude/commands/cc-native/, .claude/skills/cc-native/,
873
+ * .windsurf/workflows/cc-native/ — without hardcoding which subdirectories exist.
874
+ *
875
+ * @param targetDir - Directory to search in
876
+ * @param template - Optional template/method name to filter by
877
+ * @returns Array of IDE method folder paths
878
+ */
879
+ async findIdeMethodFolders(targetDir, template) {
880
+ // Build method set: from --template flag, or from installed methods
881
+ const methodNames = template ? new Set([template]) : await getInstalledMethodNames(targetDir);
882
+ if (methodNames.size === 0) {
883
+ return [];
884
+ }
885
+ // For each IDE root, scan all subdirectories for children matching method names
886
+ const ideRoots = Object.values(IDE_FOLDERS).map((ide) => join(targetDir, ide.root));
887
+ const ideResults = await Promise.all(ideRoots.map(async (ideRoot) => {
888
+ // Get all subdirectories of IDE root (e.g., .claude/commands/, .claude/skills/)
889
+ try {
890
+ const topEntries = await fs.readdir(ideRoot, { withFileTypes: true });
891
+ const subdirs = topEntries.filter((e) => e.isDirectory());
892
+ // For each subdirectory, check for method-named children
893
+ const subResults = await Promise.all(subdirs.map(async (subdir) => {
894
+ const subdirPath = join(ideRoot, subdir.name);
895
+ try {
896
+ const entries = await fs.readdir(subdirPath, { withFileTypes: true });
897
+ return entries
898
+ .filter((entry) => entry.isDirectory() && methodNames.has(entry.name))
899
+ .map((entry) => join(subdirPath, entry.name));
900
+ }
901
+ catch {
902
+ return [];
903
+ }
904
+ }));
905
+ return subResults.flat();
906
+ }
907
+ catch {
908
+ return [];
909
+ }
910
+ }));
911
+ return ideResults.flat();
912
+ }
913
+ /**
914
+ * Find all output folders in the target directory.
915
+ * Looks for .aiwcli/_output/{method}/ structure.
916
+ *
917
+ * @param targetDir - Directory to search in
918
+ * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
919
+ * @returns Array of output folder paths
920
+ */
921
+ async findOutputFolders(targetDir, template) {
922
+ const containerDir = join(targetDir, AIWCLI_CONTAINER);
923
+ const outputDir = join(containerDir, OUTPUT_FOLDER_NAME);
924
+ // Check if _output folder exists
925
+ try {
926
+ const stat = await fs.stat(outputDir);
927
+ if (!stat.isDirectory()) {
928
+ return [];
929
+ }
930
+ }
931
+ catch {
932
+ // _output folder doesn't exist
933
+ return [];
934
+ }
935
+ // If template specified, only look for that specific method folder
936
+ if (template) {
937
+ const methodPath = join(outputDir, template);
938
+ try {
939
+ const stat = await fs.stat(methodPath);
940
+ if (stat.isDirectory()) {
941
+ return [methodPath];
942
+ }
943
+ }
944
+ catch {
945
+ // Method folder doesn't exist
946
+ }
947
+ return [];
948
+ }
949
+ // No template filter - find all method folders within _output
950
+ const foundFolders = [];
951
+ try {
952
+ const entries = await fs.readdir(outputDir, { withFileTypes: true });
953
+ for (const entry of entries) {
954
+ if (entry.isDirectory()) {
955
+ foundFolders.push(join(outputDir, entry.name));
956
+ }
957
+ }
958
+ }
959
+ catch {
960
+ // Directory can't be read - return empty
961
+ }
962
+ return foundFolders;
963
+ }
964
+ /**
965
+ * Find all workflow folders in the target directory.
966
+ * Looks for .aiwcli/_{method}/ structure (e.g., .aiwcli/_gsd/, .aiwcli/_bmad/).
967
+ *
968
+ * @param targetDir - Directory to search in
969
+ * @param template - Optional template/method name to filter by (e.g., 'bmad', 'gsd')
970
+ * @returns Array of workflow folder paths
971
+ */
972
+ async findWorkflowFolders(targetDir, template) {
973
+ const foundFolders = [];
974
+ const containerDir = join(targetDir, AIWCLI_CONTAINER);
975
+ try {
976
+ const entries = await fs.readdir(containerDir, { withFileTypes: true });
977
+ for (const entry of entries) {
978
+ // Look for directories starting with underscore (workflow folders)
979
+ if (entry.isDirectory() && entry.name.startsWith('_') && entry.name !== OUTPUT_FOLDER_NAME) {
980
+ // If template specified, only include matching folder
981
+ if (template) {
982
+ if (entry.name === `_${template}`) {
983
+ foundFolders.push(join(containerDir, entry.name));
984
+ }
985
+ }
986
+ else {
987
+ foundFolders.push(join(containerDir, entry.name));
988
+ }
989
+ }
990
+ }
991
+ }
992
+ catch {
993
+ // Directory can't be read - return empty
994
+ }
995
+ return foundFolders;
996
+ }
997
+ /**
998
+ * Remove method entries from IDE settings files (methods tracking only).
999
+ * Settings reconstruction handles hooks/permissions; this only strips the methods object.
1000
+ */
1001
+ async removeMethodEntries(targetDir, methodsToRemove) {
1002
+ const ops = Object.values(IDE_FOLDERS).map(async (ide) => {
1003
+ const settingsPath = join(targetDir, ide.root, ide.settingsFile);
1004
+ try {
1005
+ const content = await fs.readFile(settingsPath, 'utf8');
1006
+ const settings = JSON.parse(content);
1007
+ if (settings.methods && typeof settings.methods === 'object') {
1008
+ for (const method of methodsToRemove) {
1009
+ if (method in settings.methods) {
1010
+ delete settings.methods[method];
1011
+ }
1012
+ }
1013
+ if (Object.keys(settings.methods).length === 0) {
1014
+ delete settings.methods;
1015
+ }
1016
+ // Write back with methods removed (backup created by reconstructor)
1017
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
1018
+ }
1019
+ }
1020
+ catch {
1021
+ // Settings file doesn't exist or can't be read
1022
+ }
1023
+ });
1024
+ await Promise.all(ops);
1025
+ }
1075
1026
  }
@@ -29,28 +29,12 @@ export default class Init extends BaseCommand {
29
29
  * @returns Template description
30
30
  */
31
31
  private getTemplateDescription;
32
- /**
33
- * Merge settings from multiple method templates into project settings.
34
- * Processes methods in order, allowing later methods to override earlier ones.
35
- *
36
- * @param targetDir - Project directory
37
- * @param methods - Array of method names to merge (e.g., ['_shared', 'cc-native'])
38
- * @param ides - IDEs being configured (for IDE-specific merging)
39
- */
40
- private mergeMethodsSettings;
41
- /**
42
- * Merge Windsurf template hooks into project hooks
43
- *
44
- * @param targetDir - Project directory
45
- * @param templatePath - Template source path
46
- */
47
- private mergeWindsurfTemplateHooks;
48
32
  /**
49
33
  * Perform post-installation actions.
50
34
  *
51
35
  * Handles:
52
36
  * - Method tracking in settings.json
53
- * - Settings/hooks merging for all methods
37
+ * - Settings reconstruction from all active templates
54
38
  * - .gitignore updates
55
39
  *
56
40
  * @param config - Post-install configuration