@haaaiawd/anws 1.2.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +208 -173
  2. package/bin/cli.js +22 -9
  3. package/lib/adapters/index.js +157 -0
  4. package/lib/agents.js +185 -0
  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 +238 -174
  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 +355 -149
  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 +166 -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 +38 -33
  43. package/templates/{.agent → .agents}/workflows/challenge.md +21 -15
  44. package/templates/{.agent → .agents}/workflows/change.md +24 -15
  45. package/templates/{.agent → .agents}/workflows/craft.md +9 -20
  46. package/templates/{.agent → .agents}/workflows/design-system.md +83 -56
  47. package/templates/{.agent → .agents}/workflows/explore.md +6 -19
  48. package/templates/{.agent → .agents}/workflows/forge.md +36 -38
  49. package/templates/{.agent → .agents}/workflows/genesis.md +76 -64
  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 +134 -113
  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,175 +1,239 @@
1
- 'use strict';
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { buildProjectionPlan } = require('./manifest');
5
+ const { getTarget, listTargets } = require('./adapters');
6
+ const { resolveAgentsInstall, printLegacyMigrationWarning, pathExists } = require('./agents');
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');
13
+
14
+ async function init() {
15
+ const cwd = process.cwd();
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();
33
+
34
+ const written = [];
35
+ const skipped = [];
36
+ const successfulTargets = [];
37
+ const failedTargets = [];
38
+
39
+ for (const targetPlan of targetPlans) {
40
+ const target = getTarget(targetPlan.targetId);
41
+ const agentsDecision = target.id === 'antigravity'
42
+ ? await resolveAgentsInstall({
43
+ cwd,
44
+ askMigrate,
45
+ forceYes: !!global.__ANWS_FORCE_YES
46
+ })
47
+ : {
48
+ shouldWriteRootAgents: false,
49
+ shouldWarnMigration: false
50
+ };
51
+
52
+ const conflicting = await findConflicts(cwd, targetPlan.managedFiles);
53
+ if (conflicting.length > 0) {
54
+ const confirmed = await askOverwrite(conflicting.length, target.label);
55
+ if (!confirmed) {
56
+ skipped.push(...targetPlan.managedFiles);
57
+ failedTargets.push({
58
+ targetId: target.id,
59
+ targetLabel: target.label,
60
+ reason: `Skipped ${conflicting.length} conflicting managed file(s)`
61
+ });
62
+ continue;
63
+ }
64
+ }
65
+
66
+ const result = await writeTargetFiles(cwd, {
67
+ targetPlan,
68
+ protectedFiles: targetPlan.userProtectedFiles,
69
+ srcAgents,
70
+ shouldWriteRootAgents: agentsDecision.shouldWriteRootAgents,
71
+ resolveCanonicalSource
72
+ });
73
+
74
+ written.push(...result.written);
75
+ skipped.push(...result.skipped);
76
+ successfulTargets.push(summarizeTargetState(targetPlan, cliVersion));
77
+
78
+ if (agentsDecision.shouldWarnMigration) {
79
+ printLegacyMigrationWarning();
80
+ }
81
+ }
82
+
83
+ await ensureChangelogDir(cwd);
84
+ const existingTargets = installState.lockResult.lock?.targets || [];
85
+ const generatedAt = new Date().toISOString();
86
+ await writeInstallLock(cwd, createInstallLock({
87
+ cliVersion,
88
+ generatedAt,
89
+ targets: dedupeTargets([
90
+ ...existingTargets,
91
+ ...successfulTargets
92
+ ]),
93
+ lastUpdateSummary: {
94
+ successfulTargets: successfulTargets.map((item) => item.targetId),
95
+ failedTargets: failedTargets.map((item) => item.targetId),
96
+ updatedAt: generatedAt
97
+ }
98
+ }));
99
+
100
+ printTargetSummary(successfulTargets, failedTargets);
101
+
102
+ for (const rel of written) {
103
+ fileLine(rel.replace(/\\/g, '/'));
104
+ }
105
+
106
+ if (skipped.length > 0) {
107
+ blank();
108
+ info('Skipped (project-specific, preserved):');
109
+ for (const rel of skipped) {
110
+ skippedLine(rel.replace(/\\/g, '/'));
111
+ }
112
+ }
113
+
114
+ blank();
115
+ success(`Done! ${written.length} files written for ${successfulTargets.map((item) => item.targetLabel).join(', ') || 'selected targets'}.`);
116
+ printNextSteps(targets);
117
+ }
118
+
119
+ // ─── 辅助函数 ──────────────────────────────────────────────────────────────────
120
+
121
+ /**
122
+ * 找出 cwd 中已存在的托管文件列表。
123
+ * @returns {Promise<string[]>} 已存在的托管文件相对路径数组
124
+ */
125
+ async function findConflicts(cwd, managedFiles) {
126
+ const conflicts = [];
127
+ for (const rel of managedFiles) {
128
+ const abs = path.join(cwd, rel);
129
+ const exists = await pathExists(abs);
130
+ if (exists) conflicts.push(rel);
131
+ }
132
+ return conflicts;
133
+ }
134
+
135
+ /**
136
+ * 交互式询问用户是否覆盖(默认 N)。
137
+ * 非 TTY 环境(如 CI)自动返回 false。
138
+ * @returns {Promise<boolean>}
139
+ */
140
+ async function askOverwrite(count, label) {
141
+ if (global.__ANWS_FORCE_YES) return true;
142
+
143
+ if (!process.stdin.isTTY) {
144
+ warn(`${count} managed file(s) already exist. Non-TTY: skipping overwrite.`);
145
+ return false;
146
+ }
147
+
148
+ return confirm({
149
+ message: `Overwrite ${count} managed file(s) for ${label}?`,
150
+ confirmLabel: 'Overwrite',
151
+ cancelLabel: 'Skip',
152
+ defaultValue: false
153
+ });
154
+ }
155
+
156
+ async function askMigrate() {
157
+ if (global.__ANWS_FORCE_YES) return true;
158
+
159
+ if (!process.stdin.isTTY) {
160
+ return false;
161
+ }
162
+
163
+ return confirm({
164
+ message: 'Legacy .agent/ directory detected. Migrate to .agents/?',
165
+ confirmLabel: 'Migrate',
166
+ cancelLabel: 'Keep legacy',
167
+ defaultValue: false
168
+ });
169
+ }
170
+
171
+ /**
172
+ * 打印操作摘要(更新场景)。
173
+ * @param {string[]} files 已写入的文件
174
+ * @param {string[]} skipped 受保护跳过的文件
175
+ * @param {string} action
176
+ */
177
+ function printSummary(files, skipped = [], action) {
178
+ const verb = action === 'updated' ? 'Updating' : 'Writing';
179
+ blank();
180
+ info(`${verb} files...`);
181
+ blank();
182
+ for (const rel of files) {
183
+ fileLine(rel.replace(/\\/g, '/'));
184
+ }
185
+ if (skipped.length > 0) {
186
+ blank();
187
+ info('Skipped (project-specific, preserved):');
188
+ for (const rel of skipped) {
189
+ skippedLine(rel.replace(/\\/g, '/'));
190
+ }
191
+ }
192
+ blank();
193
+ success(`Done! ${files.length} file(s) ${action}${skipped.length > 0 ? `, ${skipped.length} skipped` : ''}.`);
194
+ if (action === 'updated') {
195
+ info('Managed files have been updated to the latest version.');
196
+ }
197
+ }
198
+
199
+ async function selectTargets() {
200
+ if (global.__ANWS_TARGET_IDS && global.__ANWS_TARGET_IDS.length > 0) {
201
+ return global.__ANWS_TARGET_IDS.map((targetId) => getTarget(targetId));
202
+ }
203
+
204
+ if (!process.stdin.isTTY) {
205
+ return [getTarget('antigravity')];
206
+ }
207
+
208
+ const targets = listTargets();
209
+
210
+ return selectMultiple({
211
+ message: 'Choose your target AI IDEs:',
212
+ options: targets.map((target) => ({ label: target.label, value: target.id })),
213
+ initialSelectedIndexes: [1]
214
+ }).then((selectedOptions) => selectedOptions.map((option) => getTarget(option.value)));
215
+ }
216
+
217
+ function printNextSteps(targets) {
218
+ blank();
219
+ section('Next steps', targets.some((target) => target.rootAgentFile)
220
+ ? [
221
+ '1. Read AGENTS.md to understand the system',
222
+ '2. Run /quickstart in your AI assistant to analyze and start the workflow'
223
+ ]
224
+ : [
225
+ '1. Review files written under the selected target directories',
226
+ '2. Run /quickstart in your AI assistant to analyze and start the workflow'
227
+ ]);
228
+ }
229
+
230
+ function printTargetSummary(successfulTargets, failedTargets) {
231
+ blank();
232
+ section('Target summary', [
233
+ ...successfulTargets.map((target) => `✔ ${target.targetLabel} (${target.targetId})`),
234
+ ...failedTargets.map((target) => `✖ ${target.targetLabel} (${target.targetId}) — ${target.reason}`)
235
+ ]);
236
+ }
237
+
238
+ module.exports = init;
2
239
 
3
- const fs = require('node:fs/promises');
4
- const path = require('node:path');
5
- const { copyDir } = require('./copy');
6
- const { MANAGED_FILES, USER_PROTECTED_FILES } = require('./manifest');
7
- const { success, warn, info, fileLine, skippedLine, blank, logo } = require('./output');
8
-
9
- /**
10
- * anws init — 将工作流系统写入当前项目
11
- */
12
- async function init() {
13
- const cwd = process.cwd();
14
- const srcRoot = path.join(__dirname, '..', 'templates', '.agent');
15
- const destRoot = path.join(cwd, '.agent');
16
-
17
- // ── 冲突检测(T1.2.3 在此处插入冲突分支)──────────────────────────────────
18
- const conflicting = await findConflicts(cwd);
19
- if (conflicting.length > 0) {
20
- const confirmed = await askOverwrite(conflicting.length);
21
- if (!confirmed) {
22
- blank();
23
- info('Aborted. No files were changed.');
24
- process.exit(0);
25
- }
26
- // 仅覆盖托管文件(用户自有文件不受影响)
27
- const { written: updated, skipped } = await overwriteManaged(srcRoot, cwd);
28
- printSummary(updated, skipped, 'updated');
29
- return;
30
- }
31
- // ── 无冲突:直接复制 ─────────────────────────────────────────────────────────
32
-
33
- logo();
34
- info('Initializing Antigravity Workflow System...');
35
- blank();
36
-
37
- const writtenFiles = await copyDir(srcRoot, destRoot);
38
- const written = Array.isArray(writtenFiles) ? writtenFiles : [];
39
-
40
- // 把外层的 AGENTS.md 拷贝出来
41
- const srcAgents = path.join(__dirname, '..', 'templates', 'AGENTS.md');
42
- const destAgents = path.join(cwd, 'AGENTS.md');
43
- try {
44
- await fs.copyFile(srcAgents, destAgents);
45
- written.push(destAgents);
46
- } catch (e) {
47
- // 忽略
48
- }
49
-
50
- // 打印文件列表
51
- for (const absPath of written) {
52
- const rel = path.relative(cwd, absPath).replace(/\\/g, '/');
53
- fileLine(rel);
54
- }
55
-
56
- blank();
57
- success(`Done! ${written.length} files written to .agent/`);
58
- printNextSteps();
59
- }
60
-
61
- // ─── 辅助函数 ──────────────────────────────────────────────────────────────────
62
-
63
- /**
64
- * 找出 cwd 中已存在的托管文件列表。
65
- * @returns {Promise<string[]>} 已存在的托管文件相对路径数组
66
- */
67
- async function findConflicts(cwd) {
68
- const conflicts = [];
69
- for (const rel of MANAGED_FILES) {
70
- const abs = path.join(cwd, rel);
71
- const exists = await fs.access(abs).then(() => true).catch(() => false);
72
- if (exists) conflicts.push(rel);
73
- }
74
- return conflicts;
75
- }
76
-
77
- /**
78
- * 交互式询问用户是否覆盖(默认 N)。
79
- * 非 TTY 环境(如 CI)自动返回 false。
80
- * @returns {Promise<boolean>}
81
- */
82
- async function askOverwrite(count) {
83
- if (global.__ANWS_FORCE_YES) return true;
84
-
85
- // 非 TTY 环境:默认不覆盖,防止 CI 挂起
86
- if (!process.stdin.isTTY) {
87
- warn(`${count} managed file(s) already exist. Non-TTY: skipping overwrite.`);
88
- return false;
89
- }
90
-
91
- const readline = require('node:readline');
92
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
93
-
94
- return new Promise((resolve) => {
95
- rl.question(
96
- `\n\u26a0 ${count} managed file(s) already exist. Overwrite? [y/N] `,
97
- (answer) => {
98
- rl.close();
99
- resolve(answer.trim().toLowerCase() === 'y');
100
- }
101
- );
102
- });
103
- }
104
-
105
- /**
106
- * 仅覆盖 MANAGED_FILES 清单内的文件,用户自有文件不受影响。
107
- * USER_PROTECTED_FILES 中的文件即便冲突也跳过,保留用户修改。
108
- * @returns {{ written: string[], skipped: string[] }}
109
- */
110
- async function overwriteManaged(srcRoot, cwd) {
111
- const srcBase = path.dirname(srcRoot); // templates/
112
- const written = [];
113
- const skipped = [];
114
-
115
- for (const rel of MANAGED_FILES) {
116
- // 受保护文件:文件已存在时跳过,交给用户自行维护
117
- if (USER_PROTECTED_FILES.includes(rel)) {
118
- const destPath = path.join(cwd, rel);
119
- const exists = await fs.access(destPath).then(() => true).catch(() => false);
120
- if (exists) {
121
- skipped.push(rel);
122
- continue;
123
- }
124
- }
125
-
126
- const srcPath = path.join(srcBase, rel);
127
- const destPath = path.join(cwd, rel);
128
-
129
- await fs.mkdir(path.dirname(destPath), { recursive: true });
130
- const srcExists = await fs.access(srcPath).then(() => true).catch(() => false);
131
- if (srcExists) {
132
- await fs.copyFile(srcPath, destPath);
133
- written.push(rel);
134
- }
135
- }
136
-
137
- return { written, skipped };
138
- }
139
-
140
- /**
141
- * 打印操作摘要(更新场景)。
142
- * @param {string[]} files 已写入的文件
143
- * @param {string[]} skipped 受保护跳过的文件
144
- * @param {string} action
145
- */
146
- function printSummary(files, skipped = [], action) {
147
- const verb = action === 'updated' ? 'Updating' : 'Writing';
148
- blank();
149
- info(`${verb} files...`);
150
- blank();
151
- for (const rel of files) {
152
- fileLine(rel.replace(/\\/g, '/'));
153
- }
154
- if (skipped.length > 0) {
155
- blank();
156
- info('Skipped (project-specific, preserved):');
157
- for (const rel of skipped) {
158
- skippedLine(rel.replace(/\\/g, '/'));
159
- }
160
- }
161
- blank();
162
- success(`Done! ${files.length} file(s) ${action}${skipped.length > 0 ? `, ${skipped.length} skipped` : ''}.`);
163
- if (action === 'updated') {
164
- info('Managed files have been updated to the latest version.');
165
- }
166
- }
167
-
168
- function printNextSteps() {
169
- blank();
170
- info('Next steps:');
171
- info(' 1. Read AGENTS.md to understand the system');
172
- info(' 2. Run /quickstart in your AI assistant to analyze and start the workflow');
173
- }
174
-
175
- module.exports = init;