@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.
- package/README.md +208 -173
- package/bin/cli.js +22 -9
- package/lib/adapters/index.js +157 -0
- package/lib/agents.js +185 -0
- package/lib/changelog.js +187 -0
- package/lib/copy.js +72 -1
- package/lib/diff.js +270 -0
- package/lib/init.js +238 -174
- package/lib/install-state.js +195 -0
- package/lib/manifest.js +184 -42
- package/lib/output.js +185 -13
- package/lib/prompt.js +284 -0
- package/lib/resources/index.js +27 -0
- package/lib/update.js +355 -149
- package/package.json +10 -6
- package/templates/.agents/skills/concept-modeler/SKILL.md +176 -0
- package/templates/{.agent → .agents}/skills/design-reviewer/SKILL.md +6 -6
- package/templates/.agents/skills/nexus-mapper/SKILL.md +306 -0
- package/templates/.agents/skills/nexus-mapper/references/language-customization.md +164 -0
- package/templates/.agents/skills/nexus-mapper/references/output-schema.md +298 -0
- package/templates/.agents/skills/nexus-mapper/references/probe-protocol.md +246 -0
- package/templates/.agents/skills/nexus-mapper/scripts/extract_ast.py +706 -0
- package/templates/.agents/skills/nexus-mapper/scripts/git_detective.py +194 -0
- package/templates/.agents/skills/nexus-mapper/scripts/languages.json +127 -0
- package/templates/.agents/skills/nexus-mapper/scripts/query_graph.py +556 -0
- package/templates/.agents/skills/nexus-mapper/scripts/requirements.txt +6 -0
- package/templates/{.agent → .agents}/skills/report-template/SKILL.md +11 -14
- package/templates/.agents/skills/report-template/references/REPORT_TEMPLATE.md +100 -0
- package/templates/{.agent → .agents}/skills/runtime-inspector/SKILL.md +1 -1
- package/templates/.agents/skills/sequential-thinking/SKILL.md +166 -0
- package/templates/.agents/skills/spec-writer/SKILL.md +108 -0
- package/templates/{.agent → .agents}/skills/spec-writer/references/prd_template.md +1 -1
- package/templates/{.agent → .agents}/skills/system-architect/SKILL.md +3 -3
- package/templates/.agents/skills/system-architect/references/rfc_template.md +59 -0
- package/templates/{.agent → .agents}/skills/system-designer/SKILL.md +6 -6
- package/templates/{.agent → .agents}/skills/system-designer/references/system-design-template.md +75 -25
- package/templates/{.agent → .agents}/skills/task-planner/SKILL.md +1 -1
- package/templates/.agents/skills/task-planner/references/TASK_TEMPLATE.md +144 -0
- package/templates/{.agent → .agents}/skills/task-reviewer/SKILL.md +4 -3
- package/templates/{.agent → .agents}/skills/tech-evaluator/SKILL.md +2 -2
- package/templates/{.agent → .agents}/skills/tech-evaluator/references/ADR_TEMPLATE.md +10 -0
- package/templates/{.agent → .agents}/workflows/blueprint.md +38 -33
- package/templates/{.agent → .agents}/workflows/challenge.md +21 -15
- package/templates/{.agent → .agents}/workflows/change.md +24 -15
- package/templates/{.agent → .agents}/workflows/craft.md +9 -20
- package/templates/{.agent → .agents}/workflows/design-system.md +83 -56
- package/templates/{.agent → .agents}/workflows/explore.md +6 -19
- package/templates/{.agent → .agents}/workflows/forge.md +36 -38
- package/templates/{.agent → .agents}/workflows/genesis.md +76 -64
- package/templates/.agents/workflows/probe.md +168 -0
- package/templates/{.agent → .agents}/workflows/quickstart.md +7 -12
- package/templates/.agents/workflows/upgrade.md +192 -0
- package/templates/AGENTS.md +134 -113
- package/templates/.agent/skills/build-inspector/SKILL.md +0 -83
- package/templates/.agent/skills/complexity-guard/SKILL.md +0 -71
- package/templates/.agent/skills/complexity-guard/references/anti_patterns.md +0 -21
- package/templates/.agent/skills/concept-modeler/SKILL.md +0 -112
- package/templates/.agent/skills/concept-modeler/prompts/GLOSSARY_PROMPT.md +0 -40
- package/templates/.agent/skills/concept-modeler/references/ENTITY_EXTRACTION_PROMPT.md +0 -299
- package/templates/.agent/skills/concept-modeler/scripts/glossary_gen.py +0 -66
- package/templates/.agent/skills/git-forensics/SKILL.md +0 -74
- package/templates/.agent/skills/git-forensics/references/ANALYSIS_METHODOLOGY.md +0 -193
- package/templates/.agent/skills/git-forensics/scripts/__pycache__/git_forensics.cpython-313.pyc +0 -0
- package/templates/.agent/skills/git-forensics/scripts/git_forensics.py +0 -615
- package/templates/.agent/skills/git-forensics/scripts/git_hotspots.py +0 -118
- package/templates/.agent/skills/report-template/references/REPORT_TEMPLATE.md +0 -100
- package/templates/.agent/skills/spec-writer/SKILL.md +0 -108
- package/templates/.agent/skills/system-architect/references/rfc_template.md +0 -59
- package/templates/.agent/skills/task-planner/references/TASK_TEMPLATE.md +0 -144
- package/templates/.agent/workflows/scout.md +0 -139
- /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
|
+
};
|
package/lib/changelog.js
ADDED
|
@@ -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
|
-
|
|
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 };
|