@haaaiawd/anws 1.2.5 → 2.0.1

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 (71) hide show
  1. package/README.md +230 -174
  2. package/bin/cli.js +22 -9
  3. package/lib/adapters/index.js +157 -0
  4. package/lib/agents.js +136 -1
  5. package/lib/changelog.js +187 -0
  6. package/lib/copy.js +72 -1
  7. package/lib/diff.js +270 -0
  8. package/lib/init.js +150 -125
  9. package/lib/install-state.js +195 -0
  10. package/lib/manifest.js +184 -42
  11. package/lib/output.js +185 -13
  12. package/lib/prompt.js +284 -0
  13. package/lib/resources/index.js +27 -0
  14. package/lib/update.js +291 -83
  15. package/package.json +10 -6
  16. package/templates/.agents/skills/concept-modeler/SKILL.md +176 -0
  17. package/templates/{.agent → .agents}/skills/design-reviewer/SKILL.md +6 -6
  18. package/templates/.agents/skills/nexus-mapper/SKILL.md +306 -0
  19. package/templates/.agents/skills/nexus-mapper/references/language-customization.md +164 -0
  20. package/templates/.agents/skills/nexus-mapper/references/output-schema.md +298 -0
  21. package/templates/.agents/skills/nexus-mapper/references/probe-protocol.md +246 -0
  22. package/templates/.agents/skills/nexus-mapper/scripts/extract_ast.py +706 -0
  23. package/templates/.agents/skills/nexus-mapper/scripts/git_detective.py +194 -0
  24. package/templates/.agents/skills/nexus-mapper/scripts/languages.json +127 -0
  25. package/templates/.agents/skills/nexus-mapper/scripts/query_graph.py +556 -0
  26. package/templates/.agents/skills/nexus-mapper/scripts/requirements.txt +6 -0
  27. package/templates/{.agent → .agents}/skills/report-template/SKILL.md +11 -14
  28. package/templates/.agents/skills/report-template/references/REPORT_TEMPLATE.md +100 -0
  29. package/templates/{.agent → .agents}/skills/runtime-inspector/SKILL.md +1 -1
  30. package/templates/.agents/skills/sequential-thinking/SKILL.md +179 -0
  31. package/templates/.agents/skills/spec-writer/SKILL.md +108 -0
  32. package/templates/{.agent → .agents}/skills/spec-writer/references/prd_template.md +1 -1
  33. package/templates/{.agent → .agents}/skills/system-architect/SKILL.md +3 -3
  34. package/templates/.agents/skills/system-architect/references/rfc_template.md +59 -0
  35. package/templates/{.agent → .agents}/skills/system-designer/SKILL.md +6 -6
  36. package/templates/{.agent → .agents}/skills/system-designer/references/system-design-template.md +75 -25
  37. package/templates/{.agent → .agents}/skills/task-planner/SKILL.md +1 -1
  38. package/templates/.agents/skills/task-planner/references/TASK_TEMPLATE.md +144 -0
  39. package/templates/{.agent → .agents}/skills/task-reviewer/SKILL.md +4 -3
  40. package/templates/{.agent → .agents}/skills/tech-evaluator/SKILL.md +2 -2
  41. package/templates/{.agent → .agents}/skills/tech-evaluator/references/ADR_TEMPLATE.md +10 -0
  42. package/templates/{.agent → .agents}/workflows/blueprint.md +32 -27
  43. package/templates/{.agent → .agents}/workflows/challenge.md +21 -15
  44. package/templates/{.agent → .agents}/workflows/change.md +23 -14
  45. package/templates/{.agent → .agents}/workflows/craft.md +8 -19
  46. package/templates/{.agent → .agents}/workflows/design-system.md +81 -54
  47. package/templates/{.agent → .agents}/workflows/explore.md +6 -19
  48. package/templates/{.agent → .agents}/workflows/forge.md +30 -32
  49. package/templates/{.agent → .agents}/workflows/genesis.md +68 -56
  50. package/templates/.agents/workflows/probe.md +168 -0
  51. package/templates/{.agent → .agents}/workflows/quickstart.md +7 -12
  52. package/templates/.agents/workflows/upgrade.md +192 -0
  53. package/templates/AGENTS.md +66 -45
  54. package/templates/.agent/skills/build-inspector/SKILL.md +0 -83
  55. package/templates/.agent/skills/complexity-guard/SKILL.md +0 -71
  56. package/templates/.agent/skills/complexity-guard/references/anti_patterns.md +0 -21
  57. package/templates/.agent/skills/concept-modeler/SKILL.md +0 -112
  58. package/templates/.agent/skills/concept-modeler/prompts/GLOSSARY_PROMPT.md +0 -40
  59. package/templates/.agent/skills/concept-modeler/references/ENTITY_EXTRACTION_PROMPT.md +0 -299
  60. package/templates/.agent/skills/concept-modeler/scripts/glossary_gen.py +0 -66
  61. package/templates/.agent/skills/git-forensics/SKILL.md +0 -74
  62. package/templates/.agent/skills/git-forensics/references/ANALYSIS_METHODOLOGY.md +0 -193
  63. package/templates/.agent/skills/git-forensics/scripts/__pycache__/git_forensics.cpython-313.pyc +0 -0
  64. package/templates/.agent/skills/git-forensics/scripts/git_forensics.py +0 -615
  65. package/templates/.agent/skills/git-forensics/scripts/git_hotspots.py +0 -118
  66. package/templates/.agent/skills/report-template/references/REPORT_TEMPLATE.md +0 -100
  67. package/templates/.agent/skills/spec-writer/SKILL.md +0 -108
  68. package/templates/.agent/skills/system-architect/references/rfc_template.md +0 -59
  69. package/templates/.agent/skills/task-planner/references/TASK_TEMPLATE.md +0 -144
  70. package/templates/.agent/workflows/scout.md +0 -139
  71. /package/templates/{.agent → .agents}/skills/system-designer/references/system-design-detail-template.md +0 -0
package/lib/diff.js ADDED
@@ -0,0 +1,270 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { c } = require('./output');
6
+
7
+ async function pathExists(targetPath) {
8
+ return fs.access(targetPath).then(() => true).catch(() => false);
9
+ }
10
+
11
+ async function readTextOrEmpty(filePath) {
12
+ const exists = await pathExists(filePath);
13
+ if (!exists) return '';
14
+ return fs.readFile(filePath, 'utf8');
15
+ }
16
+
17
+ function normalize(text) {
18
+ return text.replace(/\r\n/g, '\n');
19
+ }
20
+
21
+ function toLines(text) {
22
+ const lines = normalize(text).split('\n');
23
+ while (lines.length > 0 && lines[lines.length - 1] === '') {
24
+ lines.pop();
25
+ }
26
+ return lines;
27
+ }
28
+
29
+ function createLineDiff(oldContent, newContent, maxPairs = 8) {
30
+ const before = toLines(oldContent);
31
+ const after = toLines(newContent);
32
+ const pairs = [];
33
+ let beforeIndex = 0;
34
+ let afterIndex = 0;
35
+
36
+ while (beforeIndex < before.length || afterIndex < after.length) {
37
+ const oldLine = before[beforeIndex];
38
+ const newLine = after[afterIndex];
39
+
40
+ if (oldLine === newLine) {
41
+ beforeIndex += 1;
42
+ afterIndex += 1;
43
+ continue;
44
+ }
45
+
46
+ const nextOldLine = before[beforeIndex + 1];
47
+ const nextNewLine = after[afterIndex + 1];
48
+
49
+ if (nextOldLine === newLine) {
50
+ pairs.push({
51
+ oldLineNumber: oldLine === undefined ? null : beforeIndex + 1,
52
+ newLineNumber: null,
53
+ oldText: oldLine === undefined ? '' : oldLine,
54
+ newText: '',
55
+ kind: 'removed'
56
+ });
57
+ beforeIndex += 1;
58
+ if (pairs.length >= maxPairs) break;
59
+ continue;
60
+ }
61
+
62
+ if (oldLine === nextNewLine) {
63
+ pairs.push({
64
+ oldLineNumber: null,
65
+ newLineNumber: newLine === undefined ? null : afterIndex + 1,
66
+ oldText: '',
67
+ newText: newLine === undefined ? '' : newLine,
68
+ kind: 'added'
69
+ });
70
+ afterIndex += 1;
71
+ if (pairs.length >= maxPairs) break;
72
+ continue;
73
+ }
74
+
75
+ pairs.push({
76
+ oldLineNumber: oldLine === undefined ? null : beforeIndex + 1,
77
+ newLineNumber: newLine === undefined ? null : afterIndex + 1,
78
+ oldText: oldLine === undefined ? '' : oldLine,
79
+ newText: newLine === undefined ? '' : newLine,
80
+ kind:
81
+ oldLine === undefined
82
+ ? 'added'
83
+ : newLine === undefined
84
+ ? 'removed'
85
+ : 'modified'
86
+ });
87
+
88
+ beforeIndex += 1;
89
+ afterIndex += 1;
90
+
91
+ if (pairs.length >= maxPairs) break;
92
+ }
93
+
94
+ return pairs;
95
+ }
96
+
97
+ function formatLineNumber(value) {
98
+ return value === null ? '-' : String(value);
99
+ }
100
+
101
+ function toLegacyManagedPath(rel) {
102
+ if (!rel.startsWith('.agents/')) return rel;
103
+ return `.agent/${rel.slice('.agents/'.length)}`;
104
+ }
105
+
106
+ async function resolveExistingManagedPath(cwd, rel) {
107
+ const primaryPath = path.join(cwd, rel);
108
+ if (await pathExists(primaryPath)) {
109
+ return { file: rel, absolutePath: primaryPath, sourceKind: 'current' };
110
+ }
111
+
112
+ const legacyRel = toLegacyManagedPath(rel);
113
+ if (legacyRel !== rel) {
114
+ const legacyPath = path.join(cwd, legacyRel);
115
+ if (await pathExists(legacyPath)) {
116
+ return { file: rel, absolutePath: legacyPath, sourceKind: 'legacy' };
117
+ }
118
+ }
119
+
120
+ return { file: rel, absolutePath: primaryPath, sourceKind: 'missing' };
121
+ }
122
+
123
+ async function collectManagedFileDiffs({
124
+ cwd,
125
+ managedFiles,
126
+ projectionPlan = [],
127
+ projectionEntries = [],
128
+ srcAgents,
129
+ shouldWriteRootAgents,
130
+ agentsUpdatePlan = null
131
+ }) {
132
+ const results = [];
133
+ const normalizedProjectionEntries = projectionEntries.length > 0
134
+ ? projectionEntries
135
+ : projectionPlan.flatMap((item) => item.projectionEntries || []);
136
+ const normalizedManagedFiles = managedFiles && managedFiles.length > 0
137
+ ? managedFiles
138
+ : projectionPlan.flatMap((item) => item.managedFiles || []);
139
+ const projectionMap = new Map(normalizedProjectionEntries.map((item) => [item.outputPath, item]));
140
+
141
+ for (const rel of normalizedManagedFiles) {
142
+ if (rel === 'AGENTS.md' && !shouldWriteRootAgents) {
143
+ continue;
144
+ }
145
+
146
+ const entry = projectionMap.get(rel);
147
+ const srcPath = rel === 'AGENTS.md'
148
+ ? srcAgents
149
+ : path.join(path.join(__dirname, '..', 'templates'), entry.source);
150
+ const existing = rel === 'AGENTS.md'
151
+ ? { file: rel, absolutePath: path.join(cwd, rel), sourceKind: 'current' }
152
+ : await resolveExistingManagedPath(cwd, rel);
153
+ const destPath = existing.absolutePath;
154
+
155
+ const srcExists = await pathExists(srcPath);
156
+ const destExists = await pathExists(destPath);
157
+
158
+ if (!srcExists && !destExists) continue;
159
+
160
+ if (srcExists && !destExists) {
161
+ results.push({
162
+ file: rel,
163
+ type: 'added',
164
+ summary: [],
165
+ oldContent: '',
166
+ newContent: await readTextOrEmpty(srcPath)
167
+ });
168
+ continue;
169
+ }
170
+
171
+ if (!srcExists && destExists) {
172
+ results.push({
173
+ file: rel,
174
+ type: 'deleted',
175
+ summary: [],
176
+ oldContent: await readTextOrEmpty(destPath),
177
+ newContent: ''
178
+ });
179
+ continue;
180
+ }
181
+
182
+ const oldContent = await readTextOrEmpty(destPath);
183
+ let newContent = await readTextOrEmpty(srcPath);
184
+
185
+ if (rel === 'AGENTS.md' && shouldWriteRootAgents && destExists) {
186
+ if (agentsUpdatePlan && agentsUpdatePlan.mode === 'skip') {
187
+ continue;
188
+ }
189
+ newContent = agentsUpdatePlan ? agentsUpdatePlan.content : newContent;
190
+ }
191
+
192
+ if (normalize(oldContent) === normalize(newContent)) continue;
193
+
194
+ results.push({
195
+ file: rel,
196
+ type: 'modified',
197
+ summary: createLineDiff(oldContent, newContent),
198
+ oldContent,
199
+ newContent
200
+ });
201
+ }
202
+
203
+ return results;
204
+ }
205
+
206
+ function groupChanges(changes) {
207
+ return {
208
+ added: changes.filter((item) => item.type === 'added'),
209
+ modified: changes.filter((item) => item.type === 'modified'),
210
+ deleted: changes.filter((item) => item.type === 'deleted')
211
+ };
212
+ }
213
+
214
+ function printColorBlock(title, color, items, prefix) {
215
+ if (items.length === 0) return;
216
+ console.log(` ${color}${title} (${items.length})${c.reset}`);
217
+ for (const item of items) {
218
+ console.log(` ${prefix} ${item.file}`);
219
+ }
220
+ console.log('');
221
+ }
222
+
223
+ function printPreview({ fromVersion, toVersion, changes }) {
224
+ const grouped = groupChanges(changes);
225
+ const fromLabel = fromVersion ? `v${fromVersion}` : 'fresh-install';
226
+ const toLabel = `v${toVersion}`;
227
+
228
+ console.log('╔══════════════════════════════════════════════════════════════╗');
229
+ console.log(`║ ANWS UPDATE PREVIEW - ${fromLabel} → ${toLabel}`.padEnd(63, ' ') + '║');
230
+ console.log('╚══════════════════════════════════════════════════════════════╝');
231
+ console.log('');
232
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
233
+ console.log('📁 文件级变更');
234
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
235
+ console.log('');
236
+
237
+ printColorBlock('新增', c.green, grouped.added, '+');
238
+ printColorBlock('修改', c.yellow, grouped.modified, '~');
239
+ printColorBlock('删除', c.red, grouped.deleted, '-');
240
+
241
+ if (grouped.added.length === 0 && grouped.modified.length === 0 && grouped.deleted.length === 0) {
242
+ console.log(' Already up to date.');
243
+ console.log('');
244
+ return;
245
+ }
246
+
247
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
248
+ console.log('📄 内容级变更详情');
249
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
250
+ console.log('');
251
+
252
+ for (const item of grouped.modified) {
253
+ console.log(`${c.yellow}${item.file}${c.reset}`);
254
+ console.log('──────────────────────────────────────────────────────────────');
255
+ for (const pair of item.summary) {
256
+ console.log(` old ${formatLineNumber(pair.oldLineNumber).padStart(4, ' ')} | ${c.red}-${c.reset} ${pair.oldText}`);
257
+ console.log(` new ${formatLineNumber(pair.newLineNumber).padStart(4, ' ')} | ${c.green}+${c.reset} ${pair.newText}`);
258
+ console.log(' ----');
259
+ }
260
+ console.log('');
261
+ }
262
+
263
+ console.log('执行 `anws update` 以应用以上变更。');
264
+ }
265
+
266
+ module.exports = {
267
+ collectManagedFileDiffs,
268
+ groupChanges,
269
+ printPreview
270
+ };
package/lib/init.js CHANGED
@@ -1,79 +1,123 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs/promises');
4
3
  const path = require('node:path');
5
- const { copyDir } = require('./copy');
6
- const { MANAGED_FILES, USER_PROTECTED_FILES } = require('./manifest');
4
+ const { buildProjectionPlan } = require('./manifest');
5
+ const { getTarget, listTargets } = require('./adapters');
7
6
  const { resolveAgentsInstall, printLegacyMigrationWarning, pathExists } = require('./agents');
8
- const { success, warn, info, fileLine, skippedLine, blank, logo } = require('./output');
7
+ const { ensureChangelogDir } = require('./changelog');
8
+ const { ROOT_AGENTS_FILE, resolveCanonicalSource } = require('./resources');
9
+ const { writeTargetFiles } = require('./copy');
10
+ const { createInstallLock, dedupeTargets, detectInstallState, summarizeTargetState, writeInstallLock } = require('./install-state');
11
+ const { selectMultiple, confirm } = require('./prompt');
12
+ const { success, warn, info, fileLine, skippedLine, blank, logo, section } = require('./output');
9
13
 
10
- /**
11
- * anws init — 将工作流系统写入当前项目
12
- */
13
14
  async function init() {
14
15
  const cwd = process.cwd();
15
- const srcRoot = path.join(__dirname, '..', 'templates', '.agent');
16
- const destRoot = path.join(cwd, '.agent');
17
- const srcAgents = path.join(__dirname, '..', 'templates', 'AGENTS.md');
18
-
19
- const agentsDecision = await resolveAgentsInstall({
20
- cwd,
21
- askMigrate,
22
- forceYes: !!global.__ANWS_FORCE_YES
23
- });
16
+ logo();
17
+ const targets = await selectTargets();
18
+ const targetIds = Array.from(new Set(targets.map((item) => item.id)));
19
+ const targetPlans = buildProjectionPlan(targetIds);
20
+ const installState = await detectInstallState(cwd);
21
+ const srcAgents = ROOT_AGENTS_FILE;
22
+ const cliVersion = require(path.join(__dirname, '..', 'package.json')).version;
23
+
24
+ info('Initializing Anws...');
25
+ info(`Target IDEs: ${targets.map((item) => item.label).join(', ')}`);
26
+ if (installState.needsFallback) {
27
+ info('Install lock missing or unreadable. Falling back to scanned target state.');
28
+ }
29
+ if (installState.drift.hasDrift) {
30
+ warn(`Detected install-state drift. Missing on disk: ${installState.drift.missingOnDisk.join(', ') || 'none'}; untracked on disk: ${installState.drift.untrackedOnDisk.join(', ') || 'none'}.`);
31
+ }
32
+ blank();
24
33
 
25
- // ── 冲突检测(T1.2.3 在此处插入冲突分支)──────────────────────────────────
26
- const conflicting = await findConflicts(cwd);
27
- if (conflicting.length > 0) {
28
- const confirmed = await askOverwrite(conflicting.length);
29
- if (!confirmed) {
30
- blank();
31
- info('Aborted. No files were changed.');
32
- process.exit(0);
34
+ const written = [];
35
+ const skipped = [];
36
+ const successfulTargets = [];
37
+ const failedTargets = [];
38
+ const sessionWrittenFiles = new Set();
39
+
40
+ for (const targetPlan of targetPlans) {
41
+ const target = getTarget(targetPlan.targetId);
42
+ const agentsDecision = target.id === 'antigravity'
43
+ ? await resolveAgentsInstall({
44
+ cwd,
45
+ askMigrate,
46
+ forceYes: !!global.__ANWS_FORCE_YES
47
+ })
48
+ : {
49
+ shouldWriteRootAgents: true,
50
+ shouldWarnMigration: false
51
+ };
52
+
53
+ const conflicting = await findConflicts(cwd, targetPlan.managedFiles, sessionWrittenFiles);
54
+ if (conflicting.length > 0) {
55
+ const confirmed = await askOverwrite(conflicting.length, target.label);
56
+ if (!confirmed) {
57
+ skipped.push(...targetPlan.managedFiles);
58
+ failedTargets.push({
59
+ targetId: target.id,
60
+ targetLabel: target.label,
61
+ reason: `Skipped ${conflicting.length} conflicting managed file(s)`
62
+ });
63
+ continue;
64
+ }
33
65
  }
34
- // 仅覆盖托管文件(用户自有文件不受影响)
35
- const { written: updated, skipped } = await overwriteManaged(srcRoot, cwd, {
66
+
67
+ const result = await writeTargetFiles(cwd, {
68
+ targetPlan,
69
+ protectedFiles: targetPlan.userProtectedFiles,
36
70
  srcAgents,
37
- shouldWriteRootAgents: agentsDecision.shouldWriteRootAgents
71
+ shouldWriteRootAgents: agentsDecision.shouldWriteRootAgents,
72
+ resolveCanonicalSource
38
73
  });
74
+
75
+ written.push(...result.written);
76
+ for (const rel of result.written) {
77
+ sessionWrittenFiles.add(rel);
78
+ }
79
+ skipped.push(...result.skipped);
80
+ successfulTargets.push(summarizeTargetState(targetPlan, cliVersion));
81
+
39
82
  if (agentsDecision.shouldWarnMigration) {
40
83
  printLegacyMigrationWarning();
41
84
  }
42
- printSummary(updated, skipped, 'updated');
43
- return;
44
85
  }
45
- // ── 无冲突:直接复制 ─────────────────────────────────────────────────────────
46
-
47
- logo();
48
- info('Initializing Antigravity Workflow System...');
49
- blank();
50
86
 
51
- const writtenFiles = await copyDir(srcRoot, destRoot);
52
- const written = Array.isArray(writtenFiles) ? writtenFiles : [];
53
-
54
- if (agentsDecision.shouldWriteRootAgents) {
55
- const destAgents = path.join(cwd, 'AGENTS.md');
56
- try {
57
- await fs.copyFile(srcAgents, destAgents);
58
- written.push(destAgents);
59
- } catch (e) {
60
- // 忽略
87
+ await ensureChangelogDir(cwd);
88
+ const existingTargets = installState.lockResult.lock?.targets || [];
89
+ const generatedAt = new Date().toISOString();
90
+ await writeInstallLock(cwd, createInstallLock({
91
+ cliVersion,
92
+ generatedAt,
93
+ targets: dedupeTargets([
94
+ ...existingTargets,
95
+ ...successfulTargets
96
+ ]),
97
+ lastUpdateSummary: {
98
+ successfulTargets: successfulTargets.map((item) => item.targetId),
99
+ failedTargets: failedTargets.map((item) => item.targetId),
100
+ updatedAt: generatedAt
61
101
  }
62
- }
102
+ }));
63
103
 
64
- if (agentsDecision.shouldWarnMigration) {
65
- printLegacyMigrationWarning();
104
+ printTargetSummary(successfulTargets, failedTargets);
105
+
106
+ for (const rel of written) {
107
+ fileLine(rel.replace(/\\/g, '/'));
66
108
  }
67
109
 
68
- // 打印文件列表
69
- for (const absPath of written) {
70
- const rel = path.relative(cwd, absPath).replace(/\\/g, '/');
71
- fileLine(rel);
110
+ if (skipped.length > 0) {
111
+ blank();
112
+ info('Skipped (project-specific, preserved):');
113
+ for (const rel of skipped) {
114
+ skippedLine(rel.replace(/\\/g, '/'));
115
+ }
72
116
  }
73
117
 
74
118
  blank();
75
- success(`Done! ${written.length} files written to .agent/`);
76
- printNextSteps();
119
+ success(`Done! ${written.length} files written for ${successfulTargets.map((item) => item.targetLabel).join(', ') || 'selected targets'}.`);
120
+ printNextSteps(targets);
77
121
  }
78
122
 
79
123
  // ─── 辅助函数 ──────────────────────────────────────────────────────────────────
@@ -82,9 +126,12 @@ async function init() {
82
126
  * 找出 cwd 中已存在的托管文件列表。
83
127
  * @returns {Promise<string[]>} 已存在的托管文件相对路径数组
84
128
  */
85
- async function findConflicts(cwd) {
129
+ async function findConflicts(cwd, managedFiles, sessionWrittenFiles = new Set()) {
86
130
  const conflicts = [];
87
- for (const rel of MANAGED_FILES) {
131
+ for (const rel of managedFiles) {
132
+ if (sessionWrittenFiles.has(rel)) {
133
+ continue;
134
+ }
88
135
  const abs = path.join(cwd, rel);
89
136
  const exists = await pathExists(abs);
90
137
  if (exists) conflicts.push(rel);
@@ -97,26 +144,19 @@ async function findConflicts(cwd) {
97
144
  * 非 TTY 环境(如 CI)自动返回 false。
98
145
  * @returns {Promise<boolean>}
99
146
  */
100
- async function askOverwrite(count) {
147
+ async function askOverwrite(count, label) {
101
148
  if (global.__ANWS_FORCE_YES) return true;
102
149
 
103
- // 非 TTY 环境:默认不覆盖,防止 CI 挂起
104
150
  if (!process.stdin.isTTY) {
105
151
  warn(`${count} managed file(s) already exist. Non-TTY: skipping overwrite.`);
106
152
  return false;
107
153
  }
108
154
 
109
- const readline = require('node:readline');
110
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
111
-
112
- return new Promise((resolve) => {
113
- rl.question(
114
- `\n\u26a0 ${count} managed file(s) already exist. Overwrite? [y/N] `,
115
- (answer) => {
116
- rl.close();
117
- resolve(answer.trim().toLowerCase() === 'y');
118
- }
119
- );
155
+ return confirm({
156
+ message: `Overwrite ${count} managed file(s) for ${label}?`,
157
+ confirmLabel: 'Overwrite',
158
+ cancelLabel: 'Skip',
159
+ defaultValue: false
120
160
  });
121
161
  }
122
162
 
@@ -127,62 +167,14 @@ async function askMigrate() {
127
167
  return false;
128
168
  }
129
169
 
130
- const readline = require('node:readline');
131
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
132
-
133
- return new Promise((resolve) => {
134
- rl.question(
135
- '\n\u26a0 Legacy .agent/rules/agents.md detected. Do you want to migrate to root AGENTS.md? [y/N] ',
136
- (answer) => {
137
- rl.close();
138
- resolve(answer.trim().toLowerCase() === 'y');
139
- }
140
- );
170
+ return confirm({
171
+ message: 'Legacy .agent/ directory detected. Migrate to .agents/?',
172
+ confirmLabel: 'Migrate',
173
+ cancelLabel: 'Keep legacy',
174
+ defaultValue: false
141
175
  });
142
176
  }
143
177
 
144
- /**
145
- * 仅覆盖 MANAGED_FILES 清单内的文件,用户自有文件不受影响。
146
- * USER_PROTECTED_FILES 中的文件即便冲突也跳过,保留用户修改。
147
- * @returns {{ written: string[], skipped: string[] }}
148
- */
149
- async function overwriteManaged(srcRoot, cwd, options = {}) {
150
- const srcBase = path.dirname(srcRoot); // templates/
151
- const written = [];
152
- const skipped = [];
153
- const shouldWriteRootAgents = options.shouldWriteRootAgents !== false;
154
- const srcAgents = options.srcAgents || path.join(srcBase, 'AGENTS.md');
155
-
156
- for (const rel of MANAGED_FILES) {
157
- if (rel === 'AGENTS.md' && !shouldWriteRootAgents) {
158
- skipped.push(rel);
159
- continue;
160
- }
161
-
162
- // 受保护文件:文件已存在时跳过,交给用户自行维护
163
- if (USER_PROTECTED_FILES.includes(rel)) {
164
- const destPath = path.join(cwd, rel);
165
- const exists = await pathExists(destPath);
166
- if (exists) {
167
- skipped.push(rel);
168
- continue;
169
- }
170
- }
171
-
172
- const srcPath = rel === 'AGENTS.md' ? srcAgents : path.join(srcBase, rel);
173
- const destPath = path.join(cwd, rel);
174
-
175
- await fs.mkdir(path.dirname(destPath), { recursive: true });
176
- const srcExists = await pathExists(srcPath);
177
- if (srcExists) {
178
- await fs.copyFile(srcPath, destPath);
179
- written.push(rel);
180
- }
181
- }
182
-
183
- return { written, skipped };
184
- }
185
-
186
178
  /**
187
179
  * 打印操作摘要(更新场景)。
188
180
  * @param {string[]} files 已写入的文件
@@ -211,11 +203,44 @@ function printSummary(files, skipped = [], action) {
211
203
  }
212
204
  }
213
205
 
214
- function printNextSteps() {
206
+ async function selectTargets() {
207
+ if (global.__ANWS_TARGET_IDS && global.__ANWS_TARGET_IDS.length > 0) {
208
+ return global.__ANWS_TARGET_IDS.map((targetId) => getTarget(targetId));
209
+ }
210
+
211
+ if (!process.stdin.isTTY) {
212
+ return [getTarget('antigravity')];
213
+ }
214
+
215
+ const targets = listTargets();
216
+
217
+ return selectMultiple({
218
+ message: 'Choose your target AI IDEs:',
219
+ options: targets.map((target) => ({ label: target.label, value: target.id })),
220
+ initialSelectedIndexes: [1]
221
+ }).then((selectedOptions) => selectedOptions.map((option) => getTarget(option.value)));
222
+ }
223
+
224
+ function printNextSteps(targets) {
215
225
  blank();
216
- info('Next steps:');
217
- info(' 1. Read AGENTS.md to understand the system');
218
- info(' 2. Run /quickstart in your AI assistant to analyze and start the workflow');
226
+ section('Next steps', targets.some((target) => target.rootAgentFile)
227
+ ? [
228
+ '1. Read AGENTS.md to understand the system',
229
+ '2. Run /quickstart in your AI assistant to analyze and start the workflow'
230
+ ]
231
+ : [
232
+ '1. Review files written under the selected target directories',
233
+ '2. Run /quickstart in your AI assistant to analyze and start the workflow'
234
+ ]);
235
+ }
236
+
237
+ function printTargetSummary(successfulTargets, failedTargets) {
238
+ blank();
239
+ section('Target summary', [
240
+ ...successfulTargets.map((target) => `✔ ${target.targetLabel} (${target.targetId})`),
241
+ ...failedTargets.map((target) => `✖ ${target.targetLabel} (${target.targetId}) — ${target.reason}`)
242
+ ]);
219
243
  }
220
244
 
221
245
  module.exports = init;
246
+