@haaaiawd/anws 2.0.5 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +11 -5
- package/lib/changelog.js +78 -7
- package/lib/diff.js +17 -0
- package/lib/update.js +21 -75
- package/package.json +1 -1
- package/templates/.agents/skills/design-reviewer/SKILL.md +22 -7
- package/templates/.agents/skills/system-designer/references/system-design-template.md +19 -2
- package/templates/.agents/skills/task-planner/SKILL.md +40 -2
- package/templates/.agents/skills/task-planner/references/TASK_TEMPLATE.md +68 -38
- package/templates/.agents/skills/task-reviewer/SKILL.md +46 -19
- package/templates/.agents/workflows/blueprint.md +38 -48
- package/templates/.agents/workflows/challenge.md +170 -149
- package/templates/.agents/workflows/change.md +52 -39
- package/templates/.agents/workflows/design-system.md +24 -0
- package/templates/.agents/workflows/explore.md +34 -0
- package/templates/.agents/workflows/forge.md +44 -20
- package/templates/.agents/workflows/genesis.md +108 -18
- package/templates/.agents/workflows/quickstart.md +46 -2
package/bin/cli.js
CHANGED
|
@@ -22,8 +22,7 @@ COMMANDS
|
|
|
22
22
|
OPTIONS
|
|
23
23
|
-v, --version Print version number
|
|
24
24
|
-h, --help Show this help message
|
|
25
|
-
--target Target AI IDE(s) for
|
|
26
|
-
--check Preview grouped update diffs without writing files or rebuilding install-lock state
|
|
25
|
+
--target Target AI IDE(s) for init, comma-separated (${TARGET_IDS.join(', ')})
|
|
27
26
|
|
|
28
27
|
SUPPORTED TARGETS
|
|
29
28
|
windsurf workflows + skills
|
|
@@ -40,8 +39,7 @@ SUPPORTED TARGETS
|
|
|
40
39
|
EXAMPLES
|
|
41
40
|
anws init # Choose target IDEs and install their managed workflow projections
|
|
42
41
|
anws init --target windsurf,codex,opencode
|
|
43
|
-
anws update #
|
|
44
|
-
anws update --check # Preview grouped changes per target without writing files
|
|
42
|
+
anws update # One-click update for all matched targets from install-lock, fallback scan, or drift repair
|
|
45
43
|
`.trimStart();
|
|
46
44
|
|
|
47
45
|
// ─── 参数解析 ─────────────────────────────────────────────────────────────────
|
|
@@ -90,7 +88,15 @@ async function main() {
|
|
|
90
88
|
break;
|
|
91
89
|
|
|
92
90
|
case 'update':
|
|
93
|
-
|
|
91
|
+
if (values.target !== undefined) {
|
|
92
|
+
error('`anws update --target` has been removed. Use `anws update` to update all matched targets.');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
if (values.check) {
|
|
96
|
+
error('`anws update --check` has been removed. Use `anws update` directly.');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
await require('../lib/update')();
|
|
94
100
|
break;
|
|
95
101
|
|
|
96
102
|
default:
|
package/lib/changelog.js
CHANGED
|
@@ -55,6 +55,61 @@ function compareSemver(a, b) {
|
|
|
55
55
|
return 0;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function normalizeText(text) {
|
|
59
|
+
return String(text || '').replace(/\r\n/g, '\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildMergedChangeKey(item) {
|
|
63
|
+
const canonicalPath = item.source || item.file;
|
|
64
|
+
const summaryKey = JSON.stringify(item.summary || []);
|
|
65
|
+
const contentKey = item.type === 'modified'
|
|
66
|
+
? summaryKey
|
|
67
|
+
: `${normalizeText(item.oldContent)}\n<<<ANWS_CHANGE_SPLIT>>>\n${normalizeText(item.newContent)}`;
|
|
68
|
+
|
|
69
|
+
return `${item.type}::${canonicalPath}::${item.resourceId || canonicalPath}::${contentKey}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function mergeChanges(changes) {
|
|
73
|
+
const grouped = new Map();
|
|
74
|
+
|
|
75
|
+
for (const item of changes) {
|
|
76
|
+
const canonicalPath = item.source || item.file;
|
|
77
|
+
const key = buildMergedChangeKey(item);
|
|
78
|
+
const affectedFile = {
|
|
79
|
+
file: item.file,
|
|
80
|
+
targetId: item.targetId || null,
|
|
81
|
+
targetLabel: item.targetLabel || null
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (!grouped.has(key)) {
|
|
85
|
+
grouped.set(key, {
|
|
86
|
+
...item,
|
|
87
|
+
canonicalPath,
|
|
88
|
+
affectedFiles: [affectedFile]
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const current = grouped.get(key);
|
|
94
|
+
current.affectedFiles.push(affectedFile);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.from(grouped.values()).map((item) => ({
|
|
98
|
+
...item,
|
|
99
|
+
affectedFiles: item.affectedFiles.sort((left, right) => left.file.localeCompare(right.file))
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function formatAffectedTargetList(item) {
|
|
104
|
+
const targets = Array.from(new Set(
|
|
105
|
+
item.affectedFiles
|
|
106
|
+
.filter((entry) => entry.targetId && entry.targetLabel)
|
|
107
|
+
.map((entry) => `${entry.targetLabel} (${entry.targetId})`)
|
|
108
|
+
));
|
|
109
|
+
|
|
110
|
+
return targets.length > 0 ? targets.join(', ') : '无';
|
|
111
|
+
}
|
|
112
|
+
|
|
58
113
|
function formatFileList(title, items) {
|
|
59
114
|
const lines = [`### ${title}`];
|
|
60
115
|
if (items.length === 0) {
|
|
@@ -63,7 +118,10 @@ function formatFileList(title, items) {
|
|
|
63
118
|
}
|
|
64
119
|
|
|
65
120
|
for (const item of items) {
|
|
66
|
-
lines.push(`- \`${item.
|
|
121
|
+
lines.push(`- \`${item.canonicalPath}\` — 影响 Targets: ${formatAffectedTargetList(item)}`);
|
|
122
|
+
for (const affectedFile of item.affectedFiles) {
|
|
123
|
+
lines.push(` - \`${affectedFile.file}\``);
|
|
124
|
+
}
|
|
67
125
|
}
|
|
68
126
|
|
|
69
127
|
return lines.join('\n');
|
|
@@ -72,7 +130,10 @@ function formatFileList(title, items) {
|
|
|
72
130
|
function formatDetail(item) {
|
|
73
131
|
if (item.type === 'added') {
|
|
74
132
|
return [
|
|
75
|
-
`### \`${item.
|
|
133
|
+
`### \`${item.canonicalPath}\``,
|
|
134
|
+
`- **影响 Targets**: ${formatAffectedTargetList(item)}`,
|
|
135
|
+
'- **影响文件**:',
|
|
136
|
+
...item.affectedFiles.map((entry) => ` - \`${entry.file}\``),
|
|
76
137
|
'- **新增文件**',
|
|
77
138
|
'- **说明**: 该文件在旧版本中不存在,因此无前后逐行对比。'
|
|
78
139
|
].join('\n');
|
|
@@ -80,7 +141,10 @@ function formatDetail(item) {
|
|
|
80
141
|
|
|
81
142
|
if (item.type === 'deleted') {
|
|
82
143
|
return [
|
|
83
|
-
`### \`${item.
|
|
144
|
+
`### \`${item.canonicalPath}\``,
|
|
145
|
+
`- **影响 Targets**: ${formatAffectedTargetList(item)}`,
|
|
146
|
+
'- **影响文件**:',
|
|
147
|
+
...item.affectedFiles.map((entry) => ` - \`${entry.file}\``),
|
|
84
148
|
'- **删除文件**',
|
|
85
149
|
'- **说明**: 该文件在新版本中不存在,因此无前后逐行对比。'
|
|
86
150
|
].join('\n');
|
|
@@ -88,13 +152,19 @@ function formatDetail(item) {
|
|
|
88
152
|
|
|
89
153
|
if (item.summary.length === 0) {
|
|
90
154
|
return [
|
|
91
|
-
`### \`${item.
|
|
155
|
+
`### \`${item.canonicalPath}\``,
|
|
156
|
+
`- **影响 Targets**: ${formatAffectedTargetList(item)}`,
|
|
157
|
+
'- **影响文件**:',
|
|
158
|
+
...item.affectedFiles.map((entry) => ` - \`${entry.file}\``),
|
|
92
159
|
'- **说明**: 检测到内容变更,但未能提取到摘要。'
|
|
93
160
|
].join('\n');
|
|
94
161
|
}
|
|
95
162
|
|
|
96
163
|
return [
|
|
97
|
-
`### \`${item.
|
|
164
|
+
`### \`${item.canonicalPath}\``,
|
|
165
|
+
`- **影响 Targets**: ${formatAffectedTargetList(item)}`,
|
|
166
|
+
'- **影响文件**:',
|
|
167
|
+
...item.affectedFiles.map((entry) => ` - \`${entry.file}\``),
|
|
98
168
|
'```diff',
|
|
99
169
|
...item.summary.flatMap((pair) => [
|
|
100
170
|
`- [old:${pair.oldLineNumber === null ? '-' : pair.oldLineNumber}] ${pair.oldText}`,
|
|
@@ -106,7 +176,8 @@ function formatDetail(item) {
|
|
|
106
176
|
|
|
107
177
|
async function generateChangelog({ cwd, version, changes, targetSummary = null }) {
|
|
108
178
|
const changelogDir = await ensureChangelogDir(cwd);
|
|
109
|
-
const
|
|
179
|
+
const mergedChanges = mergeChanges(changes);
|
|
180
|
+
const grouped = groupChanges(mergedChanges);
|
|
110
181
|
const now = new Date();
|
|
111
182
|
const timestamp = now.toISOString().replace('T', ' ').slice(0, 19);
|
|
112
183
|
const filePath = path.join(changelogDir, `v${version}.md`);
|
|
@@ -143,7 +214,7 @@ async function generateChangelog({ cwd, version, changes, targetSummary = null }
|
|
|
143
214
|
'',
|
|
144
215
|
'## 内容级变更详情',
|
|
145
216
|
'',
|
|
146
|
-
...(
|
|
217
|
+
...(mergedChanges.length > 0 ? mergedChanges.map(formatDetail).flatMap((section) => [section, '']) : ['- 无变更', ''])
|
|
147
218
|
].join('\n');
|
|
148
219
|
|
|
149
220
|
await fs.writeFile(filePath, content, 'utf8');
|
package/lib/diff.js
CHANGED
|
@@ -137,6 +137,7 @@ async function collectManagedFileDiffs({
|
|
|
137
137
|
? managedFiles
|
|
138
138
|
: projectionPlan.flatMap((item) => item.managedFiles || []);
|
|
139
139
|
const projectionMap = new Map(normalizedProjectionEntries.map((item) => [item.outputPath, item]));
|
|
140
|
+
const fallbackTarget = projectionPlan[0] || null;
|
|
140
141
|
|
|
141
142
|
for (const rel of normalizedManagedFiles) {
|
|
142
143
|
if (rel === 'AGENTS.md' && !shouldWriteRootAgents) {
|
|
@@ -144,6 +145,19 @@ async function collectManagedFileDiffs({
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
const entry = projectionMap.get(rel);
|
|
148
|
+
const metadata = entry
|
|
149
|
+
? {
|
|
150
|
+
source: entry.source,
|
|
151
|
+
resourceId: entry.id,
|
|
152
|
+
targetId: entry.targetId,
|
|
153
|
+
targetLabel: entry.targetLabel
|
|
154
|
+
}
|
|
155
|
+
: {
|
|
156
|
+
source: rel,
|
|
157
|
+
resourceId: rel === 'AGENTS.md' ? 'root-agents' : rel,
|
|
158
|
+
targetId: fallbackTarget?.targetId || null,
|
|
159
|
+
targetLabel: fallbackTarget?.targetLabel || null
|
|
160
|
+
};
|
|
147
161
|
const srcPath = rel === 'AGENTS.md'
|
|
148
162
|
? srcAgents
|
|
149
163
|
: path.join(path.join(__dirname, '..', 'templates'), entry.source);
|
|
@@ -160,6 +174,7 @@ async function collectManagedFileDiffs({
|
|
|
160
174
|
if (srcExists && !destExists) {
|
|
161
175
|
results.push({
|
|
162
176
|
file: rel,
|
|
177
|
+
...metadata,
|
|
163
178
|
type: 'added',
|
|
164
179
|
summary: [],
|
|
165
180
|
oldContent: '',
|
|
@@ -171,6 +186,7 @@ async function collectManagedFileDiffs({
|
|
|
171
186
|
if (!srcExists && destExists) {
|
|
172
187
|
results.push({
|
|
173
188
|
file: rel,
|
|
189
|
+
...metadata,
|
|
174
190
|
type: 'deleted',
|
|
175
191
|
summary: [],
|
|
176
192
|
oldContent: await readTextOrEmpty(destPath),
|
|
@@ -193,6 +209,7 @@ async function collectManagedFileDiffs({
|
|
|
193
209
|
|
|
194
210
|
results.push({
|
|
195
211
|
file: rel,
|
|
212
|
+
...metadata,
|
|
196
213
|
type: 'modified',
|
|
197
214
|
summary: createLineDiff(oldContent, newContent),
|
|
198
215
|
oldContent,
|
package/lib/update.js
CHANGED
|
@@ -5,32 +5,33 @@ const path = require('node:path');
|
|
|
5
5
|
const { buildProjectionPlan } = require('./manifest');
|
|
6
6
|
const { getTarget } = require('./adapters');
|
|
7
7
|
const { planAgentsUpdate, resolveAgentsInstall, printLegacyMigrationWarning, pathExists } = require('./agents');
|
|
8
|
-
const { collectManagedFileDiffs
|
|
8
|
+
const { collectManagedFileDiffs } = require('./diff');
|
|
9
9
|
const { detectUpgrade, generateChangelog } = require('./changelog');
|
|
10
10
|
const { writeTargetFiles } = require('./copy');
|
|
11
11
|
const { createInstallLock, dedupeTargets, detectInstallState, summarizeTargetState, writeInstallLock } = require('./install-state');
|
|
12
12
|
const { confirm } = require('./prompt');
|
|
13
13
|
const { ROOT_AGENTS_FILE, resolveCanonicalSource } = require('./resources');
|
|
14
|
-
const {
|
|
14
|
+
const { warn, error, info, fileLine, skippedLine, blank, logo, section } = require('./output');
|
|
15
15
|
|
|
16
|
-
async function update(
|
|
16
|
+
async function update() {
|
|
17
17
|
const cwd = process.cwd();
|
|
18
|
-
const check = !!options.check;
|
|
19
18
|
const legacyAgentDir = path.join(cwd, '.agent');
|
|
20
19
|
const { version } = require(path.join(__dirname, '..', 'package.json'));
|
|
21
20
|
const installState = await detectInstallState(cwd);
|
|
22
21
|
const legacyAgentExists = await pathExists(legacyAgentDir);
|
|
23
22
|
const isLegacyMigration = installState.selectedTargets.length === 0 && legacyAgentExists;
|
|
24
|
-
const
|
|
25
|
-
const targetPlans = buildProjectionPlan(selectedTargetIds);
|
|
23
|
+
const detectedTargetIds = isLegacyMigration ? ['antigravity'] : installState.selectedTargets;
|
|
26
24
|
|
|
27
|
-
if (
|
|
25
|
+
if (detectedTargetIds.length === 0 && !legacyAgentExists) {
|
|
28
26
|
logo();
|
|
29
27
|
error('No supported Anws target layout found in current directory.');
|
|
30
28
|
info('Run `anws init` first to set up the workflow system.');
|
|
31
29
|
process.exit(1);
|
|
32
30
|
}
|
|
33
31
|
|
|
32
|
+
const targetPlans = buildProjectionPlan(detectedTargetIds);
|
|
33
|
+
const detectedTargetPlans = buildProjectionPlan(detectedTargetIds);
|
|
34
|
+
|
|
34
35
|
const srcAgents = ROOT_AGENTS_FILE;
|
|
35
36
|
|
|
36
37
|
if (isLegacyMigration) {
|
|
@@ -100,41 +101,18 @@ async function update(options = {}) {
|
|
|
100
101
|
|
|
101
102
|
const changes = targetContexts.flatMap((context) => context.changes);
|
|
102
103
|
|
|
103
|
-
if (check) {
|
|
104
|
-
if (!versionState.needUpgrade) {
|
|
105
|
-
if (!isLegacyMigration) {
|
|
106
|
-
logo();
|
|
107
|
-
blank();
|
|
108
|
-
}
|
|
109
|
-
info(`Already up to date. Latest recorded version is v${versionState.latestVersion || version}.`);
|
|
110
|
-
printTargetSelection(installState, targetContexts.map((context) => context.target));
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (!isLegacyMigration) {
|
|
114
|
-
logo();
|
|
115
|
-
blank();
|
|
116
|
-
}
|
|
117
|
-
printTargetSelection(installState, targetContexts.map((context) => context.target));
|
|
118
|
-
printPreview({
|
|
119
|
-
fromVersion: versionState.fromVersion,
|
|
120
|
-
toVersion: versionState.toVersion,
|
|
121
|
-
changes
|
|
122
|
-
});
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
104
|
if (!versionState.needUpgrade) {
|
|
127
105
|
if (!isLegacyMigration) {
|
|
128
106
|
logo();
|
|
129
107
|
blank();
|
|
130
108
|
}
|
|
131
109
|
printTargetSelection(installState, targetContexts.map((context) => context.target));
|
|
132
|
-
if (
|
|
110
|
+
if (installState.canRebuildLock && detectedTargetIds.length > 0) {
|
|
133
111
|
const generatedAt = new Date().toISOString();
|
|
134
112
|
await writeInstallLock(cwd, createInstallLock({
|
|
135
113
|
cliVersion: version,
|
|
136
114
|
generatedAt,
|
|
137
|
-
targets: dedupeTargets(
|
|
115
|
+
targets: dedupeTargets(detectedTargetPlans.map((targetPlan) => summarizeTargetState(targetPlan, version))),
|
|
138
116
|
lastUpdateSummary: {
|
|
139
117
|
successfulTargets: [],
|
|
140
118
|
failedTargets: [],
|
|
@@ -152,15 +130,7 @@ async function update(options = {}) {
|
|
|
152
130
|
blank();
|
|
153
131
|
}
|
|
154
132
|
|
|
155
|
-
|
|
156
|
-
installState,
|
|
157
|
-
targets: targetContexts.map((context) => context.target)
|
|
158
|
-
});
|
|
159
|
-
if (!confirmed) {
|
|
160
|
-
blank();
|
|
161
|
-
info('Aborted. No files were changed.');
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
133
|
+
printTargetSelection(installState, targetContexts.map((context) => context.target));
|
|
164
134
|
|
|
165
135
|
const updated = [];
|
|
166
136
|
const skipped = [];
|
|
@@ -217,6 +187,12 @@ async function update(options = {}) {
|
|
|
217
187
|
}
|
|
218
188
|
});
|
|
219
189
|
const generatedAt = new Date().toISOString();
|
|
190
|
+
const successfulTargetIdSet = new Set(successfulTargets.map((item) => item.targetId));
|
|
191
|
+
const retainedDetectedTargets = installState.canRebuildLock
|
|
192
|
+
? detectedTargetPlans
|
|
193
|
+
.filter((targetPlan) => !successfulTargetIdSet.has(targetPlan.targetId))
|
|
194
|
+
.map((targetPlan) => summarizeTargetState(targetPlan, version))
|
|
195
|
+
: [];
|
|
220
196
|
const existingLockTargets = installState.canRebuildLock
|
|
221
197
|
? []
|
|
222
198
|
: (installState.lockResult.lock?.targets || []);
|
|
@@ -225,6 +201,7 @@ async function update(options = {}) {
|
|
|
225
201
|
generatedAt,
|
|
226
202
|
targets: dedupeTargets([
|
|
227
203
|
...existingLockTargets,
|
|
204
|
+
...retainedDetectedTargets,
|
|
228
205
|
...successfulTargets
|
|
229
206
|
]),
|
|
230
207
|
lastUpdateSummary: {
|
|
@@ -251,23 +228,6 @@ async function update(options = {}) {
|
|
|
251
228
|
});
|
|
252
229
|
}
|
|
253
230
|
|
|
254
|
-
async function askUpdate({ installState, targets }) {
|
|
255
|
-
if (global.__ANWS_FORCE_YES) return true;
|
|
256
|
-
|
|
257
|
-
if (!process.stdin.isTTY) {
|
|
258
|
-
warn('Non-TTY environment detected. Skipping update to avoid accidental overwrites.');
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return confirm({
|
|
263
|
-
message: buildUpdateConfirmationMessage(targets),
|
|
264
|
-
contextLines: buildUpdateConfirmationContextLines(installState, targets),
|
|
265
|
-
confirmLabel: 'Continue',
|
|
266
|
-
cancelLabel: 'Cancel',
|
|
267
|
-
defaultValue: false
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
231
|
async function askMigrate() {
|
|
272
232
|
if (global.__ANWS_FORCE_YES) return true;
|
|
273
233
|
|
|
@@ -308,23 +268,8 @@ async function maybeDeleteLegacyDir(legacyAgentDir) {
|
|
|
308
268
|
return true;
|
|
309
269
|
}
|
|
310
270
|
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
if (labels.length === 0) {
|
|
314
|
-
return 'This will overwrite all managed files for the detected target layout.';
|
|
315
|
-
}
|
|
316
|
-
return `This will overwrite all managed files for: ${labels.join(', ')}.`;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function buildUpdateConfirmationContextLines(installState, targets) {
|
|
320
|
-
const lines = [
|
|
321
|
-
`Matched targets: ${targets.map((target) => `${target.label} (${target.id})`).join(', ') || 'none'}`,
|
|
322
|
-
installState.needsFallback ? 'State source: directory scan fallback' : 'State source: install-lock + directory scan'
|
|
323
|
-
];
|
|
324
|
-
if (installState.drift.hasDrift) {
|
|
325
|
-
lines.push(`State drift detected. Missing on disk: ${installState.drift.missingOnDisk.join(', ') || 'none'}; untracked on disk: ${installState.drift.untrackedOnDisk.join(', ') || 'none'}.`);
|
|
326
|
-
}
|
|
327
|
-
return lines;
|
|
271
|
+
function buildSelectionModeLine() {
|
|
272
|
+
return 'Selection mode: detected target layout';
|
|
328
273
|
}
|
|
329
274
|
|
|
330
275
|
function printLegacyMigrationNotice() {
|
|
@@ -338,6 +283,7 @@ function printLegacyMigrationNotice() {
|
|
|
338
283
|
function printTargetSelection(installState, targets) {
|
|
339
284
|
blank();
|
|
340
285
|
section('Target selection', [
|
|
286
|
+
buildSelectionModeLine(),
|
|
341
287
|
`Matched targets: ${targets.map((target) => `${target.label} (${target.id})`).join(', ') || 'none'}`,
|
|
342
288
|
installState.needsFallback ? 'State source: directory scan fallback' : 'State source: install-lock + directory scan',
|
|
343
289
|
...(installState.drift.hasDrift
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haaaiawd/anws",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Anws — A spec-driven workflow framework for AI-assisted development. Empowers prompt engineers to build production-ready software through structured PRD → Architecture → Task decomposition. Works with Claude Code, GitHub Copilot, Cursor, Windsurf, and any tool that reads AGENTS.md.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anws",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design-reviewer
|
|
3
|
-
description:
|
|
3
|
+
description: 使用三维框架(系统设计、运行模拟、工程实现)系统性审查架构和系统设计文档,作为 challenge 工作流中的规范契约设计证据层。产出按严重度分级的发现,关联到具体文档段落。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 设计审查大师手册
|
|
@@ -9,6 +9,8 @@ description: 使用三维框架(系统设计、运行模拟、工程实现)
|
|
|
9
9
|
> 对设计严厉,代码才能优雅。"
|
|
10
10
|
|
|
11
11
|
你是**设计审查大师**,负责系统性审查架构和系统设计文档。你的三维框架确保没有任何一类风险被遗漏。
|
|
12
|
+
在 `/challenge` 工作流中,你的角色是:**为规范契约是否闭合提供设计侧证据**,而不是单独给出脱离上下文的最终裁决。
|
|
13
|
+
你优先要证明的是:哪些契约在**系统边界、接口、状态、时序、错误路径**上没有闭合。
|
|
12
14
|
|
|
13
15
|
---
|
|
14
16
|
|
|
@@ -126,15 +128,28 @@ description: 使用三维框架(系统设计、运行模拟、工程实现)
|
|
|
126
128
|
| 工程实现 | — | — | — | — | — |
|
|
127
129
|
| **合计** | **—** | **—** | **—** | **—** | **—** |
|
|
128
130
|
|
|
131
|
+
**高信号结论**: [用 1-3 句概括最值得进入 challenge 主报告的问题]
|
|
132
|
+
|
|
129
133
|
---
|
|
130
134
|
|
|
131
|
-
###
|
|
135
|
+
### 核心发现清单
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
| ID | 维度 | 严重度 | 文档位置 | 发现 | 影响 | 建议 |
|
|
138
|
+
|----|------|--------|----------|------|------|------|
|
|
139
|
+
| DR-01 | 系统设计 | Critical | 02_ARCHITECTURE_OVERVIEW.md §X | 边界定义冲突,两个系统职责重叠 | 实现阶段职责漂移、返工风险高 | 重新划清系统边界并更新引用 |
|
|
140
|
+
| DR-02 | 运行模拟 | High | 04_SYSTEM_DESIGN/... §Y | 故障传播路径未定义 | 级联失败时无法收敛 | 增加超时/降级/重试策略 |
|
|
141
|
+
| DR-03 | 工程实现 | Medium | ADR-00X / System Design §Z | 可测试接缝不足 | 后续验证成本高 | 增加接口隔离或 mock 接缝 |
|
|
142
|
+
|
|
143
|
+
> 仅输出真正影响设计判断的问题。低价值措辞、重复担忧不要进入清单。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### Top Findings 详情(仅展开 Critical / High)
|
|
135
148
|
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
#### DR-01 [标题]
|
|
150
|
+
|
|
151
|
+
**严重度**: Critical
|
|
152
|
+
**文档位置**: [精确的文件和章节引用]
|
|
138
153
|
|
|
139
154
|
**证据**:
|
|
140
155
|
- 文档分析: [来自 PRD/Architecture/ADR 的具体内容]
|
|
@@ -145,7 +160,7 @@ description: 使用三维框架(系统设计、运行模拟、工程实现)
|
|
|
145
160
|
- [不修复会发生什么]
|
|
146
161
|
|
|
147
162
|
**建议**:
|
|
148
|
-
[
|
|
163
|
+
- [最小修复方向]
|
|
149
164
|
```
|
|
150
165
|
|
|
151
166
|
---
|
|
@@ -537,14 +537,31 @@ classDiagram
|
|
|
537
537
|
- **RBAC (Role-Based Access Control)**: 基于角色的访问控制
|
|
538
538
|
- **p95**: 95th percentile,95%的请求响应时间小于该值
|
|
539
539
|
|
|
540
|
-
### 14.2
|
|
540
|
+
### 14.2 Optional Skills & Reference Resources (可选 Skills 与参考资源)
|
|
541
|
+
>
|
|
542
|
+
> 本节用于记录在设计过程中实际参考过的 skill、组件库、方法论或外部资料。
|
|
543
|
+
> 这些内容是辅助输入,不是系统事实来源;最终方案仍以本项目的 PRD、ADR、Architecture Overview 和本文档自身为准。
|
|
544
|
+
>
|
|
545
|
+
> **记录建议**:
|
|
546
|
+
> - 写明资源名称
|
|
547
|
+
> - 写明它帮助了哪个设计决策
|
|
548
|
+
> - 写明最终采纳了什么,舍弃了什么
|
|
549
|
+
>
|
|
550
|
+
> **示例(前端系统)**:
|
|
551
|
+
> - `vercel-react-best-practices`: 用于校验 React 组件边界、渲染策略、性能优化建议
|
|
552
|
+
> - `frontend-design`: 用于参考排版、配色、层级和动效方向
|
|
553
|
+
> - `shadcn/ui`: 用于基础组件模式参考
|
|
554
|
+
> - `Aceternity UI`: 用于展示型区块和交互动效灵感
|
|
555
|
+
> - `Magic UI`: 用于 Tailwind-first 的视觉与动画参考
|
|
556
|
+
|
|
557
|
+
### 14.3 References (参考资料)
|
|
541
558
|
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
|
542
559
|
- [PostgreSQL Best Practices](https://wiki.postgresql.org/wiki/Don%27t_Do_This)
|
|
543
560
|
- [JWT Best Practices](https://tools.ietf.org/html/rfc8725)
|
|
544
561
|
- [Architecture Overview](../02_ARCHITECTURE_OVERVIEW.md)
|
|
545
562
|
- [ADR001: Tech Stack](../03_ADR/ADR001_TECH_STACK.md)
|
|
546
563
|
|
|
547
|
-
### 14.
|
|
564
|
+
### 14.4 Change Log (变更日志)
|
|
548
565
|
|
|
549
566
|
| Version | Date | Changes | Author |
|
|
550
567
|
| ------- | ---------- | -------- | ------ |
|
|
@@ -18,8 +18,10 @@ description: 使用WBS方法将系统设计文档分解为层次化任务。支
|
|
|
18
18
|
2. **加载必需文档**:读取 Architecture Overview + PRD
|
|
19
19
|
3. **加载可选文档**:扫描 ADR 目录 + System Design 目录
|
|
20
20
|
4. **缺失检查**:必需文档缺失则报错退出
|
|
21
|
-
5.
|
|
22
|
-
6.
|
|
21
|
+
5. **加载测试约束**:如果 Workflow 或 ADR 提供测试策略、质量门禁、Sprint 边界,必须一并纳入任务生成输入
|
|
22
|
+
6. **执行拆解**:按 WBS 方法拆解任务
|
|
23
|
+
7. **应用验证类型选择逻辑**:为每个任务分配“最轻但足够”的验证类型,避免默认升级为 E2E
|
|
24
|
+
8. **输出**:保存到 `.anws/v{N}/05_TASKS.md`
|
|
23
25
|
|
|
24
26
|
---
|
|
25
27
|
|
|
@@ -33,6 +35,13 @@ description: 使用WBS方法将系统设计文档分解为层次化任务。支
|
|
|
33
35
|
> 3. **可验证** - 每个Task有明确的Done When标准
|
|
34
36
|
> 4. **可追溯** - 每个Task关联PRD需求 [REQ-XXX]
|
|
35
37
|
|
|
38
|
+
> [!IMPORTANT]
|
|
39
|
+
> **测试规划附加原则**:
|
|
40
|
+
> - 优先选择**最轻但足够**的验证类型
|
|
41
|
+
> - 如 Workflow / ADR 已声明测试策略,必须优先遵循,不得自行改重
|
|
42
|
+
> - **冒烟测试默认仅用于 `INT-S{N}` 或极少数里程碑任务**
|
|
43
|
+
> - **回归测试仅在已有关键能力可能被破坏时生成**,不是所有任务的默认要求
|
|
44
|
+
|
|
36
45
|
❌ **错误做法**:
|
|
37
46
|
- 平铺任务列表(无层次)
|
|
38
47
|
- 任务过大(如"实现整个后端")
|
|
@@ -114,6 +123,7 @@ description: 使用WBS方法将系统设计文档分解为层次化任务。支
|
|
|
114
123
|
- **验收标准**:
|
|
115
124
|
- [ ] Done When 1
|
|
116
125
|
- [ ] Done When 2
|
|
126
|
+
- **验证类型**: 单元测试 | 集成测试 | E2E测试 | 冒烟测试 | 回归测试 | 手动验证 | 编译检查 | Lint检查
|
|
117
127
|
- **验证说明**: 如何确认任务完成 (检查什么,如何确认)
|
|
118
128
|
- **估时**: 预估工时(如: 2h, 1d, 1w)
|
|
119
129
|
- **依赖**: T{X}.{Y}.{Z} (依赖的Task ID)
|
|
@@ -130,11 +140,39 @@ description: 使用WBS方法将系统设计文档分解为层次化任务。支
|
|
|
130
140
|
- [ ] `npm run dev` 正常启动
|
|
131
141
|
- [ ] 页面显示"Hello World"
|
|
132
142
|
- [ ] TypeScript类型检查通过
|
|
143
|
+
- **验证类型**: 编译检查
|
|
133
144
|
- **估时**: 2h
|
|
134
145
|
- **依赖**: 无
|
|
135
146
|
- **优先级**: P0
|
|
136
147
|
```
|
|
137
148
|
|
|
149
|
+
### 验证类型选择逻辑
|
|
150
|
+
|
|
151
|
+
> [!IMPORTANT]
|
|
152
|
+
> **如果 Workflow 未给出更具体约束,按以下默认顺序决策:**
|
|
153
|
+
|
|
154
|
+
1. **局部逻辑 / 纯算法 / 数据变换** → 单元测试
|
|
155
|
+
2. **跨模块 / 接口 / 数据库 / 多服务协作** → 集成测试
|
|
156
|
+
3. **直接面向终端用户的关键路径** → E2E测试 或 手动验证
|
|
157
|
+
4. **Sprint 退出标准 / 里程碑 gate** → 冒烟测试
|
|
158
|
+
5. **修改可能影响已完成关键能力** → 回归测试
|
|
159
|
+
6. **配置、脚手架、基础设施** → 编译检查 / Lint检查 / 手动验证
|
|
160
|
+
|
|
161
|
+
**选择细则**:
|
|
162
|
+
- 不要因为任务“看起来重要”就默认选择 E2E测试
|
|
163
|
+
- 如果集成测试足以证明任务完成,就不要升级为 E2E测试
|
|
164
|
+
- 如果只是里程碑 readiness 检查,优先使用少量冒烟测试,而不是新建大量 E2E任务
|
|
165
|
+
- 如果只是验证旧能力未被破坏,优先复用已有测试集合作为回归测试
|
|
166
|
+
|
|
167
|
+
### Sprint 与冒烟测试绑定规则
|
|
168
|
+
|
|
169
|
+
> [!IMPORTANT]
|
|
170
|
+
> **只有在 Workflow 已提供 Sprint 路线图 / INT 任务语义时,才应生成里程碑级冒烟测试。**
|
|
171
|
+
|
|
172
|
+
- 如果上游 Workflow 已定义 `INT-S{N}`,则将冒烟测试优先绑定到这些 INT 任务
|
|
173
|
+
- 不要为每个普通 Level 3 开发任务单独生成冒烟测试
|
|
174
|
+
- 若没有明确 Sprint / 里程碑边界,则优先退回单元测试、集成测试、手动验证,而不是滥造冒烟任务
|
|
175
|
+
|
|
138
176
|
### 接口追溯规则
|
|
139
177
|
|
|
140
178
|
> [!IMPORTANT]
|