aiwcli 0.13.3 → 0.13.5
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/clear.d.ts +5 -5
- package/dist/commands/clear.js +27 -23
- package/dist/commands/init/index.d.ts +3 -3
- package/dist/commands/init/index.js +22 -35
- package/dist/lib/git-exclude-manager.d.ts +59 -0
- package/dist/lib/{gitignore-manager.js → git-exclude-manager.js} +86 -51
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +11 -0
- package/dist/templates/_shared/scripts/status_line.ts +56 -62
- package/dist/templates/cc-native/_cc-native/cc-native.config.json +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +1 -1
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +3 -2
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/codex-agent.ts +6 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/lib/gitignore-manager.d.ts +0 -49
package/dist/commands/clear.d.ts
CHANGED
|
@@ -31,12 +31,12 @@ export default class ClearCommand extends BaseCommand {
|
|
|
31
31
|
*/
|
|
32
32
|
private cleanupBackupFiles;
|
|
33
33
|
/**
|
|
34
|
-
* Clean up
|
|
34
|
+
* Clean up git exclude entries and prune stale entries.
|
|
35
35
|
*
|
|
36
36
|
* @param targetDir - Project root directory
|
|
37
|
-
* @returns True if
|
|
37
|
+
* @returns True if git exclude was updated
|
|
38
38
|
*/
|
|
39
|
-
private
|
|
39
|
+
private cleanupGitExclude;
|
|
40
40
|
/**
|
|
41
41
|
* Display a list of folders to remove.
|
|
42
42
|
*
|
|
@@ -103,7 +103,7 @@ export default class ClearCommand extends BaseCommand {
|
|
|
103
103
|
*/
|
|
104
104
|
private findWorkflowFolders;
|
|
105
105
|
/**
|
|
106
|
-
* Perform all post-deletion cleanup: empty dir removal,
|
|
106
|
+
* Perform all post-deletion cleanup: empty dir removal, git exclude, settings, IDE folders.
|
|
107
107
|
*
|
|
108
108
|
* @param targetDir - Project root directory
|
|
109
109
|
* @param methodsToRemove - Method names being removed
|
|
@@ -139,7 +139,7 @@ export default class ClearCommand extends BaseCommand {
|
|
|
139
139
|
* @param deleteCounts.deletedOutput - Number of output folders deleted
|
|
140
140
|
* @param deleteCounts.deletedIde - Number of IDE method folders deleted
|
|
141
141
|
* @param cleanup - Cleanup operation results
|
|
142
|
-
* @param cleanup.
|
|
142
|
+
* @param cleanup.gitExcludeUpdated - Whether git exclude was updated
|
|
143
143
|
* @param cleanup.removedOutputDir - Whether _output dir was removed
|
|
144
144
|
* @param cleanup.removedAiwcliContainer - Whether .aiwcli dir was removed
|
|
145
145
|
* @param cleanup.removedClaudeDir - Whether .claude dir was removed
|
package/dist/commands/clear.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import confirm from '@inquirer/confirm';
|
|
4
4
|
import { Flags } from '@oclif/core';
|
|
5
5
|
import BaseCommand from '../lib/base-command.js';
|
|
6
|
-
import {
|
|
6
|
+
import { computeExcludeRemovals, pruneExcludeStaleEntries, removeExcludeEntries, resolveGitDir } from '../lib/git-exclude-manager.js';
|
|
7
7
|
import { pathExists } from '../lib/paths.js';
|
|
8
8
|
import { getSharedTemplatePath } from '../lib/template-resolver.js';
|
|
9
9
|
import { reconstructIdeSettings } from '../lib/template-settings-reconstructor.js';
|
|
@@ -660,23 +660,26 @@ export default class ClearCommand extends BaseCommand {
|
|
|
660
660
|
await Promise.all(cleanups);
|
|
661
661
|
}
|
|
662
662
|
/**
|
|
663
|
-
* Clean up
|
|
663
|
+
* Clean up git exclude entries and prune stale entries.
|
|
664
664
|
*
|
|
665
665
|
* @param targetDir - Project root directory
|
|
666
|
-
* @returns True if
|
|
666
|
+
* @returns True if git exclude was updated
|
|
667
667
|
*/
|
|
668
|
-
async
|
|
669
|
-
const
|
|
668
|
+
async cleanupGitExclude(targetDir) {
|
|
669
|
+
const gitDir = await resolveGitDir(targetDir);
|
|
670
|
+
if (!gitDir)
|
|
671
|
+
return false;
|
|
672
|
+
const { toRemove, toKeep } = await computeExcludeRemovals(gitDir, targetDir);
|
|
670
673
|
for (const { entry, reason } of toKeep) {
|
|
671
|
-
this.logDebug(`Keeping ${entry}/ in
|
|
674
|
+
this.logDebug(`Keeping ${entry}/ in git exclude (${reason})`);
|
|
672
675
|
}
|
|
673
676
|
if (toRemove.length > 0) {
|
|
674
|
-
await
|
|
675
|
-
this.logDebug(`Removed from
|
|
677
|
+
await removeExcludeEntries(gitDir, toRemove);
|
|
678
|
+
this.logDebug(`Removed from git exclude: ${toRemove.join(', ')}`);
|
|
676
679
|
}
|
|
677
|
-
const pruned = await
|
|
680
|
+
const pruned = await pruneExcludeStaleEntries(gitDir, targetDir);
|
|
678
681
|
if (pruned) {
|
|
679
|
-
this.logDebug('Pruned stale
|
|
682
|
+
this.logDebug('Pruned stale git exclude entries');
|
|
680
683
|
}
|
|
681
684
|
return toRemove.length > 0 || pruned;
|
|
682
685
|
}
|
|
@@ -736,14 +739,15 @@ export default class ClearCommand extends BaseCommand {
|
|
|
736
739
|
this.logInfo(`${IDE_FOLDERS.windsurf.root}/ folder will be removed (will be empty)`);
|
|
737
740
|
this.log('');
|
|
738
741
|
}
|
|
739
|
-
// Compute
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
742
|
+
// Compute git exclude changes for dry-run display
|
|
743
|
+
const gitDir = await resolveGitDir(targetDir);
|
|
744
|
+
const excludeSimulation = gitDir ? await computeExcludeRemovals(gitDir, targetDir) : { toRemove: [], toKeep: [] };
|
|
745
|
+
if (excludeSimulation.toRemove.length > 0 || excludeSimulation.toKeep.length > 0) {
|
|
746
|
+
this.logInfo('Git exclude changes:');
|
|
747
|
+
for (const { entry, reason } of excludeSimulation.toKeep) {
|
|
744
748
|
this.log(` keep ${entry}/ (${reason})`);
|
|
745
749
|
}
|
|
746
|
-
for (const entry of
|
|
750
|
+
for (const entry of excludeSimulation.toRemove) {
|
|
747
751
|
this.log(` remove ${entry}/`);
|
|
748
752
|
}
|
|
749
753
|
this.log('');
|
|
@@ -923,7 +927,7 @@ export default class ClearCommand extends BaseCommand {
|
|
|
923
927
|
return foundFolders;
|
|
924
928
|
}
|
|
925
929
|
/**
|
|
926
|
-
* Perform all post-deletion cleanup: empty dir removal,
|
|
930
|
+
* Perform all post-deletion cleanup: empty dir removal, git exclude, settings, IDE folders.
|
|
927
931
|
*
|
|
928
932
|
* @param targetDir - Project root directory
|
|
929
933
|
* @param methodsToRemove - Method names being removed
|
|
@@ -942,8 +946,8 @@ export default class ClearCommand extends BaseCommand {
|
|
|
942
946
|
if (removedAiwcliContainer) {
|
|
943
947
|
this.logDebug(`Removed empty ${AIWCLI_CONTAINER}/ folder`);
|
|
944
948
|
}
|
|
945
|
-
// Smart
|
|
946
|
-
const
|
|
949
|
+
// Smart git exclude removal
|
|
950
|
+
const gitExcludeUpdated = await this.cleanupGitExclude(targetDir);
|
|
947
951
|
// Reconstruct IDE settings
|
|
948
952
|
let { updatedClaudeSettings, updatedWindsurfSettings } = await this.reconstructSettingsAfterRemoval(targetDir, methodsToRemove);
|
|
949
953
|
// Clean up backup files
|
|
@@ -957,7 +961,7 @@ export default class ClearCommand extends BaseCommand {
|
|
|
957
961
|
updatedWindsurfSettings = false;
|
|
958
962
|
return {
|
|
959
963
|
removedOutputDir, removedAiwcliContainer, removedClaudeDir, removedWindsurfDir,
|
|
960
|
-
updatedClaudeSettings, updatedWindsurfSettings,
|
|
964
|
+
updatedClaudeSettings, updatedWindsurfSettings, gitExcludeUpdated,
|
|
961
965
|
};
|
|
962
966
|
}
|
|
963
967
|
/**
|
|
@@ -1056,7 +1060,7 @@ export default class ClearCommand extends BaseCommand {
|
|
|
1056
1060
|
* @param deleteCounts.deletedOutput - Number of output folders deleted
|
|
1057
1061
|
* @param deleteCounts.deletedIde - Number of IDE method folders deleted
|
|
1058
1062
|
* @param cleanup - Cleanup operation results
|
|
1059
|
-
* @param cleanup.
|
|
1063
|
+
* @param cleanup.gitExcludeUpdated - Whether git exclude was updated
|
|
1060
1064
|
* @param cleanup.removedOutputDir - Whether _output dir was removed
|
|
1061
1065
|
* @param cleanup.removedAiwcliContainer - Whether .aiwcli dir was removed
|
|
1062
1066
|
* @param cleanup.removedClaudeDir - Whether .claude dir was removed
|
|
@@ -1082,8 +1086,8 @@ export default class ClearCommand extends BaseCommand {
|
|
|
1082
1086
|
if (cleanup.removedWindsurfDir)
|
|
1083
1087
|
parts.push(`${IDE_FOLDERS.windsurf.root}/ folder`);
|
|
1084
1088
|
this.logSuccess(`Cleared: ${parts.join(', ')}.`);
|
|
1085
|
-
if (cleanup.
|
|
1086
|
-
this.logSuccess('Updated .
|
|
1089
|
+
if (cleanup.gitExcludeUpdated) {
|
|
1090
|
+
this.logSuccess('Updated git exclude.');
|
|
1087
1091
|
}
|
|
1088
1092
|
if (cleanup.updatedClaudeSettings) {
|
|
1089
1093
|
this.logSuccess('Updated .claude/settings.json (backup: settings.json.backup).');
|
|
@@ -41,7 +41,7 @@ export default class Init extends BaseCommand {
|
|
|
41
41
|
* Perform minimal installation (_shared folder only, no template method).
|
|
42
42
|
*
|
|
43
43
|
* @param targetDir - Target directory for installation
|
|
44
|
-
* @param
|
|
44
|
+
* @param gitDir - Resolved git directory path, or null if not a git repo
|
|
45
45
|
*/
|
|
46
46
|
private performMinimalInstall;
|
|
47
47
|
/**
|
|
@@ -56,8 +56,8 @@ export default class Init extends BaseCommand {
|
|
|
56
56
|
* @param config.targetDir - Project directory
|
|
57
57
|
* @param config.method - Method name that was installed
|
|
58
58
|
* @param config.ides - IDEs that were configured
|
|
59
|
-
* @param config.
|
|
60
|
-
* @param config.
|
|
59
|
+
* @param config.gitDir - Resolved git directory path, or null if not a git repo
|
|
60
|
+
* @param config.foldersForExclude - Folders to add to git exclude
|
|
61
61
|
*/
|
|
62
62
|
private performPostInstallActions;
|
|
63
63
|
/**
|
|
@@ -8,7 +8,7 @@ import input from '@inquirer/input';
|
|
|
8
8
|
import select from '@inquirer/select';
|
|
9
9
|
import { Flags } from '@oclif/core';
|
|
10
10
|
import BaseCommand from '../../lib/base-command.js';
|
|
11
|
-
import {
|
|
11
|
+
import { AIW_EXCLUDE_ENTRIES, resolveGitDir, updateGitExclude } from '../../lib/git-exclude-manager.js';
|
|
12
12
|
import { IdePathResolver } from '../../lib/ide-path-resolver.js';
|
|
13
13
|
import { pathExists } from '../../lib/paths.js';
|
|
14
14
|
import { getTargetSettingsFile, readClaudeSettings, writeClaudeSettings } from '../../lib/settings-hierarchy.js';
|
|
@@ -24,20 +24,7 @@ const AVAILABLE_IDES = [
|
|
|
24
24
|
{ value: 'claude', name: 'Claude Code', description: 'Anthropic Claude Code CLI' },
|
|
25
25
|
{ value: 'windsurf', name: 'Windsurf', description: 'Codeium Windsurf IDE' },
|
|
26
26
|
];
|
|
27
|
-
|
|
28
|
-
* Detect if current directory is a git repository.
|
|
29
|
-
* Checks for .git directory existence.
|
|
30
|
-
*/
|
|
31
|
-
async function detectGitRepository(targetDir) {
|
|
32
|
-
try {
|
|
33
|
-
const gitPath = join(targetDir, '.git');
|
|
34
|
-
await fs.access(gitPath);
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
27
|
+
// detectGitRepository replaced by resolveGitDir from git-exclude-manager
|
|
41
28
|
/**
|
|
42
29
|
* Extract project name from directory path.
|
|
43
30
|
* Returns the basename of the given directory.
|
|
@@ -82,12 +69,12 @@ export default class Init extends BaseCommand {
|
|
|
82
69
|
// Get available templates for validation
|
|
83
70
|
const availableTemplates = await getAvailableTemplates();
|
|
84
71
|
// Check git repository early (needed by both install paths)
|
|
85
|
-
const
|
|
72
|
+
const gitDir = await resolveGitDir(targetDir);
|
|
86
73
|
// Resolve installation configuration from flags or interactive wizard
|
|
87
74
|
const config = await this.resolveInstallationConfig(flags, targetDir, availableTemplates);
|
|
88
75
|
// If config is null, perform minimal install (shared folder only)
|
|
89
76
|
if (!config) {
|
|
90
|
-
await this.performMinimalInstall(targetDir,
|
|
77
|
+
await this.performMinimalInstall(targetDir, gitDir);
|
|
91
78
|
return;
|
|
92
79
|
}
|
|
93
80
|
const { method, ides, username, projectName } = config;
|
|
@@ -144,22 +131,22 @@ export default class Init extends BaseCommand {
|
|
|
144
131
|
projectName,
|
|
145
132
|
templatePath,
|
|
146
133
|
});
|
|
147
|
-
// Collect all folders that need
|
|
134
|
+
// Collect all folders that need exclude entries
|
|
148
135
|
// The .aiwcli/ container holds all template infrastructure and runtime data
|
|
149
|
-
const
|
|
136
|
+
const foldersForExclude = [...AIW_EXCLUDE_ENTRIES];
|
|
150
137
|
// Report installation results
|
|
151
138
|
if (result.installedFolders.length > 0) {
|
|
152
139
|
this.logSuccess(`✓ Installed: ${result.installedFolders.join(', ')}`);
|
|
153
140
|
}
|
|
154
141
|
// Install global resolver for cwd-drift-proof hook/status line commands
|
|
155
142
|
await this.installGlobalResolver();
|
|
156
|
-
// Perform post-installation actions (settings tracking, hook merging,
|
|
143
|
+
// Perform post-installation actions (settings tracking, hook merging, git exclude updates)
|
|
157
144
|
await this.performPostInstallActions({
|
|
158
145
|
targetDir,
|
|
159
146
|
method,
|
|
160
147
|
ides,
|
|
161
|
-
|
|
162
|
-
|
|
148
|
+
gitDir,
|
|
149
|
+
foldersForExclude,
|
|
163
150
|
});
|
|
164
151
|
this.log('');
|
|
165
152
|
this.logSuccess(`✓ ${method} initialized successfully`);
|
|
@@ -261,9 +248,9 @@ export default class Init extends BaseCommand {
|
|
|
261
248
|
* Perform minimal installation (_shared folder only, no template method).
|
|
262
249
|
*
|
|
263
250
|
* @param targetDir - Target directory for installation
|
|
264
|
-
* @param
|
|
251
|
+
* @param gitDir - Resolved git directory path, or null if not a git repo
|
|
265
252
|
*/
|
|
266
|
-
async performMinimalInstall(targetDir,
|
|
253
|
+
async performMinimalInstall(targetDir, gitDir) {
|
|
267
254
|
this.logInfo('Performing minimal installation (_shared folder only)...');
|
|
268
255
|
this.log('');
|
|
269
256
|
// Create .aiwcli container and install _shared
|
|
@@ -292,10 +279,10 @@ export default class Init extends BaseCommand {
|
|
|
292
279
|
await this.installGlobalResolver();
|
|
293
280
|
// Reconstruct settings from _shared template
|
|
294
281
|
await reconstructIdeSettings(targetDir, [], ['claude']);
|
|
295
|
-
// Update
|
|
296
|
-
if (
|
|
297
|
-
await
|
|
298
|
-
this.logSuccess('✓
|
|
282
|
+
// Update git exclude if git repository exists
|
|
283
|
+
if (gitDir) {
|
|
284
|
+
await updateGitExclude(gitDir, [...AIW_EXCLUDE_ENTRIES]);
|
|
285
|
+
this.logSuccess('✓ git exclude updated');
|
|
299
286
|
}
|
|
300
287
|
this.log('');
|
|
301
288
|
this.logSuccess('✓ Minimal installation completed successfully');
|
|
@@ -316,11 +303,11 @@ export default class Init extends BaseCommand {
|
|
|
316
303
|
* @param config.targetDir - Project directory
|
|
317
304
|
* @param config.method - Method name that was installed
|
|
318
305
|
* @param config.ides - IDEs that were configured
|
|
319
|
-
* @param config.
|
|
320
|
-
* @param config.
|
|
306
|
+
* @param config.gitDir - Resolved git directory path, or null if not a git repo
|
|
307
|
+
* @param config.foldersForExclude - Folders to add to git exclude
|
|
321
308
|
*/
|
|
322
309
|
async performPostInstallActions(config) {
|
|
323
|
-
const { targetDir, method, ides,
|
|
310
|
+
const { targetDir, method, ides, gitDir, foldersForExclude } = config;
|
|
324
311
|
// Track method installation in settings.json first (so reconstructor can read methods list)
|
|
325
312
|
await this.trackMethodInstallation(targetDir, method, ides);
|
|
326
313
|
// Read installed methods to build active templates list
|
|
@@ -330,10 +317,10 @@ export default class Init extends BaseCommand {
|
|
|
330
317
|
// Reconstruct IDE settings from all active templates
|
|
331
318
|
await reconstructIdeSettings(targetDir, activeTemplates, ides);
|
|
332
319
|
this.logSuccess('✓ Reconstructed IDE settings from active templates');
|
|
333
|
-
// Update
|
|
334
|
-
if (
|
|
335
|
-
await
|
|
336
|
-
this.logSuccess('✓
|
|
320
|
+
// Update git exclude if git repository exists
|
|
321
|
+
if (gitDir) {
|
|
322
|
+
await updateGitExclude(gitDir, foldersForExclude);
|
|
323
|
+
this.logSuccess('✓ git exclude updated');
|
|
337
324
|
}
|
|
338
325
|
}
|
|
339
326
|
/**
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Standard exclude entries managed by AIW */
|
|
2
|
+
export declare const AIW_EXCLUDE_ENTRIES: string[];
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the git directory for a given target directory.
|
|
5
|
+
* Handles normal repos, worktrees, and submodules via `git rev-parse --git-dir`.
|
|
6
|
+
*
|
|
7
|
+
* @param targetDir - Directory to resolve git dir for
|
|
8
|
+
* @returns Absolute path to the git directory, or null if not a git repo
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveGitDir(targetDir: string): Promise<string | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Prune stale entries from the AIW Installation section in git exclude file.
|
|
13
|
+
* Checks each entry against disk existence and removes entries whose paths don't exist.
|
|
14
|
+
* Removes the entire section if no entries remain after pruning.
|
|
15
|
+
*
|
|
16
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
17
|
+
* @param targetDir - Project root directory (for disk-existence checks)
|
|
18
|
+
* @returns True if any entries were pruned
|
|
19
|
+
*/
|
|
20
|
+
export declare function pruneExcludeStaleEntries(gitDir: string, targetDir: string): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Update git exclude file with patterns for installed folders.
|
|
23
|
+
*
|
|
24
|
+
* Creates exclude file if it doesn't exist, or appends to existing file.
|
|
25
|
+
* Prevents duplicate patterns by checking each pattern individually.
|
|
26
|
+
*
|
|
27
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
28
|
+
* @param folders - List of folder names to add as exclude patterns (e.g., ['.aiwcli', '.claude'])
|
|
29
|
+
*/
|
|
30
|
+
export declare function updateGitExclude(gitDir: string, folders: string[]): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Compute which AIW exclude entries should be removed during clear.
|
|
33
|
+
* Returns a simulation result — the caller decides whether to apply.
|
|
34
|
+
*
|
|
35
|
+
* Logic per entry:
|
|
36
|
+
* - If in permanentEntries → keep (reason: "permanent")
|
|
37
|
+
* - If directory exists and is non-empty → keep (reason: "directory has content")
|
|
38
|
+
* - Otherwise → mark for removal
|
|
39
|
+
*
|
|
40
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
41
|
+
* @param targetDir - Project root directory (for disk-existence checks)
|
|
42
|
+
* @param permanentEntries - Entries that should never be removed (defaults to AIW_PERMANENT_ENTRIES)
|
|
43
|
+
* @returns Lists of entries to remove and entries to keep with reasons
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeExcludeRemovals(gitDir: string, targetDir: string, permanentEntries?: string[]): Promise<{
|
|
46
|
+
toKeep: Array<{
|
|
47
|
+
entry: string;
|
|
48
|
+
reason: string;
|
|
49
|
+
}>;
|
|
50
|
+
toRemove: string[];
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Remove specific entries from the AIW section in git exclude file.
|
|
54
|
+
* Cleans up the section header if no entries remain.
|
|
55
|
+
*
|
|
56
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
57
|
+
* @param entriesToRemove - Entry names to remove (without trailing slash)
|
|
58
|
+
*/
|
|
59
|
+
export declare function removeExcludeEntries(gitDir: string, entriesToRemove: string[]): Promise<void>;
|
|
@@ -1,27 +1,60 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
1
2
|
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
+
import { isAbsolute, join } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
3
5
|
import { pathExists } from './paths.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
4
7
|
/**
|
|
5
|
-
* AIW
|
|
8
|
+
* AIW exclude section header marker
|
|
6
9
|
*/
|
|
7
|
-
const
|
|
8
|
-
/** Standard
|
|
9
|
-
export const
|
|
10
|
-
/** Entries that should NEVER be removed from
|
|
10
|
+
const AIW_EXCLUDE_HEADER = '# AIW Installation';
|
|
11
|
+
/** Standard exclude entries managed by AIW */
|
|
12
|
+
export const AIW_EXCLUDE_ENTRIES = ['.aiwcli', '_output', '.claude', '.windsurf'];
|
|
13
|
+
/** Entries that should NEVER be removed from exclude, even on clear */
|
|
11
14
|
const AIW_PERMANENT_ENTRIES = ['_output'];
|
|
12
15
|
/**
|
|
13
|
-
*
|
|
16
|
+
* Resolve the git directory for a given target directory.
|
|
17
|
+
* Handles normal repos, worktrees, and submodules via `git rev-parse --git-dir`.
|
|
18
|
+
*
|
|
19
|
+
* @param targetDir - Directory to resolve git dir for
|
|
20
|
+
* @returns Absolute path to the git directory, or null if not a git repo
|
|
21
|
+
*/
|
|
22
|
+
export async function resolveGitDir(targetDir) {
|
|
23
|
+
try {
|
|
24
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', '--git-dir'], { cwd: targetDir });
|
|
25
|
+
const gitDir = stdout.trim();
|
|
26
|
+
return isAbsolute(gitDir) ? gitDir : join(targetDir, gitDir);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the path to the exclude file within the git directory.
|
|
34
|
+
*/
|
|
35
|
+
function getExcludePath(gitDir) {
|
|
36
|
+
return join(gitDir, 'info', 'exclude');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Ensure the info/ directory exists within the git directory.
|
|
40
|
+
*/
|
|
41
|
+
async function ensureInfoDir(gitDir) {
|
|
42
|
+
await fs.mkdir(join(gitDir, 'info'), { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Prune stale entries from the AIW Installation section in git exclude file.
|
|
14
46
|
* Checks each entry against disk existence and removes entries whose paths don't exist.
|
|
15
47
|
* Removes the entire section if no entries remain after pruning.
|
|
16
48
|
*
|
|
17
|
-
* @param
|
|
49
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
50
|
+
* @param targetDir - Project root directory (for disk-existence checks)
|
|
18
51
|
* @returns True if any entries were pruned
|
|
19
52
|
*/
|
|
20
|
-
export async function
|
|
21
|
-
const
|
|
53
|
+
export async function pruneExcludeStaleEntries(gitDir, targetDir) {
|
|
54
|
+
const excludePath = getExcludePath(gitDir);
|
|
22
55
|
try {
|
|
23
|
-
const content = await fs.readFile(
|
|
24
|
-
if (!content.includes(
|
|
56
|
+
const content = await fs.readFile(excludePath, 'utf8');
|
|
57
|
+
if (!content.includes(AIW_EXCLUDE_HEADER)) {
|
|
25
58
|
return false;
|
|
26
59
|
}
|
|
27
60
|
const lines = content.split('\n');
|
|
@@ -30,7 +63,7 @@ export async function pruneGitignoreStaleEntries(targetDir) {
|
|
|
30
63
|
const aiwSectionLines = [];
|
|
31
64
|
let pruned = false;
|
|
32
65
|
for (const line of lines) {
|
|
33
|
-
if (line ===
|
|
66
|
+
if (line === AIW_EXCLUDE_HEADER) {
|
|
34
67
|
inAiwSection = true;
|
|
35
68
|
aiwSectionLines.push(line);
|
|
36
69
|
continue;
|
|
@@ -40,7 +73,7 @@ export async function pruneGitignoreStaleEntries(targetDir) {
|
|
|
40
73
|
continue;
|
|
41
74
|
}
|
|
42
75
|
// AIW section ends at empty line or another comment header
|
|
43
|
-
if (line === '' || (line.startsWith('#') && line !==
|
|
76
|
+
if (line === '' || (line.startsWith('#') && line !== AIW_EXCLUDE_HEADER)) {
|
|
44
77
|
inAiwSection = false;
|
|
45
78
|
const { lines: filtered, pruned: sectionPruned } = await pruneSection(aiwSectionLines, targetDir); // eslint-disable-line no-await-in-loop
|
|
46
79
|
if (sectionPruned)
|
|
@@ -68,7 +101,7 @@ export async function pruneGitignoreStaleEntries(targetDir) {
|
|
|
68
101
|
if (result.trim() === '') {
|
|
69
102
|
result = '';
|
|
70
103
|
}
|
|
71
|
-
await fs.writeFile(
|
|
104
|
+
await fs.writeFile(excludePath, result, 'utf8');
|
|
72
105
|
return true;
|
|
73
106
|
}
|
|
74
107
|
catch {
|
|
@@ -77,14 +110,14 @@ export async function pruneGitignoreStaleEntries(targetDir) {
|
|
|
77
110
|
}
|
|
78
111
|
/**
|
|
79
112
|
* Prune stale entries from a parsed AIW section.
|
|
80
|
-
* Checks each
|
|
113
|
+
* Checks each exclude pattern against disk existence.
|
|
81
114
|
*/
|
|
82
115
|
async function pruneSection(sectionLines, targetDir) {
|
|
83
116
|
let pruned = false;
|
|
84
117
|
const filtered = [];
|
|
85
118
|
for (const line of sectionLines) {
|
|
86
119
|
// Always keep the header
|
|
87
|
-
if (line ===
|
|
120
|
+
if (line === AIW_EXCLUDE_HEADER) {
|
|
88
121
|
filtered.push(line);
|
|
89
122
|
continue;
|
|
90
123
|
}
|
|
@@ -108,7 +141,7 @@ function cleanupEmptySections(content) {
|
|
|
108
141
|
const newLines = [];
|
|
109
142
|
for (let i = 0; i < lines.length; i++) {
|
|
110
143
|
const line = lines[i];
|
|
111
|
-
if (line ===
|
|
144
|
+
if (line === AIW_EXCLUDE_HEADER) {
|
|
112
145
|
// Look ahead to see if there are any patterns
|
|
113
146
|
const nextLine = lines[i + 1];
|
|
114
147
|
if (nextLine === undefined || nextLine === '' || nextLine.startsWith('#')) {
|
|
@@ -125,20 +158,21 @@ function cleanupEmptySections(content) {
|
|
|
125
158
|
return newLines.join('\n');
|
|
126
159
|
}
|
|
127
160
|
/**
|
|
128
|
-
* Update
|
|
161
|
+
* Update git exclude file with patterns for installed folders.
|
|
129
162
|
*
|
|
130
|
-
* Creates
|
|
163
|
+
* Creates exclude file if it doesn't exist, or appends to existing file.
|
|
131
164
|
* Prevents duplicate patterns by checking each pattern individually.
|
|
132
165
|
*
|
|
133
|
-
* @param
|
|
134
|
-
* @param folders - List of folder names to add as
|
|
166
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
167
|
+
* @param folders - List of folder names to add as exclude patterns (e.g., ['.aiwcli', '.claude'])
|
|
135
168
|
*/
|
|
136
|
-
export async function
|
|
137
|
-
|
|
169
|
+
export async function updateGitExclude(gitDir, folders) {
|
|
170
|
+
await ensureInfoDir(gitDir);
|
|
171
|
+
const excludePath = getExcludePath(gitDir);
|
|
138
172
|
try {
|
|
139
|
-
// Try to read existing
|
|
140
|
-
const existing = await fs.readFile(
|
|
141
|
-
// Filter out patterns that already exist in
|
|
173
|
+
// Try to read existing exclude file
|
|
174
|
+
const existing = await fs.readFile(excludePath, 'utf8');
|
|
175
|
+
// Filter out patterns that already exist in exclude file
|
|
142
176
|
const newPatterns = folders.filter((folder) => {
|
|
143
177
|
const pattern = `${folder}/`;
|
|
144
178
|
// Check if this exact pattern exists in the file
|
|
@@ -173,17 +207,17 @@ export async function updateGitignore(targetDir, folders) {
|
|
|
173
207
|
const separator = existing.length > 0 && existing.endsWith('\n') ? '\n' : existing.length > 0 ? '\n\n' : '';
|
|
174
208
|
updatedContent = existing + separator + `# AIW Installation\n${patterns}\n`;
|
|
175
209
|
}
|
|
176
|
-
await fs.writeFile(
|
|
210
|
+
await fs.writeFile(excludePath, updatedContent, 'utf8');
|
|
177
211
|
}
|
|
178
212
|
catch {
|
|
179
|
-
//
|
|
213
|
+
// Exclude file doesn't exist, create it
|
|
180
214
|
const patterns = folders.map((folder) => `${folder}/`).join('\n');
|
|
181
215
|
const patternsBlock = `# AIW Installation\n${patterns}`;
|
|
182
|
-
await fs.writeFile(
|
|
216
|
+
await fs.writeFile(excludePath, patternsBlock + '\n', 'utf8');
|
|
183
217
|
}
|
|
184
218
|
}
|
|
185
219
|
/**
|
|
186
|
-
* Compute which AIW
|
|
220
|
+
* Compute which AIW exclude entries should be removed during clear.
|
|
187
221
|
* Returns a simulation result — the caller decides whether to apply.
|
|
188
222
|
*
|
|
189
223
|
* Logic per entry:
|
|
@@ -191,23 +225,24 @@ export async function updateGitignore(targetDir, folders) {
|
|
|
191
225
|
* - If directory exists and is non-empty → keep (reason: "directory has content")
|
|
192
226
|
* - Otherwise → mark for removal
|
|
193
227
|
*
|
|
194
|
-
* @param
|
|
228
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
229
|
+
* @param targetDir - Project root directory (for disk-existence checks)
|
|
195
230
|
* @param permanentEntries - Entries that should never be removed (defaults to AIW_PERMANENT_ENTRIES)
|
|
196
231
|
* @returns Lists of entries to remove and entries to keep with reasons
|
|
197
232
|
*/
|
|
198
|
-
export async function
|
|
199
|
-
const
|
|
233
|
+
export async function computeExcludeRemovals(gitDir, targetDir, permanentEntries = AIW_PERMANENT_ENTRIES) {
|
|
234
|
+
const excludePath = getExcludePath(gitDir);
|
|
200
235
|
const toRemove = [];
|
|
201
236
|
const toKeep = [];
|
|
202
|
-
// Read AIW section entries from
|
|
237
|
+
// Read AIW section entries from exclude file
|
|
203
238
|
let content;
|
|
204
239
|
try {
|
|
205
|
-
content = await fs.readFile(
|
|
240
|
+
content = await fs.readFile(excludePath, 'utf8');
|
|
206
241
|
}
|
|
207
242
|
catch {
|
|
208
243
|
return { toRemove, toKeep };
|
|
209
244
|
}
|
|
210
|
-
if (!content.includes(
|
|
245
|
+
if (!content.includes(AIW_EXCLUDE_HEADER)) {
|
|
211
246
|
return { toRemove, toKeep };
|
|
212
247
|
}
|
|
213
248
|
// Parse entries from the AIW section
|
|
@@ -215,12 +250,12 @@ export async function computeGitignoreRemovals(targetDir, permanentEntries = AIW
|
|
|
215
250
|
let inAiwSection = false;
|
|
216
251
|
const aiwEntries = [];
|
|
217
252
|
for (const line of lines) {
|
|
218
|
-
if (line ===
|
|
253
|
+
if (line === AIW_EXCLUDE_HEADER) {
|
|
219
254
|
inAiwSection = true;
|
|
220
255
|
continue;
|
|
221
256
|
}
|
|
222
257
|
if (inAiwSection) {
|
|
223
|
-
if (line === '' || (line.startsWith('#') && line !==
|
|
258
|
+
if (line === '' || (line.startsWith('#') && line !== AIW_EXCLUDE_HEADER)) {
|
|
224
259
|
inAiwSection = false;
|
|
225
260
|
}
|
|
226
261
|
else {
|
|
@@ -261,17 +296,17 @@ export async function computeGitignoreRemovals(targetDir, permanentEntries = AIW
|
|
|
261
296
|
return { toRemove, toKeep };
|
|
262
297
|
}
|
|
263
298
|
/**
|
|
264
|
-
* Remove specific entries from the AIW section in .
|
|
299
|
+
* Remove specific entries from the AIW section in git exclude file.
|
|
265
300
|
* Cleans up the section header if no entries remain.
|
|
266
301
|
*
|
|
267
|
-
* @param
|
|
302
|
+
* @param gitDir - Git directory (contains info/exclude)
|
|
268
303
|
* @param entriesToRemove - Entry names to remove (without trailing slash)
|
|
269
304
|
*/
|
|
270
|
-
export async function
|
|
271
|
-
const
|
|
305
|
+
export async function removeExcludeEntries(gitDir, entriesToRemove) {
|
|
306
|
+
const excludePath = getExcludePath(gitDir);
|
|
272
307
|
try {
|
|
273
|
-
const content = await fs.readFile(
|
|
274
|
-
if (!content.includes(
|
|
308
|
+
const content = await fs.readFile(excludePath, 'utf8');
|
|
309
|
+
if (!content.includes(AIW_EXCLUDE_HEADER)) {
|
|
275
310
|
return;
|
|
276
311
|
}
|
|
277
312
|
const patternsToRemove = new Set(entriesToRemove.map((e) => `${e}/`));
|
|
@@ -280,16 +315,16 @@ export async function removeGitignoreEntries(targetDir, entriesToRemove) {
|
|
|
280
315
|
let inAiwSection = false;
|
|
281
316
|
const aiwSectionLines = [];
|
|
282
317
|
for (const line of lines) {
|
|
283
|
-
if (line ===
|
|
318
|
+
if (line === AIW_EXCLUDE_HEADER) {
|
|
284
319
|
inAiwSection = true;
|
|
285
320
|
aiwSectionLines.push(line);
|
|
286
321
|
continue;
|
|
287
322
|
}
|
|
288
323
|
if (inAiwSection) {
|
|
289
|
-
if (line === '' || (line.startsWith('#') && line !==
|
|
324
|
+
if (line === '' || (line.startsWith('#') && line !== AIW_EXCLUDE_HEADER)) {
|
|
290
325
|
inAiwSection = false;
|
|
291
326
|
// Filter the AIW section
|
|
292
|
-
const filtered = aiwSectionLines.filter((l) => l ===
|
|
327
|
+
const filtered = aiwSectionLines.filter((l) => l === AIW_EXCLUDE_HEADER || !patternsToRemove.has(l));
|
|
293
328
|
newLines.push(...filtered, line);
|
|
294
329
|
}
|
|
295
330
|
else {
|
|
@@ -302,7 +337,7 @@ export async function removeGitignoreEntries(targetDir, entriesToRemove) {
|
|
|
302
337
|
}
|
|
303
338
|
// Handle AIW section at end of file
|
|
304
339
|
if (inAiwSection) {
|
|
305
|
-
const filtered = aiwSectionLines.filter((l) => l ===
|
|
340
|
+
const filtered = aiwSectionLines.filter((l) => l === AIW_EXCLUDE_HEADER || !patternsToRemove.has(l));
|
|
306
341
|
newLines.push(...filtered);
|
|
307
342
|
}
|
|
308
343
|
// Clean up empty AIW section
|
|
@@ -311,9 +346,9 @@ export async function removeGitignoreEntries(targetDir, entriesToRemove) {
|
|
|
311
346
|
if (result.trim() === '') {
|
|
312
347
|
result = '';
|
|
313
348
|
}
|
|
314
|
-
await fs.writeFile(
|
|
349
|
+
await fs.writeFile(excludePath, result, 'utf8');
|
|
315
350
|
}
|
|
316
351
|
catch {
|
|
317
|
-
//
|
|
352
|
+
// Exclude file doesn't exist or can't be read
|
|
318
353
|
}
|
|
319
354
|
}
|
|
@@ -138,6 +138,17 @@ export function isExecSyncError(e: unknown): e is ExecSyncError {
|
|
|
138
138
|
* On non-Windows platforms, returns the string unchanged (execFile
|
|
139
139
|
* handles quoting automatically without shell).
|
|
140
140
|
*/
|
|
141
|
+
/**
|
|
142
|
+
* Normalize a file path for CLI subprocess usage.
|
|
143
|
+
* Replaces backslashes with forward slashes on Windows to avoid
|
|
144
|
+
* cmd.exe escape interpretation issues. No-op on other platforms.
|
|
145
|
+
* Forward slashes are valid path separators on Windows in all modern CLIs.
|
|
146
|
+
*/
|
|
147
|
+
export function normalizePathForCli(p: string): string {
|
|
148
|
+
if (process.platform !== "win32") return p;
|
|
149
|
+
return p.replaceAll("\\", "/");
|
|
150
|
+
}
|
|
151
|
+
|
|
141
152
|
export function shellQuoteWin(arg: string): string {
|
|
142
153
|
if (process.platform !== "win32") return arg;
|
|
143
154
|
return '"' + arg.replaceAll('"', '""') + '"';
|
|
@@ -199,6 +199,15 @@ function isWideChar(cp: number): boolean {
|
|
|
199
199
|
);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
function measureWidth(str: string): number {
|
|
203
|
+
const stripped = str.replace(ANSI_RE, "");
|
|
204
|
+
let w = 0;
|
|
205
|
+
for (const ch of stripped) {
|
|
206
|
+
w += isWideChar(ch.codePointAt(0) ?? 0) ? 2 : 1;
|
|
207
|
+
}
|
|
208
|
+
return w;
|
|
209
|
+
}
|
|
210
|
+
|
|
202
211
|
function truncateToWidth(str: string, maxWidth: number): string {
|
|
203
212
|
// Walk through string preserving ANSI codes (zero-width) and measuring visible chars
|
|
204
213
|
let w = 0;
|
|
@@ -244,9 +253,8 @@ function truncateToWidth(str: string, maxWidth: number): string {
|
|
|
244
253
|
// Separator (dynamic width)
|
|
245
254
|
// ---------------------------------------------------------------------------
|
|
246
255
|
|
|
247
|
-
function makeSeparator(
|
|
248
|
-
|
|
249
|
-
return `${SLATE_600}${"─".repeat(w)}${RESET}`;
|
|
256
|
+
function makeSeparator(width: number): string {
|
|
257
|
+
return `${SLATE_600}${"─".repeat(Math.max(1, width))}${RESET}`;
|
|
250
258
|
}
|
|
251
259
|
|
|
252
260
|
// ---------------------------------------------------------------------------
|
|
@@ -276,7 +284,7 @@ function renderContext(
|
|
|
276
284
|
contextK: number,
|
|
277
285
|
maxK: number,
|
|
278
286
|
modelName: string,
|
|
279
|
-
):
|
|
287
|
+
): string {
|
|
280
288
|
let pctColor: string;
|
|
281
289
|
if (contextPct <= 33) pctColor = EMERALD;
|
|
282
290
|
else if (contextPct <= 66) pctColor = AMBER;
|
|
@@ -342,8 +350,7 @@ function renderContext(
|
|
|
342
350
|
}
|
|
343
351
|
}
|
|
344
352
|
|
|
345
|
-
|
|
346
|
-
console.log(makeSeparator(termWidth));
|
|
353
|
+
return truncateToWidth(line, termWidth);
|
|
347
354
|
}
|
|
348
355
|
|
|
349
356
|
// ---------------------------------------------------------------------------
|
|
@@ -461,14 +468,16 @@ function renderGit(
|
|
|
461
468
|
termWidth: number,
|
|
462
469
|
git: GitStatus | null,
|
|
463
470
|
dirName: string,
|
|
464
|
-
):
|
|
471
|
+
): string {
|
|
465
472
|
const totalChanged = git ? git.modified + git.staged : 0;
|
|
466
473
|
const statusIcon =
|
|
467
474
|
git && (totalChanged > 0 || git.untracked > 0) ? "*" : "\u2713";
|
|
468
475
|
|
|
476
|
+
let line: string;
|
|
477
|
+
|
|
469
478
|
switch (mode) {
|
|
470
479
|
case "micro": {
|
|
471
|
-
|
|
480
|
+
line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET}`;
|
|
472
481
|
if (git) {
|
|
473
482
|
line += ` ${GIT_VALUE}${git.branch}${RESET}`;
|
|
474
483
|
if (git.age_display) {
|
|
@@ -481,12 +490,10 @@ function renderGit(
|
|
|
481
490
|
line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
|
|
482
491
|
}
|
|
483
492
|
}
|
|
484
|
-
console.log(truncateToWidth(line, termWidth));
|
|
485
|
-
|
|
486
493
|
break;
|
|
487
494
|
}
|
|
488
495
|
case "mini": {
|
|
489
|
-
|
|
496
|
+
line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET}`;
|
|
490
497
|
if (git) {
|
|
491
498
|
line += ` ${SLATE_600}\u2502${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
|
|
492
499
|
if (git.age_display) {
|
|
@@ -502,12 +509,10 @@ function renderGit(
|
|
|
502
509
|
}
|
|
503
510
|
}
|
|
504
511
|
}
|
|
505
|
-
console.log(truncateToWidth(line, termWidth));
|
|
506
|
-
|
|
507
512
|
break;
|
|
508
513
|
}
|
|
509
514
|
case "nano": {
|
|
510
|
-
|
|
515
|
+
line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET}`;
|
|
511
516
|
if (git) {
|
|
512
517
|
line += ` ${GIT_VALUE}${git.branch}${RESET} `;
|
|
513
518
|
if (statusIcon === "\u2713") {
|
|
@@ -516,12 +521,10 @@ function renderGit(
|
|
|
516
521
|
line += `${GIT_MODIFIED}*${totalChanged}${RESET}`;
|
|
517
522
|
}
|
|
518
523
|
}
|
|
519
|
-
console.log(truncateToWidth(line, termWidth));
|
|
520
|
-
|
|
521
524
|
break;
|
|
522
525
|
}
|
|
523
526
|
default: {
|
|
524
|
-
|
|
527
|
+
line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_PRIMARY}PWD:${RESET} ${GIT_DIR}${dirName}${RESET}`;
|
|
525
528
|
if (git) {
|
|
526
529
|
line +=
|
|
527
530
|
` ${SLATE_600}\u2502${RESET} ` +
|
|
@@ -556,9 +559,10 @@ function renderGit(
|
|
|
556
559
|
}
|
|
557
560
|
}
|
|
558
561
|
}
|
|
559
|
-
console.log(truncateToWidth(line, termWidth));
|
|
560
562
|
}
|
|
561
563
|
}
|
|
564
|
+
|
|
565
|
+
return truncateToWidth(line, termWidth);
|
|
562
566
|
}
|
|
563
567
|
|
|
564
568
|
// ---------------------------------------------------------------------------
|
|
@@ -587,7 +591,7 @@ function renderContextManager(
|
|
|
587
591
|
mode: string,
|
|
588
592
|
contextId: string,
|
|
589
593
|
contextState: Record<string, unknown> | null,
|
|
590
|
-
):
|
|
594
|
+
): string {
|
|
591
595
|
// Strip YYMMDD-HHMM- timestamp prefix from context ID for display
|
|
592
596
|
let displayId = contextId.replace(/^\d{6}-\d{4}-/, "");
|
|
593
597
|
if (!displayId) displayId = contextId;
|
|
@@ -650,47 +654,24 @@ function renderContextManager(
|
|
|
650
654
|
planPart = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Plan:${RESET} ${SLATE_300}${truncatedPlan}${RESET}`;
|
|
651
655
|
}
|
|
652
656
|
|
|
657
|
+
// Note: termWidth not available here — caller truncates via main()
|
|
653
658
|
switch (mode) {
|
|
654
|
-
case "micro":
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
case "mini": {
|
|
662
|
-
console.log(
|
|
663
|
-
`${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}` +
|
|
664
|
-
`${modeBadge}${planPart}`,
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
break;
|
|
668
|
-
}
|
|
669
|
-
case "nano": {
|
|
670
|
-
console.log(
|
|
671
|
-
`${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`,
|
|
672
|
-
);
|
|
673
|
-
|
|
674
|
-
break;
|
|
675
|
-
}
|
|
676
|
-
default: {
|
|
677
|
-
console.log(
|
|
678
|
-
`${CTX_ACCENT}\u25C6${RESET} ${CTX_SECONDARY}Context:${RESET} ${SLATE_300}${truncatedId}${RESET}` +
|
|
679
|
-
`${modeBadge}${planPart}`,
|
|
680
|
-
);
|
|
681
|
-
}
|
|
659
|
+
case "micro":
|
|
660
|
+
case "nano":
|
|
661
|
+
return `${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`;
|
|
662
|
+
case "mini":
|
|
663
|
+
return `${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}${planPart}`;
|
|
664
|
+
default:
|
|
665
|
+
return `${CTX_ACCENT}\u25C6${RESET} ${CTX_SECONDARY}Context:${RESET} ${SLATE_300}${truncatedId}${RESET}${modeBadge}${planPart}`;
|
|
682
666
|
}
|
|
683
667
|
}
|
|
684
668
|
|
|
685
|
-
function renderNoContext(mode: string):
|
|
669
|
+
function renderNoContext(mode: string): string {
|
|
686
670
|
const warn = `${ROSE}\u26A0 ${RESET}`;
|
|
687
671
|
if (mode === "normal") {
|
|
688
|
-
|
|
689
|
-
`${warn} ${ROSE}NO CONTEXT${RESET} ${SLATE_500}\u2014 type ^ for context manager${RESET}`,
|
|
690
|
-
);
|
|
691
|
-
} else {
|
|
692
|
-
console.log(`${warn} ${ROSE}NO CONTEXT${RESET}`);
|
|
672
|
+
return `${warn} ${ROSE}NO CONTEXT${RESET} ${SLATE_500}\u2014 type ^ for context manager${RESET}`;
|
|
693
673
|
}
|
|
674
|
+
return `${warn} ${ROSE}NO CONTEXT${RESET}`;
|
|
694
675
|
}
|
|
695
676
|
|
|
696
677
|
// ---------------------------------------------------------------------------
|
|
@@ -826,22 +807,35 @@ function main(): void {
|
|
|
826
807
|
// Resolve context ID for display and persistence
|
|
827
808
|
const contextId = resolveContextId(sessionId);
|
|
828
809
|
|
|
829
|
-
// Render
|
|
830
|
-
renderContext(mode, termWidth, contextPct, contextK, maxK, modelName);
|
|
831
|
-
|
|
832
|
-
// Render PWD + git section (PWD always shown, git stats only when in a repo)
|
|
810
|
+
// Render content lines first, truncate all to terminal width, then size separators
|
|
811
|
+
const contextLine = renderContext(mode, termWidth, contextPct, contextK, maxK, modelName);
|
|
833
812
|
const git = getGitStatus(currentDir);
|
|
834
|
-
renderGit(mode, termWidth, git, dirName);
|
|
813
|
+
const gitLine = renderGit(mode, termWidth, git, dirName);
|
|
835
814
|
|
|
836
|
-
|
|
837
|
-
console.log(makeSeparator(termWidth));
|
|
815
|
+
let ctxMgrLine: string;
|
|
838
816
|
if (contextId) {
|
|
839
817
|
const contextState = loadContextState(contextId);
|
|
840
|
-
renderContextManager(mode, contextId, contextState);
|
|
818
|
+
ctxMgrLine = truncateToWidth(renderContextManager(mode, contextId, contextState), termWidth);
|
|
841
819
|
} else {
|
|
842
|
-
renderNoContext(mode);
|
|
820
|
+
ctxMgrLine = truncateToWidth(renderNoContext(mode), termWidth);
|
|
843
821
|
}
|
|
844
822
|
|
|
823
|
+
// Measure visible width of each content line, cap separator at terminal width
|
|
824
|
+
const maxContentWidth = Math.min(
|
|
825
|
+
termWidth,
|
|
826
|
+
Math.max(
|
|
827
|
+
measureWidth(contextLine),
|
|
828
|
+
measureWidth(gitLine),
|
|
829
|
+
measureWidth(ctxMgrLine),
|
|
830
|
+
),
|
|
831
|
+
);
|
|
832
|
+
const sep = makeSeparator(maxContentWidth);
|
|
833
|
+
|
|
834
|
+
const lines = [contextLine, sep, gitLine, sep, ctxMgrLine];
|
|
835
|
+
|
|
836
|
+
// Single atomic write — prevents partial output if process is interrupted
|
|
837
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
838
|
+
|
|
845
839
|
// Persist context_window to state.json
|
|
846
840
|
if (contextId) {
|
|
847
841
|
writeContextWindow(contextId, {
|
|
@@ -71,7 +71,7 @@ export const DEFAULT_COMPLEXITY_CATEGORIES = ["code", "infrastructure", "documen
|
|
|
71
71
|
export const DEFAULT_MODELS_CONFIG: ModelsConfig = {
|
|
72
72
|
providers: {
|
|
73
73
|
claude: { enabled: true, models: ["sonnet"] },
|
|
74
|
-
codex: { enabled: true, models: ["
|
|
74
|
+
codex: { enabled: true, models: ["codex-mini-latest"] },
|
|
75
75
|
},
|
|
76
76
|
};
|
|
77
77
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { logDebug, logInfo, logWarn, logError } from "../../../../../_shared/lib-ts/base/logger.js";
|
|
8
|
-
import { getInternalSubprocessEnv, findExecutable, execFileAsync } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
8
|
+
import { getInternalSubprocessEnv, findExecutable, execFileAsync, normalizePathForCli } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
9
9
|
import { debugLog, debugRaw } from "../../../../lib-ts/debug.js";
|
|
10
10
|
import type { AgentConfig } from "../../../../lib-ts/types.js";
|
|
11
11
|
|
|
@@ -176,7 +176,8 @@ export abstract class BaseCliAgent<T> {
|
|
|
176
176
|
|
|
177
177
|
// 3. Execute subprocess
|
|
178
178
|
const env = getInternalSubprocessEnv();
|
|
179
|
-
const
|
|
179
|
+
const normalizedCliPath = normalizePathForCli(cliPath);
|
|
180
|
+
const result = await execFileAsync(normalizedCliPath, args, {
|
|
180
181
|
input: prompt,
|
|
181
182
|
timeout: this.timeout * 1000,
|
|
182
183
|
env: env as Record<string, string>,
|
package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/codex-agent.ts
CHANGED
|
@@ -8,7 +8,7 @@ import * as os from "node:os";
|
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
|
|
10
10
|
import { logDebug, logWarn } from "../../../../../_shared/lib-ts/base/logger.js";
|
|
11
|
-
import { getInternalSubprocessEnv, execFileAsync } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
11
|
+
import { getInternalSubprocessEnv, execFileAsync, normalizePathForCli, shellQuoteWin } from "../../../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
12
12
|
import { debugLog, debugRaw } from "../../../../lib-ts/debug.js";
|
|
13
13
|
import { parseJsonMaybe, coerceToReview } from "../../../../lib-ts/json-parser.js";
|
|
14
14
|
import type { ReviewerResult } from "../../../../lib-ts/types.js";
|
|
@@ -35,9 +35,12 @@ export class CodexAgent extends BaseCliAgent<ReviewerResult> {
|
|
|
35
35
|
const outPath = path.join(this.tempDir, "output.json");
|
|
36
36
|
fs.writeFileSync(schemaPath, JSON.stringify(this.schema, null, 2), "utf-8");
|
|
37
37
|
|
|
38
|
-
const
|
|
38
|
+
const normalizedSchema = shellQuoteWin(normalizePathForCli(schemaPath));
|
|
39
|
+
const normalizedOut = shellQuoteWin(normalizePathForCli(outPath));
|
|
40
|
+
|
|
41
|
+
const cmdArgs = ["exec", "--sandbox", "read-only", "--reasoning", "medium"];
|
|
39
42
|
if (this.agent.model) cmdArgs.push("--model", this.agent.model);
|
|
40
|
-
cmdArgs.push("--output-schema",
|
|
43
|
+
cmdArgs.push("--output-schema", normalizedSchema, "-o", normalizedOut, "-");
|
|
41
44
|
|
|
42
45
|
return cmdArgs;
|
|
43
46
|
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/** Standard gitignore entries managed by AIW */
|
|
2
|
-
export declare const AIW_GITIGNORE_ENTRIES: string[];
|
|
3
|
-
/**
|
|
4
|
-
* Prune stale entries from the AIW Installation section in .gitignore.
|
|
5
|
-
* Checks each entry against disk existence and removes entries whose paths don't exist.
|
|
6
|
-
* Removes the entire section if no entries remain after pruning.
|
|
7
|
-
*
|
|
8
|
-
* @param targetDir - Directory containing .gitignore
|
|
9
|
-
* @returns True if any entries were pruned
|
|
10
|
-
*/
|
|
11
|
-
export declare function pruneGitignoreStaleEntries(targetDir: string): Promise<boolean>;
|
|
12
|
-
/**
|
|
13
|
-
* Update .gitignore with patterns for installed folders.
|
|
14
|
-
*
|
|
15
|
-
* Creates .gitignore if it doesn't exist, or appends to existing file.
|
|
16
|
-
* Prevents duplicate patterns by checking each pattern individually.
|
|
17
|
-
*
|
|
18
|
-
* @param targetDir - Directory containing .gitignore file
|
|
19
|
-
* @param folders - List of folder names to add as gitignore patterns (e.g., ['_bmad', '.claude'])
|
|
20
|
-
*/
|
|
21
|
-
export declare function updateGitignore(targetDir: string, folders: string[]): Promise<void>;
|
|
22
|
-
/**
|
|
23
|
-
* Compute which AIW gitignore entries should be removed during clear.
|
|
24
|
-
* Returns a simulation result — the caller decides whether to apply.
|
|
25
|
-
*
|
|
26
|
-
* Logic per entry:
|
|
27
|
-
* - If in permanentEntries → keep (reason: "permanent")
|
|
28
|
-
* - If directory exists and is non-empty → keep (reason: "directory has content")
|
|
29
|
-
* - Otherwise → mark for removal
|
|
30
|
-
*
|
|
31
|
-
* @param targetDir - Directory containing .gitignore
|
|
32
|
-
* @param permanentEntries - Entries that should never be removed (defaults to AIW_PERMANENT_ENTRIES)
|
|
33
|
-
* @returns Lists of entries to remove and entries to keep with reasons
|
|
34
|
-
*/
|
|
35
|
-
export declare function computeGitignoreRemovals(targetDir: string, permanentEntries?: string[]): Promise<{
|
|
36
|
-
toKeep: Array<{
|
|
37
|
-
entry: string;
|
|
38
|
-
reason: string;
|
|
39
|
-
}>;
|
|
40
|
-
toRemove: string[];
|
|
41
|
-
}>;
|
|
42
|
-
/**
|
|
43
|
-
* Remove specific entries from the AIW section in .gitignore.
|
|
44
|
-
* Cleans up the section header if no entries remain.
|
|
45
|
-
*
|
|
46
|
-
* @param targetDir - Directory containing .gitignore
|
|
47
|
-
* @param entriesToRemove - Entry names to remove (without trailing slash)
|
|
48
|
-
*/
|
|
49
|
-
export declare function removeGitignoreEntries(targetDir: string, entriesToRemove: string[]): Promise<void>;
|