@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
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ const TARGETS = {
7
+ windsurf: {
8
+ id: 'windsurf',
9
+ label: 'Windsurf',
10
+ rootAgentFile: false,
11
+ projectionTypes: {
12
+ workflow: ['workflows'],
13
+ skill: ['skills']
14
+ },
15
+ projections: {
16
+ workflows: '.windsurf/workflows',
17
+ skills: '.windsurf/skills'
18
+ },
19
+ detect: ['.windsurf/workflows/genesis.md']
20
+ },
21
+ antigravity: {
22
+ id: 'antigravity',
23
+ label: 'Antigravity',
24
+ rootAgentFile: true,
25
+ projectionTypes: {
26
+ workflow: ['workflows'],
27
+ skill: ['skills']
28
+ },
29
+ projections: {
30
+ workflows: '.agents/workflows',
31
+ skills: '.agents/skills'
32
+ },
33
+ detect: ['.agents/workflows/genesis.md', '.agent/workflows/genesis.md']
34
+ },
35
+ cursor: {
36
+ id: 'cursor',
37
+ label: 'Cursor',
38
+ rootAgentFile: false,
39
+ projectionTypes: {
40
+ workflow: ['commands'],
41
+ skill: ['skills']
42
+ },
43
+ projections: {
44
+ commands: '.cursor/commands',
45
+ skills: '.cursor/skills'
46
+ },
47
+ detect: ['.cursor/commands/genesis.md']
48
+ },
49
+ claude: {
50
+ id: 'claude',
51
+ label: 'Claude',
52
+ rootAgentFile: false,
53
+ projectionTypes: {
54
+ workflow: ['commands'],
55
+ skill: ['skills']
56
+ },
57
+ projections: {
58
+ commands: '.claude/commands',
59
+ skills: '.claude/skills'
60
+ },
61
+ detect: ['.claude/commands/genesis.md']
62
+ },
63
+ copilot: {
64
+ id: 'copilot',
65
+ label: 'GitHub Copilot',
66
+ rootAgentFile: false,
67
+ projectionTypes: {
68
+ workflow: ['prompts'],
69
+ skill: ['skills']
70
+ },
71
+ projections: {
72
+ prompts: '.github/prompts',
73
+ skills: '.github/skills'
74
+ },
75
+ detect: ['.github/prompts/genesis.prompt.md']
76
+ },
77
+ codex: {
78
+ id: 'codex',
79
+ label: 'Codex (Preview)',
80
+ rootAgentFile: false,
81
+ projectionTypes: {
82
+ workflow: ['skills'],
83
+ skill: ['skills']
84
+ },
85
+ projections: {
86
+ skills: '.codex/skills'
87
+ },
88
+ detect: ['.codex/skills/anws-system/SKILL.md']
89
+ },
90
+ opencode: {
91
+ id: 'opencode',
92
+ label: 'OpenCode',
93
+ rootAgentFile: false,
94
+ projectionTypes: {
95
+ workflow: ['commands'],
96
+ skill: ['skills']
97
+ },
98
+ projections: {
99
+ commands: '.opencode/commands',
100
+ skills: '.opencode/skills'
101
+ },
102
+ detect: ['.opencode/commands/genesis.md']
103
+ }
104
+ };
105
+
106
+ function listTargets() {
107
+ return Object.values(TARGETS);
108
+ }
109
+
110
+ function getTarget(targetId) {
111
+ if (!targetId) {
112
+ throw new Error('targetId is required');
113
+ }
114
+
115
+ const target = TARGETS[targetId];
116
+ if (!target) {
117
+ throw new Error(`Unsupported target: ${targetId}. Supported targets: ${listTargets().map((item) => item.id).join(', ')}`);
118
+ }
119
+
120
+ return target;
121
+ }
122
+
123
+ async function pathExists(targetPath) {
124
+ return fs.access(targetPath).then(() => true).catch(() => false);
125
+ }
126
+
127
+ async function detectInstalledTarget(cwd) {
128
+ const targets = await detectInstalledTargets(cwd);
129
+ return targets[0] || null;
130
+ }
131
+
132
+ async function detectInstalledTargets(cwd) {
133
+ const installedTargets = [];
134
+
135
+ for (const target of listTargets()) {
136
+ for (const relPath of target.detect) {
137
+ if (await pathExists(path.join(cwd, relPath))) {
138
+ installedTargets.push(target);
139
+ break;
140
+ }
141
+ }
142
+ }
143
+
144
+ return installedTargets;
145
+ }
146
+
147
+ module.exports = {
148
+ TARGETS,
149
+ detectInstalledTarget,
150
+ detectInstalledTargets,
151
+ getTarget,
152
+ listTargets
153
+ };
154
+
155
+
156
+
157
+
package/lib/agents.js ADDED
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { warn, blank } = require('./output');
6
+ const AUTO_BEGIN_RE = /<!-- AUTO:BEGIN[\s\S]*?-->/;
7
+ const AUTO_END_RE = /<!-- AUTO:END -->/;
8
+ const LEGACY_HEADINGS = [
9
+ '## 📍 当前状态',
10
+ '## 🌳 项目结构',
11
+ '## 🧭 导航指南'
12
+ ];
13
+
14
+ async function pathExists(targetPath) {
15
+ return fs.access(targetPath).then(() => true).catch(() => false);
16
+ }
17
+
18
+ async function getAgentsState(cwd) {
19
+ const legacyPath = path.join(cwd, '.agent', 'rules', 'agents.md');
20
+ const rootPath = path.join(cwd, 'AGENTS.md');
21
+
22
+ return {
23
+ legacyPath,
24
+ rootPath,
25
+ legacyExists: await pathExists(legacyPath),
26
+ rootExists: await pathExists(rootPath)
27
+ };
28
+ }
29
+
30
+ async function resolveAgentsInstall({ cwd, askMigrate, forceYes = false }) {
31
+ const state = await getAgentsState(cwd);
32
+
33
+ if (!state.legacyExists) {
34
+ return { ...state, shouldWriteRootAgents: true, shouldWarnMigration: false };
35
+ }
36
+
37
+ const migrate = forceYes ? true : await askMigrate();
38
+ return {
39
+ ...state,
40
+ shouldWriteRootAgents: migrate,
41
+ shouldWarnMigration: migrate
42
+ };
43
+ }
44
+
45
+ function getAutoBlockRange(content) {
46
+ const beginMatch = content.match(AUTO_BEGIN_RE);
47
+ const endMatch = content.match(AUTO_END_RE);
48
+
49
+ if (!beginMatch || !endMatch) return null;
50
+
51
+ const beginIndex = beginMatch.index;
52
+ const endIndex = endMatch.index;
53
+ const beginEnd = beginIndex + beginMatch[0].length;
54
+ const endEnd = endIndex + endMatch[0].length;
55
+
56
+ if (beginIndex > endIndex) return null;
57
+
58
+ return {
59
+ beginIndex,
60
+ beginEnd,
61
+ endIndex,
62
+ endEnd
63
+ };
64
+ }
65
+
66
+ function hasAutoBlock(content) {
67
+ return getAutoBlockRange(content) !== null;
68
+ }
69
+
70
+ function isRecognizedLegacyAgents(content) {
71
+ // 检查主标题
72
+ if (!content.includes('# AGENTS.md - AI 协作协议')) return false;
73
+
74
+ // 检查三个核心 section 标题(允许标题后包含额外文字)
75
+ return LEGACY_HEADINGS.every((heading) => content.includes(heading));
76
+ }
77
+
78
+ function extractSection(content, heading) {
79
+ const headingIndex = content.indexOf(heading);
80
+ if (headingIndex === -1) return null;
81
+
82
+ const afterHeading = content.slice(headingIndex + heading.length);
83
+ const sectionBreaks = [];
84
+ const nextRule = afterHeading.indexOf('\n---');
85
+ const nextHeading = afterHeading.indexOf('\n## ');
86
+ if (nextRule !== -1) sectionBreaks.push(nextRule);
87
+ if (nextHeading !== -1) sectionBreaks.push(nextHeading);
88
+
89
+ const relativeEnd = sectionBreaks.length > 0
90
+ ? Math.min(...sectionBreaks)
91
+ : afterHeading.length;
92
+
93
+ return content.slice(headingIndex, headingIndex + heading.length + relativeEnd).trimEnd();
94
+ }
95
+
96
+ function replaceSection(content, heading, replacement) {
97
+ const current = extractSection(content, heading);
98
+ if (!current) return content;
99
+ return content.replace(current, replacement);
100
+ }
101
+
102
+ function migrateLegacyAgentsContent({ templateContent, existingContent }) {
103
+ const templateRange = getAutoBlockRange(templateContent);
104
+ if (!templateRange) {
105
+ return {
106
+ mode: 'replace',
107
+ content: templateContent
108
+ };
109
+ }
110
+
111
+ let autoBody = templateContent.slice(templateRange.beginEnd, templateRange.endIndex);
112
+ for (const heading of LEGACY_HEADINGS) {
113
+ const legacySection = extractSection(existingContent, heading);
114
+ if (legacySection) {
115
+ autoBody = replaceSection(autoBody, heading, legacySection);
116
+ }
117
+ }
118
+
119
+ return {
120
+ mode: 'migrate',
121
+ content: `${templateContent.slice(0, templateRange.beginEnd)}${autoBody}${templateContent.slice(templateRange.endIndex)}`
122
+ };
123
+ }
124
+
125
+ function mergeAgentsContent({ templateContent, existingContent }) {
126
+ const templateRange = getAutoBlockRange(templateContent);
127
+ const existingRange = getAutoBlockRange(existingContent);
128
+
129
+ if (!templateRange || !existingRange) {
130
+ return {
131
+ mode: 'replace',
132
+ content: templateContent
133
+ };
134
+ }
135
+
136
+ const templatePrefix = templateContent.slice(0, templateRange.beginEnd);
137
+ const templateSuffix = templateContent.slice(templateRange.endIndex);
138
+ const existingMiddle = existingContent.slice(existingRange.beginEnd, existingRange.endIndex);
139
+
140
+ return {
141
+ mode: 'merge',
142
+ content: `${templatePrefix}${existingMiddle}${templateSuffix}`
143
+ };
144
+ }
145
+
146
+ function planAgentsUpdate({ templateContent, existingContent }) {
147
+ if (!existingContent) {
148
+ return {
149
+ mode: 'replace',
150
+ content: templateContent
151
+ };
152
+ }
153
+
154
+ if (hasAutoBlock(existingContent)) {
155
+ return mergeAgentsContent({ templateContent, existingContent });
156
+ }
157
+
158
+ if (isRecognizedLegacyAgents(existingContent)) {
159
+ return migrateLegacyAgentsContent({ templateContent, existingContent });
160
+ }
161
+
162
+ return {
163
+ mode: 'skip',
164
+ content: existingContent,
165
+ warning: 'Root AGENTS.md does not contain AUTO markers and does not match a recognized legacy template. anws update will preserve it unchanged. Please migrate it manually or re-run after adding markers.'
166
+ };
167
+ }
168
+
169
+ function printLegacyMigrationWarning() {
170
+ blank();
171
+ warn('Please manually copy your custom rules from .agent/rules/agents.md to the root AGENTS.md');
172
+ warn('After copying, you can safely delete the old .agent/rules/agents.md file.');
173
+ blank();
174
+ }
175
+
176
+ module.exports = {
177
+ getAgentsState,
178
+ hasAutoBlock,
179
+ isRecognizedLegacyAgents,
180
+ mergeAgentsContent,
181
+ planAgentsUpdate,
182
+ resolveAgentsInstall,
183
+ printLegacyMigrationWarning,
184
+ pathExists
185
+ };
@@ -0,0 +1,187 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { groupChanges } = require('./diff');
6
+
7
+ const README_CONTENT = `# ⚠️ 重要:请勿删除此目录!
8
+
9
+ ## 这个目录是做什么的?
10
+
11
+ \`.anws/changelog/\` 存放每次 \`anws update\` 生成的升级记录。
12
+
13
+ ## 为什么不能删除?
14
+
15
+ 1. **AI 升级判断依赖此目录**
16
+ - AI 需要读取历史升级记录来判断变更级别
17
+ - 删除后将导致 AI 无法准确判断升级影响
18
+
19
+ 2. **业务文档升级的依据**
20
+ - 升级记录包含详细的变更详情
21
+ - AI 根据这些记录升级你的业务文档
22
+
23
+ 3. **回滚依据**
24
+ - 如果升级出现问题,可以参考历史记录回滚
25
+
26
+ ## 文件命名规则
27
+
28
+ - \`v1.4.0.md\` - v1.4.0 的变更记录
29
+ - \`v1.5.0.md\` - v1.5.0 的变更记录
30
+
31
+ ## 可以删除单个文件吗?
32
+
33
+ **不建议**。每个文件都是升级历史的一部分,删除会影响 AI 的判断能力。
34
+ `;
35
+
36
+ async function ensureChangelogDir(cwd) {
37
+ const changelogDir = path.join(cwd, '.anws', 'changelog');
38
+ await fs.mkdir(changelogDir, { recursive: true });
39
+ await fs.writeFile(path.join(changelogDir, '.gitkeep'), '', 'utf8');
40
+ await fs.writeFile(path.join(changelogDir, 'README.md'), README_CONTENT, 'utf8');
41
+ return changelogDir;
42
+ }
43
+
44
+ function compareSemver(a, b) {
45
+ const aParts = String(a).split('.').map((item) => Number(item));
46
+ const bParts = String(b).split('.').map((item) => Number(item));
47
+
48
+ for (let index = 0; index < 3; index += 1) {
49
+ const left = aParts[index] || 0;
50
+ const right = bParts[index] || 0;
51
+ if (left > right) return 1;
52
+ if (left < right) return -1;
53
+ }
54
+
55
+ return 0;
56
+ }
57
+
58
+ function formatFileList(title, items) {
59
+ const lines = [`### ${title}`];
60
+ if (items.length === 0) {
61
+ lines.push('- 无');
62
+ return lines.join('\n');
63
+ }
64
+
65
+ for (const item of items) {
66
+ lines.push(`- \`${item.file}\``);
67
+ }
68
+
69
+ return lines.join('\n');
70
+ }
71
+
72
+ function formatDetail(item) {
73
+ if (item.type === 'added') {
74
+ return [
75
+ `### \`${item.file}\``,
76
+ '- **新增文件**',
77
+ '- **说明**: 该文件在旧版本中不存在,因此无前后逐行对比。'
78
+ ].join('\n');
79
+ }
80
+
81
+ if (item.type === 'deleted') {
82
+ return [
83
+ `### \`${item.file}\``,
84
+ '- **删除文件**',
85
+ '- **说明**: 该文件在新版本中不存在,因此无前后逐行对比。'
86
+ ].join('\n');
87
+ }
88
+
89
+ if (item.summary.length === 0) {
90
+ return [
91
+ `### \`${item.file}\``,
92
+ '- **说明**: 检测到内容变更,但未能提取到摘要。'
93
+ ].join('\n');
94
+ }
95
+
96
+ return [
97
+ `### \`${item.file}\``,
98
+ '```diff',
99
+ ...item.summary.flatMap((pair) => [
100
+ `- [old:${pair.oldLineNumber === null ? '-' : pair.oldLineNumber}] ${pair.oldText}`,
101
+ `+ [new:${pair.newLineNumber === null ? '-' : pair.newLineNumber}] ${pair.newText}`
102
+ ]),
103
+ '```'
104
+ ].join('\n');
105
+ }
106
+
107
+ async function generateChangelog({ cwd, version, changes, targetSummary = null }) {
108
+ const changelogDir = await ensureChangelogDir(cwd);
109
+ const grouped = groupChanges(changes);
110
+ const now = new Date();
111
+ const timestamp = now.toISOString().replace('T', ' ').slice(0, 19);
112
+ const filePath = path.join(changelogDir, `v${version}.md`);
113
+
114
+ const content = [
115
+ `# 升级记录: v${version}`,
116
+ '',
117
+ '> ⚠️ **此文件由 `anws update` 自动生成,请勿删除!**',
118
+ '> 删除后将导致 AI 无法获取历史升级信息,影响后续升级判断。',
119
+ '',
120
+ '## 元信息',
121
+ `- **升级版本**: ${version}`,
122
+ `- 升级时间: ${timestamp}`,
123
+ '- **升级类型**: 由 `/upgrade` 工作流判断 (Minor/Major)',
124
+ ...(targetSummary
125
+ ? [
126
+ `- **成功 Targets**: ${targetSummary.successfulTargets.length > 0 ? targetSummary.successfulTargets.join(', ') : '无'}`,
127
+ `- **失败 Targets**: ${targetSummary.failedTargets.length > 0 ? targetSummary.failedTargets.join(', ') : '无'}`
128
+ ]
129
+ : []),
130
+ '',
131
+ '## 变更摘要',
132
+ `- 新增文件: ${grouped.added.length}`,
133
+ `- 修改文件: ${grouped.modified.length}`,
134
+ `- 删除文件: ${grouped.deleted.length}`,
135
+ '',
136
+ '## 文件级变更清单',
137
+ '',
138
+ formatFileList('新增文件', grouped.added),
139
+ '',
140
+ formatFileList('修改文件', grouped.modified),
141
+ '',
142
+ formatFileList('删除文件', grouped.deleted),
143
+ '',
144
+ '## 内容级变更详情',
145
+ '',
146
+ ...(changes.length > 0 ? changes.map(formatDetail).flatMap((section) => [section, '']) : ['- 无变更', ''])
147
+ ].join('\n');
148
+
149
+ await fs.writeFile(filePath, content, 'utf8');
150
+ return filePath;
151
+ }
152
+
153
+ async function detectUpgrade({ cwd, version }) {
154
+ const changelogDir = path.join(cwd, '.anws', 'changelog');
155
+
156
+ try {
157
+ const files = await fs.readdir(changelogDir);
158
+ const latest = files
159
+ .filter((name) => /^v\d+\.\d+\.\d+\.md$/.test(name))
160
+ .sort((left, right) => {
161
+ const leftVersion = left.slice(1, -3);
162
+ const rightVersion = right.slice(1, -3);
163
+ return compareSemver(leftVersion, rightVersion);
164
+ })
165
+ .pop();
166
+
167
+ if (!latest) {
168
+ return { needUpgrade: true, fromVersion: null, toVersion: version, latestVersion: null };
169
+ }
170
+
171
+ const fromVersion = latest.slice(1, -3);
172
+ return {
173
+ needUpgrade: compareSemver(fromVersion, version) !== 0,
174
+ fromVersion,
175
+ toVersion: version,
176
+ latestVersion: fromVersion
177
+ };
178
+ } catch {
179
+ return { needUpgrade: true, fromVersion: null, toVersion: version, latestVersion: null };
180
+ }
181
+ }
182
+
183
+ module.exports = {
184
+ detectUpgrade,
185
+ ensureChangelogDir,
186
+ generateChangelog
187
+ };
package/lib/copy.js CHANGED
@@ -35,4 +35,75 @@ async function copyDir(srcDir, destDir) {
35
35
  return written;
36
36
  }
37
37
 
38
- module.exports = { copyDir };
38
+ async function pathExists(targetPath) {
39
+ return fs.access(targetPath).then(() => true).catch(() => false);
40
+ }
41
+
42
+ async function writeTargetFiles(cwd, options = {}) {
43
+ const targetPlan = options.targetPlan || {};
44
+ const protectedFiles = options.protectedFiles || targetPlan.userProtectedFiles || [];
45
+ const projectionEntries = targetPlan.projectionEntries || [];
46
+ const shouldWriteRootAgents = options.shouldWriteRootAgents !== false;
47
+ const srcAgents = options.srcAgents;
48
+ const agentsUpdatePlan = options.agentsUpdatePlan;
49
+ const resolveCanonicalSource = options.resolveCanonicalSource;
50
+ const projectionMap = new Map(projectionEntries.map((item) => [item.outputPath, item]));
51
+ const written = [];
52
+ const skipped = [];
53
+
54
+ for (const rel of targetPlan.managedFiles || []) {
55
+ if (rel === 'AGENTS.md' && !shouldWriteRootAgents) {
56
+ skipped.push(rel);
57
+ continue;
58
+ }
59
+
60
+ if (rel === 'AGENTS.md') {
61
+ const destPath = path.join(cwd, rel);
62
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
63
+
64
+ if (agentsUpdatePlan && agentsUpdatePlan.mode === 'skip') {
65
+ skipped.push(rel);
66
+ continue;
67
+ }
68
+
69
+ if (agentsUpdatePlan && agentsUpdatePlan.content) {
70
+ await fs.writeFile(destPath, agentsUpdatePlan.content, 'utf8');
71
+ written.push(rel);
72
+ continue;
73
+ }
74
+
75
+ if (await pathExists(srcAgents)) {
76
+ await fs.copyFile(srcAgents, destPath);
77
+ written.push(rel);
78
+ continue;
79
+ }
80
+ }
81
+
82
+ if (protectedFiles.includes(rel)) {
83
+ const destPath = path.join(cwd, rel);
84
+ if (await pathExists(destPath)) {
85
+ skipped.push(rel);
86
+ continue;
87
+ }
88
+ }
89
+
90
+ const entry = projectionMap.get(rel);
91
+ const destPath = path.join(cwd, rel);
92
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
93
+
94
+ const srcPath = resolveCanonicalSource(entry.source);
95
+ if (await pathExists(srcPath)) {
96
+ await fs.copyFile(srcPath, destPath);
97
+ written.push(rel);
98
+ }
99
+ }
100
+
101
+ return {
102
+ targetId: targetPlan.targetId,
103
+ targetLabel: targetPlan.targetLabel,
104
+ written,
105
+ skipped
106
+ };
107
+ }
108
+
109
+ module.exports = { copyDir, writeTargetFiles };