@haaaiawd/anws 2.2.6 → 2.4.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 (94) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +52 -22
  3. package/lib/diff.js +5 -2
  4. package/lib/init.js +217 -96
  5. package/lib/install-state.js +18 -3
  6. package/lib/manifest.js +510 -213
  7. package/lib/prompt.js +68 -0
  8. package/lib/resources/index.js +36 -2
  9. package/lib/update.js +12 -6
  10. package/package.json +48 -47
  11. package/templates/.agents/skills/anws-system/SKILL.md +108 -108
  12. package/templates/.agents/skills/code-reviewer/SKILL.md +170 -103
  13. package/templates/.agents/skills/concept-modeler/SKILL.md +230 -179
  14. package/templates/.agents/skills/craft-authoring/SKILL.md +112 -49
  15. package/templates/.agents/skills/craft-authoring/references/BUNDLE_POLICY.md +61 -0
  16. package/templates/.agents/skills/craft-authoring/references/PROMPT_QUALITY_RUBRIC.md +99 -0
  17. package/templates/.agents/skills/craft-authoring/references/SCORECARD_TEMPLATE.md +64 -0
  18. package/templates/.agents/skills/design-reviewer/SKILL.md +265 -190
  19. package/templates/.agents/skills/e2e-testing-guide/SKILL.md +246 -135
  20. package/templates/.agents/skills/nexus-mapper/SKILL.md +321 -321
  21. package/templates/.agents/skills/output-contract/SKILL.md +37 -0
  22. package/templates/.agents/skills/report-template/SKILL.md +92 -92
  23. package/templates/.agents/skills/sequential-thinking/SKILL.md +222 -225
  24. package/templates/.agents/skills/spec-writer/SKILL.md +75 -30
  25. package/templates/.agents/skills/system-architect/SKILL.md +538 -678
  26. package/templates/.agents/skills/system-designer/SKILL.md +601 -601
  27. package/templates/.agents/skills/task-planner/SKILL.md +1 -2
  28. package/templates/.agents/skills/task-reviewer/SKILL.md +428 -388
  29. package/templates/.agents/skills/tech-evaluator/SKILL.md +252 -144
  30. package/templates/.agents/workflows/blueprint.md +166 -43
  31. package/templates/.agents/workflows/challenge.md +331 -497
  32. package/templates/.agents/workflows/change.md +182 -339
  33. package/templates/.agents/workflows/craft.md +159 -236
  34. package/templates/.agents/workflows/design-system.md +202 -674
  35. package/templates/.agents/workflows/explore.md +187 -399
  36. package/templates/.agents/workflows/forge.md +650 -550
  37. package/templates/.agents/workflows/genesis.md +439 -351
  38. package/templates/.agents/workflows/probe.md +219 -241
  39. package/templates/.agents/workflows/quickstart.md +302 -123
  40. package/templates/.agents/workflows/upgrade.md +145 -182
  41. package/templates_en/.agents/skills/anws-system/SKILL.md +108 -0
  42. package/templates_en/.agents/skills/code-reviewer/SKILL.md +170 -0
  43. package/templates_en/.agents/skills/concept-modeler/SKILL.md +230 -0
  44. package/templates_en/.agents/skills/craft-authoring/SKILL.md +179 -0
  45. package/templates_en/.agents/skills/craft-authoring/references/BUNDLE_POLICY.md +60 -0
  46. package/templates_en/.agents/skills/craft-authoring/references/PROMPT_QUALITY_RUBRIC.md +92 -0
  47. package/templates_en/.agents/skills/craft-authoring/references/SCORECARD_TEMPLATE.md +52 -0
  48. package/templates_en/.agents/skills/design-reviewer/SKILL.md +265 -0
  49. package/templates_en/.agents/skills/e2e-testing-guide/SKILL.md +246 -0
  50. package/templates_en/.agents/skills/nexus-mapper/SKILL.md +306 -0
  51. package/templates_en/.agents/skills/nexus-mapper/references/language-customization.md +167 -0
  52. package/templates_en/.agents/skills/nexus-mapper/references/output-schema.md +311 -0
  53. package/templates_en/.agents/skills/nexus-mapper/references/probe-protocol.md +246 -0
  54. package/templates_en/.agents/skills/nexus-mapper/scripts/extract_ast.py +706 -0
  55. package/templates_en/.agents/skills/nexus-mapper/scripts/git_detective.py +194 -0
  56. package/templates_en/.agents/skills/nexus-mapper/scripts/languages.json +127 -0
  57. package/templates_en/.agents/skills/nexus-mapper/scripts/query_graph.py +556 -0
  58. package/templates_en/.agents/skills/nexus-mapper/scripts/requirements.txt +6 -0
  59. package/templates_en/.agents/skills/nexus-query/SKILL.md +114 -0
  60. package/templates_en/.agents/skills/nexus-query/scripts/extract_ast.py +706 -0
  61. package/templates_en/.agents/skills/nexus-query/scripts/git_detective.py +194 -0
  62. package/templates_en/.agents/skills/nexus-query/scripts/languages.json +127 -0
  63. package/templates_en/.agents/skills/nexus-query/scripts/query_graph.py +556 -0
  64. package/templates_en/.agents/skills/nexus-query/scripts/requirements.txt +6 -0
  65. package/templates_en/.agents/skills/output-contract/SKILL.md +37 -0
  66. package/templates_en/.agents/skills/report-template/SKILL.md +85 -0
  67. package/templates_en/.agents/skills/report-template/references/REPORT_TEMPLATE.md +100 -0
  68. package/templates_en/.agents/skills/runtime-inspector/SKILL.md +101 -0
  69. package/templates_en/.agents/skills/sequential-thinking/SKILL.md +214 -0
  70. package/templates_en/.agents/skills/spec-writer/SKILL.md +153 -0
  71. package/templates_en/.agents/skills/spec-writer/references/prd_template.md +177 -0
  72. package/templates_en/.agents/skills/system-architect/SKILL.md +538 -0
  73. package/templates_en/.agents/skills/system-architect/references/rfc_template.md +59 -0
  74. package/templates_en/.agents/skills/system-designer/SKILL.md +534 -0
  75. package/templates_en/.agents/skills/system-designer/references/system-design-detail-template.md +187 -0
  76. package/templates_en/.agents/skills/system-designer/references/system-design-template.md +605 -0
  77. package/templates_en/.agents/skills/task-planner/SKILL.md +251 -0
  78. package/templates_en/.agents/skills/task-planner/references/TASK_TEMPLATE_05A.md +109 -0
  79. package/templates_en/.agents/skills/task-planner/references/TASK_TEMPLATE_05B.md +176 -0
  80. package/templates_en/.agents/skills/task-reviewer/SKILL.md +428 -0
  81. package/templates_en/.agents/skills/tech-evaluator/SKILL.md +252 -0
  82. package/templates_en/.agents/skills/tech-evaluator/references/ADR_TEMPLATE.md +78 -0
  83. package/templates_en/.agents/workflows/blueprint.md +200 -0
  84. package/templates_en/.agents/workflows/challenge.md +331 -0
  85. package/templates_en/.agents/workflows/change.md +182 -0
  86. package/templates_en/.agents/workflows/craft.md +159 -0
  87. package/templates_en/.agents/workflows/design-system.md +202 -0
  88. package/templates_en/.agents/workflows/explore.md +187 -0
  89. package/templates_en/.agents/workflows/forge.md +651 -0
  90. package/templates_en/.agents/workflows/genesis.md +439 -0
  91. package/templates_en/.agents/workflows/probe.md +219 -0
  92. package/templates_en/.agents/workflows/quickstart.md +303 -0
  93. package/templates_en/.agents/workflows/upgrade.md +145 -0
  94. package/templates_en/AGENTS.md +149 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <img src="assets/logo-cli.png" width="260" alt="Anws">
5
5
 
6
6
  [![License: MIT](https://opensource.org/licenses/MIT)](https://opensource.org/licenses/MIT)
7
- [![Version](https://img.shields.io/badge/version-v2.2.5-7FB5B6)](https://github.com/Haaaiawd/ANWS/releases)
7
+ [![Version](https://img.shields.io/badge/version-v2.3.1-7FB5B6)](https://github.com/Haaaiawd/ANWS/releases)
8
8
  [![Targets](https://img.shields.io/badge/Targets-Windsurf%20%7C%20Claude%20Code%20%7C%20Copilot%20%7C%20Cursor%20%7C%20Codex%20Preview%20%7C%20OpenCode%20%7C%20Trae%20%7C%20Qoder%20%7C%20Kilo%20Code-blueviolet)](https://github.com/Haaaiawd/ANWS)
9
9
 
10
10
  [English](./README.md) | [中文](./README_CN.md)
package/bin/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
2
+ "use strict";
3
3
 
4
- const { parseArgs } = require('node:util');
5
- const path = require('node:path');
6
- const { listTargets, getTarget } = require('../lib/adapters');
7
- const { blank, error, info, logo } = require('../lib/output');
4
+ const { parseArgs } = require("node:util");
5
+ const path = require("node:path");
6
+ const { listTargets, getTarget } = require("../lib/adapters");
7
+ const { blank, error, info, logo } = require("../lib/output");
8
8
 
9
9
  // ─── 版本号从 package.json 读取 ─────────────────────────────────────────────
10
- const { version } = require(path.join(__dirname, '..', 'package.json'));
10
+ const { version } = require(path.join(__dirname, "..", "package.json"));
11
11
  const TARGET_IDS = listTargets().map((target) => target.id);
12
12
 
13
13
  // ─── 帮助文本 ─────────────────────────────────────────────────────────────────
@@ -17,12 +17,24 @@ USAGE
17
17
 
18
18
  COMMANDS
19
19
  init Install one or more target AI IDE workflow projections
20
+ Init skips a target if its installedVersion equals the current CLI version
21
+ (since v2.4.0). Run update to refresh templates instead.
20
22
  update Scan installed targets from install-lock or directory layout and update them
23
+ Preserves the templateLocale recorded in install-lock. Generates a changelog.
21
24
 
22
25
  OPTIONS
23
26
  -v, --version Print version number
24
27
  -h, --help Show this help message
25
- --target Target AI IDE(s) for init, comma-separated (${TARGET_IDS.join(', ')})
28
+ -y, --yes Auto-confirm overwrite prompts (non-interactive)
29
+ --target Target AI IDE(s) for init, comma-separated (${TARGET_IDS.join(", ")})
30
+ --locale Template bundle: zh (templates/) or en (templates_en/)
31
+ Default: zh; interactive init prompts after IDE selection
32
+ Update preserves the locale recorded in install-lock
33
+
34
+ INIT VS UPDATE
35
+ Use init for first-time install or adding a new target IDE.
36
+ Use update when the CLI package has been upgraded and you want to refresh
37
+ templates without changing target selection or locale.
26
38
 
27
39
  SUPPORTED TARGETS
28
40
  windsurf workflows + skills
@@ -37,20 +49,22 @@ SUPPORTED TARGETS
37
49
  kilo workflows + skills
38
50
 
39
51
  EXAMPLES
40
- anws init # Choose target IDEs and install their managed workflow projections
52
+ anws init # Interactive: choose targets, then locale
41
53
  anws init --target windsurf,codex,opencode
42
- anws update # One-click update for all matched targets from install-lock, fallback scan, or drift repair
54
+ anws init --target windsurf --locale en -y
55
+ anws update # Refresh all matched targets from install-lock
43
56
  `.trimStart();
44
57
 
45
58
  // ─── 参数解析 ─────────────────────────────────────────────────────────────────
46
59
  const { values, positionals } = parseArgs({
47
60
  args: process.argv.slice(2),
48
61
  options: {
49
- version: { type: 'boolean', short: 'v', default: false },
50
- help: { type: 'boolean', short: 'h', default: false },
51
- yes: { type: 'boolean', short: 'y', default: false },
52
- target: { type: 'string' },
53
- check: { type: 'boolean', default: false },
62
+ version: { type: "boolean", short: "v", default: false },
63
+ help: { type: "boolean", short: "h", default: false },
64
+ yes: { type: "boolean", short: "y", default: false },
65
+ target: { type: "string" },
66
+ locale: { type: "string" },
67
+ check: { type: "boolean", default: false },
54
68
  },
55
69
  strict: false,
56
70
  allowPositionals: true,
@@ -60,8 +74,20 @@ if (values.yes) {
60
74
  global.__ANWS_FORCE_YES = true;
61
75
  }
62
76
 
77
+ if (values.locale !== undefined) {
78
+ const loc = String(values.locale).trim().toLowerCase();
79
+ if (loc !== "zh" && loc !== "en") {
80
+ error("--locale must be zh or en");
81
+ process.exit(1);
82
+ }
83
+ global.__ANWS_TEMPLATE_LOCALE = loc;
84
+ }
85
+
63
86
  if (values.target !== undefined) {
64
- const targetIds = values.target.split(',').map((item) => item.trim()).filter(Boolean);
87
+ const targetIds = values.target
88
+ .split(",")
89
+ .map((item) => item.trim())
90
+ .filter(Boolean);
65
91
  targetIds.forEach((targetId) => getTarget(targetId));
66
92
  global.__ANWS_TARGET_IDS = Array.from(new Set(targetIds));
67
93
  }
@@ -83,25 +109,29 @@ async function main() {
83
109
  const command = positionals[0];
84
110
 
85
111
  switch (command) {
86
- case 'init':
87
- await require('../lib/init')();
112
+ case "init":
113
+ await require("../lib/init")();
88
114
  break;
89
115
 
90
- case 'update':
116
+ case "update":
91
117
  if (values.target !== undefined) {
92
- error('`anws update --target` has been removed. Use `anws update` to update all matched targets.');
118
+ error(
119
+ "`anws update --target` has been removed. Use `anws update` to update all matched targets.",
120
+ );
93
121
  process.exit(1);
94
122
  }
95
123
  if (values.check) {
96
- error('`anws update --check` has been removed. Use `anws update` directly.');
124
+ error(
125
+ "`anws update --check` has been removed. Use `anws update` directly.",
126
+ );
97
127
  process.exit(1);
98
128
  }
99
- await require('../lib/update')();
129
+ await require("../lib/update")();
100
130
  break;
101
131
 
102
132
  default:
103
133
  error(`Unknown command: "${command}"`);
104
- info('Run `anws --help` to see available commands.');
134
+ info("Run `anws --help` to see available commands.");
105
135
  process.exit(1);
106
136
  }
107
137
  }
package/lib/diff.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const fs = require('node:fs/promises');
4
4
  const path = require('node:path');
5
5
  const { c } = require('./output');
6
+ const { resolveCanonicalPath } = require('./resources');
6
7
 
7
8
  async function pathExists(targetPath) {
8
9
  return fs.access(targetPath).then(() => true).catch(() => false);
@@ -127,9 +128,11 @@ async function collectManagedFileDiffs({
127
128
  projectionEntries = [],
128
129
  srcAgents,
129
130
  shouldWriteRootAgents,
130
- agentsUpdatePlan = null
131
+ agentsUpdatePlan = null,
132
+ resolveCanonicalPath: resolveCanonicalPathFn = null
131
133
  }) {
132
134
  const results = [];
135
+ const resolveSrc = resolveCanonicalPathFn || ((rel) => resolveCanonicalPath(rel, 'zh'));
133
136
  const normalizedProjectionEntries = projectionEntries.length > 0
134
137
  ? projectionEntries
135
138
  : projectionPlan.flatMap((item) => item.projectionEntries || []);
@@ -164,7 +167,7 @@ async function collectManagedFileDiffs({
164
167
  };
165
168
  const srcPath = rel === 'AGENTS.md'
166
169
  ? srcAgents
167
- : path.join(path.join(__dirname, '..', 'templates'), entry.source);
170
+ : resolveSrc(entry.source);
168
171
 
169
172
  if (rel !== 'AGENTS.md' && !(await pathExists(srcPath))) {
170
173
  throw new Error(
package/lib/init.js CHANGED
@@ -1,16 +1,36 @@
1
- 'use strict';
2
-
3
- const path = require('node:path');
4
- const fs = require('node:fs/promises');
5
- const { buildProjectionPlan } = require('./manifest');
6
- const { getTarget, listTargets } = require('./adapters');
7
- const { planAgentsUpdate, resolveAgentsInstall, printLegacyMigrationWarning, pathExists } = require('./agents');
8
- const { ensureChangelogDir } = require('./changelog');
9
- const { ROOT_AGENTS_FILE, resolveCanonicalSource } = require('./resources');
10
- const { writeTargetFiles } = require('./copy');
11
- const { createInstallLock, dedupeTargets, detectInstallState, summarizeTargetState, writeInstallLock } = require('./install-state');
12
- const { selectMultiple, confirm } = require('./prompt');
13
- const { success, warn, info, fileLine, skippedLine, blank, logo, section } = require('./output');
1
+ "use strict";
2
+
3
+ const path = require("node:path");
4
+ const fs = require("node:fs/promises");
5
+ const { buildProjectionPlan } = require("./manifest");
6
+ const { getTarget, listTargets } = require("./adapters");
7
+ const {
8
+ planAgentsUpdate,
9
+ resolveAgentsInstall,
10
+ printLegacyMigrationWarning,
11
+ pathExists,
12
+ } = require("./agents");
13
+ const { ensureChangelogDir } = require("./changelog");
14
+ const { resolveCanonicalPath, resolveRootAgentsPath } = require("./resources");
15
+ const { writeTargetFiles } = require("./copy");
16
+ const {
17
+ createInstallLock,
18
+ dedupeTargets,
19
+ detectInstallState,
20
+ summarizeTargetState,
21
+ writeInstallLock,
22
+ } = require("./install-state");
23
+ const { selectMultiple, selectRadio, confirm } = require("./prompt");
24
+ const {
25
+ success,
26
+ warn,
27
+ info,
28
+ fileLine,
29
+ skippedLine,
30
+ blank,
31
+ logo,
32
+ section,
33
+ } = require("./output");
14
34
 
15
35
  async function init() {
16
36
  const cwd = process.cwd();
@@ -18,18 +38,30 @@ async function init() {
18
38
  const installState = await detectInstallState(cwd);
19
39
  const retainedTargetIds = await resolveRetainedTargetIds(cwd, installState);
20
40
  const targets = await selectTargets(installState, retainedTargetIds);
41
+ const templateLocale = await resolveTemplateLocaleForInit(installState);
21
42
  const targetIds = Array.from(new Set(targets.map((item) => item.id)));
22
43
  const targetPlans = buildProjectionPlan(targetIds);
23
- const srcAgents = ROOT_AGENTS_FILE;
24
- const cliVersion = require(path.join(__dirname, '..', 'package.json')).version;
25
-
26
- info('Initializing Anws...');
27
- info(`Target IDEs: ${targets.map((item) => item.label).join(', ')}`);
44
+ const srcAgents = resolveRootAgentsPath(templateLocale);
45
+ const resolveCanonicalSource = (relPath) =>
46
+ resolveCanonicalPath(relPath, templateLocale);
47
+ const cliVersion = require(
48
+ path.join(__dirname, "..", "package.json"),
49
+ ).version;
50
+
51
+ info("Initializing Anws...");
52
+ info(`Target IDEs: ${targets.map((item) => item.label).join(", ")}`);
53
+ info(
54
+ `Template locale: ${templateLocale} (${templateLocale === "en" ? "templates_en/" : "templates/"})`,
55
+ );
28
56
  if (installState.needsFallback) {
29
- info('Install lock missing or unreadable. Falling back to scanned target state.');
57
+ info(
58
+ "Install lock missing or unreadable. Falling back to scanned target state.",
59
+ );
30
60
  }
31
61
  if (installState.drift.hasDrift) {
32
- warn(`Detected install-state drift. Missing on disk: ${installState.drift.missingOnDisk.join(', ') || 'none'}; untracked on disk: ${installState.drift.untrackedOnDisk.join(', ') || 'none'}.`);
62
+ warn(
63
+ `Detected install-state drift. Missing on disk: ${installState.drift.missingOnDisk.join(", ") || "none"}; untracked on disk: ${installState.drift.untrackedOnDisk.join(", ") || "none"}.`,
64
+ );
33
65
  }
34
66
  blank();
35
67
 
@@ -42,30 +74,51 @@ async function init() {
42
74
  for (const targetPlan of targetPlans) {
43
75
  const target = getTarget(targetPlan.targetId);
44
76
  const targetAlreadyInstalled = retainedTargetIds.includes(target.id);
45
- const rootAgentsExists = await pathExists(path.join(cwd, 'AGENTS.md'));
46
- const agentsDecision = target.id === 'antigravity'
47
- ? await resolveAgentsInstall({
48
- cwd,
49
- askMigrate,
50
- forceYes: !!global.__ANWS_FORCE_YES
51
- })
52
- : {
53
- shouldWriteRootAgents: true,
54
- shouldWarnMigration: false,
55
- rootExists: rootAgentsExists
56
- };
77
+
78
+ // Skip re-init of the same target at the same CLI version
79
+ const existingTarget = installState.lockResult.lock?.targets?.find(
80
+ (t) => t.targetId === target.id,
81
+ );
82
+ if (
83
+ targetAlreadyInstalled &&
84
+ existingTarget?.installedVersion === cliVersion
85
+ ) {
86
+ info(
87
+ `${target.label} already installed at v${cliVersion}. Skipping — run \`anws update\` to refresh templates.`,
88
+ );
89
+ continue;
90
+ }
91
+
92
+ const rootAgentsExists = await pathExists(path.join(cwd, "AGENTS.md"));
93
+ const agentsDecision =
94
+ target.id === "antigravity"
95
+ ? await resolveAgentsInstall({
96
+ cwd,
97
+ askMigrate,
98
+ forceYes: !!global.__ANWS_FORCE_YES,
99
+ })
100
+ : {
101
+ shouldWriteRootAgents: true,
102
+ shouldWarnMigration: false,
103
+ rootExists: rootAgentsExists,
104
+ };
57
105
 
58
106
  let agentsUpdatePlan = null;
59
107
  if (agentsDecision.shouldWriteRootAgents && agentsDecision.rootExists) {
60
- const templateContent = await fs.readFile(srcAgents, 'utf8');
61
- const existingContent = await fs.readFile(path.join(cwd, 'AGENTS.md'), 'utf8');
108
+ const templateContent = await fs.readFile(srcAgents, "utf8");
109
+ const existingContent = await fs.readFile(
110
+ path.join(cwd, "AGENTS.md"),
111
+ "utf8",
112
+ );
62
113
  agentsUpdatePlan = planAgentsUpdate({ templateContent, existingContent });
63
114
  }
64
115
 
65
116
  const conflicting = await findConflicts(
66
117
  cwd,
67
- targetAlreadyInstalled ? [] : targetPlan.managedFiles.filter((rel) => rel !== 'AGENTS.md'),
68
- sessionWrittenFiles
118
+ targetAlreadyInstalled
119
+ ? []
120
+ : targetPlan.managedFiles.filter((rel) => rel !== "AGENTS.md"),
121
+ sessionWrittenFiles,
69
122
  );
70
123
  if (conflicting.length > 0) {
71
124
  const confirmed = await askOverwrite(conflicting.length, target.label);
@@ -74,7 +127,7 @@ async function init() {
74
127
  failedTargets.push({
75
128
  targetId: target.id,
76
129
  targetLabel: target.label,
77
- reason: `Skipped ${conflicting.length} conflicting managed file(s)`
130
+ reason: `Skipped ${conflicting.length} conflicting managed file(s)`,
78
131
  });
79
132
  continue;
80
133
  }
@@ -86,7 +139,7 @@ async function init() {
86
139
  srcAgents,
87
140
  shouldWriteRootAgents: agentsDecision.shouldWriteRootAgents,
88
141
  agentsUpdatePlan,
89
- resolveCanonicalSource
142
+ resolveCanonicalSource,
90
143
  });
91
144
 
92
145
  written.push(...result.written);
@@ -103,37 +156,56 @@ async function init() {
103
156
 
104
157
  await ensureChangelogDir(cwd);
105
158
  const existingTargets = installState.lockResult.lock?.targets || [];
159
+ const mergedTargets = dedupeTargets([
160
+ ...existingTargets,
161
+ ...successfulTargets,
162
+ ]);
163
+ const priorTemplateLocale = installState.lockResult.lock?.templateLocale;
164
+ const effectiveTemplateLocale =
165
+ written.length === 0 && priorTemplateLocale
166
+ ? priorTemplateLocale
167
+ : templateLocale;
106
168
  const generatedAt = new Date().toISOString();
107
- await writeInstallLock(cwd, createInstallLock({
108
- cliVersion,
109
- generatedAt,
110
- targets: dedupeTargets([
111
- ...existingTargets,
112
- ...successfulTargets
113
- ]),
114
- lastUpdateSummary: {
115
- successfulTargets: successfulTargets.map((item) => item.targetId),
116
- failedTargets: failedTargets.map((item) => item.targetId),
117
- updatedAt: generatedAt
118
- }
119
- }));
169
+
170
+ if (mergedTargets.length > 0) {
171
+ await writeInstallLock(
172
+ cwd,
173
+ createInstallLock({
174
+ cliVersion,
175
+ generatedAt,
176
+ templateLocale: effectiveTemplateLocale,
177
+ targets: mergedTargets,
178
+ lastUpdateSummary: {
179
+ successfulTargets: successfulTargets.map((item) => item.targetId),
180
+ failedTargets: failedTargets.map((item) => item.targetId),
181
+ updatedAt: generatedAt,
182
+ },
183
+ }),
184
+ );
185
+ } else {
186
+ warn(
187
+ "Install-lock was not written: no targets were installed and no prior lock targets to retain.",
188
+ );
189
+ }
120
190
 
121
191
  printTargetSummary(successfulTargets, failedTargets);
122
192
 
123
193
  for (const rel of written) {
124
- fileLine(rel.replace(/\\/g, '/'));
194
+ fileLine(rel.replace(/\\/g, "/"));
125
195
  }
126
196
 
127
197
  if (skipped.length > 0) {
128
198
  blank();
129
- info('Skipped (project-specific, preserved):');
199
+ info("Skipped (project-specific, preserved):");
130
200
  for (const rel of skipped) {
131
- skippedLine(rel.replace(/\\/g, '/'));
201
+ skippedLine(rel.replace(/\\/g, "/"));
132
202
  }
133
203
  }
134
204
 
135
205
  blank();
136
- success(`Done! ${written.length} files written for ${successfulTargets.map((item) => item.targetLabel).join(', ') || 'selected targets'}.`);
206
+ success(
207
+ `Done! ${written.length} files written for ${successfulTargets.map((item) => item.targetLabel).join(", ") || "selected targets"}.`,
208
+ );
137
209
  printNextSteps(targets);
138
210
  }
139
211
 
@@ -143,7 +215,11 @@ async function init() {
143
215
  * 找出 cwd 中已存在的托管文件列表。
144
216
  * @returns {Promise<string[]>} 已存在的托管文件相对路径数组
145
217
  */
146
- async function findConflicts(cwd, managedFiles, sessionWrittenFiles = new Set()) {
218
+ async function findConflicts(
219
+ cwd,
220
+ managedFiles,
221
+ sessionWrittenFiles = new Set(),
222
+ ) {
147
223
  const conflicts = [];
148
224
  for (const rel of managedFiles) {
149
225
  if (sessionWrittenFiles.has(rel)) {
@@ -165,15 +241,17 @@ async function askOverwrite(count, label) {
165
241
  if (global.__ANWS_FORCE_YES) return true;
166
242
 
167
243
  if (!process.stdin.isTTY) {
168
- warn(`${count} managed file(s) already exist. Non-TTY: skipping overwrite.`);
244
+ warn(
245
+ `${count} managed file(s) already exist. Non-TTY: skipping overwrite.`,
246
+ );
169
247
  return false;
170
248
  }
171
249
 
172
250
  return confirm({
173
251
  message: `Overwrite ${count} managed file(s) for ${label}?`,
174
- confirmLabel: 'Overwrite',
175
- cancelLabel: 'Skip',
176
- defaultValue: false
252
+ confirmLabel: "Overwrite",
253
+ cancelLabel: "Skip",
254
+ defaultValue: false,
177
255
  });
178
256
  }
179
257
 
@@ -185,10 +263,10 @@ async function askMigrate() {
185
263
  }
186
264
 
187
265
  return confirm({
188
- message: 'Legacy .agent/ directory detected. Migrate to .agents/?',
189
- confirmLabel: 'Migrate',
190
- cancelLabel: 'Keep legacy',
191
- defaultValue: false
266
+ message: "Legacy .agent/ directory detected. Migrate to .agents/?",
267
+ confirmLabel: "Migrate",
268
+ cancelLabel: "Keep legacy",
269
+ defaultValue: false,
192
270
  });
193
271
  }
194
272
 
@@ -199,41 +277,71 @@ async function askMigrate() {
199
277
  * @param {string} action
200
278
  */
201
279
  function printSummary(files, skipped = [], action) {
202
- const verb = action === 'updated' ? 'Updating' : 'Writing';
280
+ const verb = action === "updated" ? "Updating" : "Writing";
203
281
  blank();
204
282
  info(`${verb} files...`);
205
283
  blank();
206
284
  for (const rel of files) {
207
- fileLine(rel.replace(/\\/g, '/'));
285
+ fileLine(rel.replace(/\\/g, "/"));
208
286
  }
209
287
  if (skipped.length > 0) {
210
288
  blank();
211
- info('Skipped (project-specific, preserved):');
289
+ info("Skipped (project-specific, preserved):");
212
290
  for (const rel of skipped) {
213
- skippedLine(rel.replace(/\\/g, '/'));
291
+ skippedLine(rel.replace(/\\/g, "/"));
214
292
  }
215
293
  }
216
294
  blank();
217
- success(`Done! ${files.length} file(s) ${action}${skipped.length > 0 ? `, ${skipped.length} skipped` : ''}.`);
218
- if (action === 'updated') {
219
- info('Managed files have been updated to the latest version.');
295
+ success(
296
+ `Done! ${files.length} file(s) ${action}${skipped.length > 0 ? `, ${skipped.length} skipped` : ""}.`,
297
+ );
298
+ if (action === "updated") {
299
+ info("Managed files have been updated to the latest version.");
300
+ }
301
+ }
302
+
303
+ async function resolveTemplateLocaleForInit(installState) {
304
+ if (
305
+ global.__ANWS_TEMPLATE_LOCALE === "zh" ||
306
+ global.__ANWS_TEMPLATE_LOCALE === "en"
307
+ ) {
308
+ return global.__ANWS_TEMPLATE_LOCALE;
309
+ }
310
+
311
+ let fromLock = "zh";
312
+ if (installState.lockResult.lock) {
313
+ fromLock = installState.lockResult.lock.templateLocale || "zh";
314
+ }
315
+
316
+ if (!process.stdin.isTTY) {
317
+ return fromLock === "en" ? "en" : "zh";
220
318
  }
319
+
320
+ const initialIndex = fromLock === "en" ? 1 : 0;
321
+ return selectRadio({
322
+ message:
323
+ "Workflow template bundle (manifest paths are the same; text is zh or en):",
324
+ options: [
325
+ { label: "中文 (templates/)", value: "zh" },
326
+ { label: "English (templates_en/)", value: "en" },
327
+ ],
328
+ initialIndex,
329
+ });
221
330
  }
222
331
 
223
332
  async function selectTargets(installState, retainedTargetIds = []) {
224
333
  const installedTargetIds = new Set(retainedTargetIds);
225
334
 
226
335
  if (global.__ANWS_TARGET_IDS && global.__ANWS_TARGET_IDS.length > 0) {
227
- return Array.from(new Set([
228
- ...retainedTargetIds,
229
- ...global.__ANWS_TARGET_IDS
230
- ])).map((targetId) => getTarget(targetId));
336
+ return Array.from(
337
+ new Set([...retainedTargetIds, ...global.__ANWS_TARGET_IDS]),
338
+ ).map((targetId) => getTarget(targetId));
231
339
  }
232
340
 
233
341
  if (!process.stdin.isTTY) {
234
342
  return retainedTargetIds.length > 0
235
343
  ? retainedTargetIds.map((targetId) => getTarget(targetId))
236
- : [getTarget('antigravity')];
344
+ : [getTarget("antigravity")];
237
345
  }
238
346
 
239
347
  const targets = listTargets();
@@ -248,22 +356,26 @@ async function selectTargets(installState, retainedTargetIds = []) {
248
356
  }
249
357
 
250
358
  if (initialSelectedIndexes.length === 0) {
251
- const antigravityIndex = targets.findIndex((target) => target.id === 'antigravity');
359
+ const antigravityIndex = targets.findIndex(
360
+ (target) => target.id === "antigravity",
361
+ );
252
362
  if (antigravityIndex >= 0) {
253
363
  initialSelectedIndexes.push(antigravityIndex);
254
364
  }
255
365
  }
256
366
 
257
367
  return selectMultiple({
258
- message: 'Choose your target AI IDEs:',
368
+ message: "Choose your target AI IDEs:",
259
369
  options: targets.map((target) => ({
260
370
  label: target.label,
261
371
  value: target.id,
262
- locked: installedTargetIds.has(target.id)
372
+ locked: installedTargetIds.has(target.id),
263
373
  })),
264
374
  initialSelectedIndexes,
265
- lockedIndexes
266
- }).then((selectedOptions) => selectedOptions.map((option) => getTarget(option.value)));
375
+ lockedIndexes,
376
+ }).then((selectedOptions) =>
377
+ selectedOptions.map((option) => getTarget(option.value)),
378
+ );
267
379
  }
268
380
 
269
381
  async function resolveRetainedTargetIds(cwd, installState) {
@@ -275,14 +387,16 @@ async function resolveRetainedTargetIds(cwd, installState) {
275
387
 
276
388
  for (const targetId of installState.selectedTargets) {
277
389
  const [targetPlan] = buildProjectionPlan([targetId]);
278
- const managedFiles = (targetPlan?.managedFiles || []).filter((rel) => rel !== 'AGENTS.md');
390
+ const managedFiles = (targetPlan?.managedFiles || []).filter(
391
+ (rel) => rel !== "AGENTS.md",
392
+ );
279
393
  if (managedFiles.length === 0) {
280
394
  retainedTargetIds.push(targetId);
281
395
  continue;
282
396
  }
283
397
 
284
398
  const managedExists = await Promise.all(
285
- managedFiles.map((rel) => pathExists(path.join(cwd, rel)))
399
+ managedFiles.map((rel) => pathExists(path.join(cwd, rel))),
286
400
  );
287
401
 
288
402
  if (managedExists.every(Boolean)) {
@@ -295,24 +409,31 @@ async function resolveRetainedTargetIds(cwd, installState) {
295
409
 
296
410
  function printNextSteps(targets) {
297
411
  blank();
298
- section('Next steps', targets.some((target) => target.rootAgentFile)
299
- ? [
300
- '1. Read AGENTS.md to understand the system',
301
- '2. Run /quickstart in your AI assistant to analyze and start the workflow'
302
- ]
303
- : [
304
- '1. Review files written under the selected target directories',
305
- '2. Run /quickstart in your AI assistant to analyze and start the workflow'
306
- ]);
412
+ section(
413
+ "Next steps",
414
+ targets.some((target) => target.rootAgentFile)
415
+ ? [
416
+ "1. Read AGENTS.md to understand the system",
417
+ "2. Run /quickstart in your AI assistant to analyze and start the workflow",
418
+ ]
419
+ : [
420
+ "1. Review files written under the selected target directories",
421
+ "2. Run /quickstart in your AI assistant to analyze and start the workflow",
422
+ ],
423
+ );
307
424
  }
308
425
 
309
426
  function printTargetSummary(successfulTargets, failedTargets) {
310
427
  blank();
311
- section('Target summary', [
312
- ...successfulTargets.map((target) => `✔ ${target.targetLabel} (${target.targetId})`),
313
- ...failedTargets.map((target) => `✖ ${target.targetLabel} (${target.targetId}) — ${target.reason}`)
428
+ section("Target summary", [
429
+ ...successfulTargets.map(
430
+ (target) => `✔ ${target.targetLabel} (${target.targetId})`,
431
+ ),
432
+ ...failedTargets.map(
433
+ (target) =>
434
+ `✖ ${target.targetLabel} (${target.targetId}) — ${target.reason}`,
435
+ ),
314
436
  ]);
315
437
  }
316
438
 
317
439
  module.exports = init;
318
-