@ecc-hgy/ae 0.5.0 → 0.6.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 (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -155
  3. package/bin/ae.js +2 -2
  4. package/package.json +43 -43
  5. package/skills/brainstorming/SKILL.md +133 -133
  6. package/skills/diagnose/SKILL.md +146 -146
  7. package/skills/diagnose/assets/issue-7-sections.md +35 -35
  8. package/skills/diagnose/scripts/hitl-loop.template.sh +41 -41
  9. package/skills/grill-me/SKILL.md +10 -10
  10. package/skills/handoff/SKILL.md +19 -19
  11. package/skills/improve-codebase-architecture/DEEPENING.md +37 -37
  12. package/skills/improve-codebase-architecture/HTML-REPORT.md +123 -123
  13. package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -44
  14. package/skills/improve-codebase-architecture/LANGUAGE.md +53 -53
  15. package/skills/improve-codebase-architecture/SKILL.md +101 -101
  16. package/skills/karpathy-guidelines/SKILL.md +63 -63
  17. package/skills/powerautomate-email-to-sharepoint-excel/powerautomate-email-to-sharepoint-excel-skill.md +496 -496
  18. package/skills/review/SKILL.md +119 -119
  19. package/skills/tdd/SKILL.md +157 -157
  20. package/skills/tdd/deep-modules.md +33 -33
  21. package/skills/tdd/interface-design.md +31 -31
  22. package/skills/tdd/mocking.md +59 -59
  23. package/skills/tdd/refactoring.md +10 -10
  24. package/skills/tdd/tests.md +61 -61
  25. package/skills/to-issues/SKILL.md +79 -79
  26. package/skills/to-issues/todo-template.md +25 -25
  27. package/skills/to-prd/SKILL.md +108 -108
  28. package/skills/using-agentic-engineering/SKILL.md +62 -62
  29. package/skills/verification-before-completion/SKILL.md +153 -153
  30. package/skills/writing-plans/SKILL.md +115 -115
  31. package/skills/zoom-out/SKILL.md +7 -7
  32. package/src/cli.js +61 -61
  33. package/src/commands/init.js +137 -137
  34. package/src/commands/setup.js +162 -162
  35. package/src/platforms.js +132 -132
  36. package/src/skeleton.js +134 -99
  37. package/src/utils/copy.js +100 -100
  38. package/src/utils/paths.js +60 -60
  39. package/src/utils/report.js +30 -30
  40. package/templates/entries/AGENTS.md +2 -0
  41. package/templates/entries/CLAUDE.md +5 -5
  42. package/templates/entries/README.md +33 -31
  43. package/templates/entries/handoff.md +1 -1
  44. package/templates/entries/spec/ADR/AGENTS.md +30 -30
  45. package/templates/entries/spec/ADR/CLAUDE.md +5 -5
  46. package/templates/entries/spec/AGENTS.md +34 -34
  47. package/templates/entries/spec/CLAUDE.md +5 -5
  48. package/templates/entries/spec/INDEX.md +28 -28
  49. package/templates/entries/spec/README.md +23 -23
package/src/skeleton.js CHANGED
@@ -1,129 +1,164 @@
1
- import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
2
- import path from 'node:path';
3
-
4
- const DEFAULT_DRY_RUN = false;
5
-
6
- const SKELETON_DIRS = [
1
+ import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ const DEFAULT_DRY_RUN = false;
5
+
6
+ const CORE_DIRS = [
7
7
  'spec',
8
8
  path.join('spec', 'needs'),
9
9
  path.join('spec', 'ADR'),
10
- 'reference',
10
+ 'docs',
11
11
  'data',
12
- 'scripts',
13
12
  ];
14
13
 
15
- const GITKEEP_DIRS = [
14
+ const CORE_GITKEEP_DIRS = [
16
15
  path.join('spec', 'needs'),
17
16
  path.join('spec', 'ADR'),
18
- 'reference',
17
+ 'docs',
19
18
  'data',
20
- 'scripts',
21
- ];
22
-
23
- const ENTRY_FILES = [
24
- 'README.md',
25
- 'AGENTS.md',
26
- 'CLAUDE.md',
27
- 'handoff.md',
28
- path.join('spec', 'README.md'),
29
- path.join('spec', 'INDEX.md'),
30
- path.join('spec', 'AGENTS.md'),
31
- path.join('spec', 'CLAUDE.md'),
32
- path.join('spec', 'ADR', 'AGENTS.md'),
33
- path.join('spec', 'ADR', 'CLAUDE.md'),
34
19
  ];
35
20
 
21
+ const ALTERNATE_SOURCE_ROOTS = ['app', 'apps', 'lib', 'packages'];
22
+ const ALTERNATE_TEST_ROOTS = ['test', '__tests__'];
23
+
24
+ const ENTRY_FILES = [
25
+ 'README.md',
26
+ 'AGENTS.md',
27
+ 'CLAUDE.md',
28
+ 'handoff.md',
29
+ path.join('spec', 'README.md'),
30
+ path.join('spec', 'INDEX.md'),
31
+ path.join('spec', 'AGENTS.md'),
32
+ path.join('spec', 'CLAUDE.md'),
33
+ path.join('spec', 'ADR', 'AGENTS.md'),
34
+ path.join('spec', 'ADR', 'CLAUDE.md'),
35
+ ];
36
+
36
37
  export async function createDirectories(target, options = {}) {
37
38
  const dryRun = Boolean(options.dryRun ?? DEFAULT_DRY_RUN);
38
39
  const created = [];
39
40
  const skipped = [];
40
41
  const updated = [];
42
+ const skeletonDirs = [...CORE_DIRS];
43
+ const gitkeepDirs = [...CORE_GITKEEP_DIRS];
41
44
 
42
- for (const dir of SKELETON_DIRS) {
43
- const absolute = path.join(target, dir);
44
- const display = `${normalizeRelative(dir)}/`;
45
-
46
- if (await exists(absolute)) {
47
- const current = await stat(absolute);
48
- if (!current.isDirectory()) {
49
- throw new Error(`expected directory but found file: ${absolute}`);
50
- }
51
- skipped.push(display);
52
- } else {
53
- created.push(display);
54
- if (!dryRun) {
55
- await mkdir(absolute, { recursive: true });
56
- }
57
- }
45
+ if (await shouldIncludeDefaultRoot(target, 'src', ALTERNATE_SOURCE_ROOTS)) {
46
+ skeletonDirs.push('src');
47
+ gitkeepDirs.push('src');
58
48
  }
59
49
 
60
- for (const dir of GITKEEP_DIRS) {
61
- const file = path.join(target, dir, '.gitkeep');
62
- const display = normalizeRelative(path.join(dir, '.gitkeep'));
63
-
64
- if (await exists(file)) {
65
- skipped.push(display);
66
- } else {
67
- created.push(display);
68
- if (!dryRun) {
69
- await mkdir(path.dirname(file), { recursive: true });
70
- await writeFile(file, '');
71
- }
72
- }
50
+ if (await shouldIncludeDefaultRoot(target, 'tests', ALTERNATE_TEST_ROOTS)) {
51
+ skeletonDirs.push('tests');
52
+ gitkeepDirs.push('tests');
73
53
  }
74
54
 
75
- return { created, skipped, updated };
55
+ for (const dir of skeletonDirs) {
56
+ const absolute = path.join(target, dir);
57
+ const display = `${normalizeRelative(dir)}/`;
58
+
59
+ if (await exists(absolute)) {
60
+ const current = await stat(absolute);
61
+ if (!current.isDirectory()) {
62
+ throw new Error(`expected directory but found file: ${absolute}`);
63
+ }
64
+ skipped.push(display);
65
+ } else {
66
+ created.push(display);
67
+ if (!dryRun) {
68
+ await mkdir(absolute, { recursive: true });
69
+ }
70
+ }
71
+ }
72
+
73
+ for (const dir of gitkeepDirs) {
74
+ const file = path.join(target, dir, '.gitkeep');
75
+ const display = normalizeRelative(path.join(dir, '.gitkeep'));
76
+
77
+ if (await exists(file)) {
78
+ skipped.push(display);
79
+ } else {
80
+ created.push(display);
81
+ if (!dryRun) {
82
+ await mkdir(path.dirname(file), { recursive: true });
83
+ await writeFile(file, '');
84
+ }
85
+ }
86
+ }
87
+
88
+ return { created, skipped, updated };
89
+ }
90
+
91
+ export async function renderEntryFiles(target, templateRoot, options = {}) {
92
+ const dryRun = Boolean(options.dryRun ?? DEFAULT_DRY_RUN);
93
+ const projectName = options.projectName ?? path.basename(target);
94
+ const projectGoal = options.projectGoal ?? 'TODO: 用一句话说明这个项目要解决什么问题。';
95
+ const created = [];
96
+ const skipped = [];
97
+ const updated = [];
98
+
99
+ for (const file of ENTRY_FILES) {
100
+ const source = path.join(templateRoot, file);
101
+ const dest = path.join(target, file);
102
+ const display = normalizeRelative(file);
103
+
104
+ if (await exists(dest)) {
105
+ skipped.push(display);
106
+ continue;
107
+ }
108
+
109
+ const template = await readFile(source, 'utf8');
110
+ const rendered = renderTemplate(template, { projectName, projectGoal });
111
+ created.push(display);
112
+
113
+ if (!dryRun) {
114
+ await mkdir(path.dirname(dest), { recursive: true });
115
+ await writeFile(dest, rendered);
116
+ }
117
+ }
118
+
119
+ return { created, skipped, updated };
120
+ }
121
+
122
+ function renderTemplate(template, replacements) {
123
+ return template
124
+ .replaceAll('{{PROJECT_NAME}}', replacements.projectName)
125
+ .replaceAll('{{PROJECT_GOAL}}', replacements.projectGoal);
126
+ }
127
+
128
+ async function exists(value) {
129
+ try {
130
+ await stat(value);
131
+ return true;
132
+ } catch (error) {
133
+ if (error?.code === 'ENOENT') {
134
+ return false;
135
+ }
136
+ throw error;
137
+ }
76
138
  }
77
139
 
78
- export async function renderEntryFiles(target, templateRoot, options = {}) {
79
- const dryRun = Boolean(options.dryRun ?? DEFAULT_DRY_RUN);
80
- const projectName = options.projectName ?? path.basename(target);
81
- const projectGoal = options.projectGoal ?? 'TODO: 用一句话说明这个项目要解决什么问题。';
82
- const created = [];
83
- const skipped = [];
84
- const updated = [];
85
-
86
- for (const file of ENTRY_FILES) {
87
- const source = path.join(templateRoot, file);
88
- const dest = path.join(target, file);
89
- const display = normalizeRelative(file);
90
-
91
- if (await exists(dest)) {
92
- skipped.push(display);
93
- continue;
94
- }
95
-
96
- const template = await readFile(source, 'utf8');
97
- const rendered = renderTemplate(template, { projectName, projectGoal });
98
- created.push(display);
99
-
100
- if (!dryRun) {
101
- await mkdir(path.dirname(dest), { recursive: true });
102
- await writeFile(dest, rendered);
140
+ async function hasDirectory(target, candidates) {
141
+ for (const candidate of candidates) {
142
+ try {
143
+ if ((await stat(path.join(target, candidate))).isDirectory()) {
144
+ return true;
145
+ }
146
+ } catch (error) {
147
+ if (error?.code !== 'ENOENT') {
148
+ throw error;
149
+ }
103
150
  }
104
151
  }
105
-
106
- return { created, skipped, updated };
107
- }
108
-
109
- function renderTemplate(template, replacements) {
110
- return template
111
- .replaceAll('{{PROJECT_NAME}}', replacements.projectName)
112
- .replaceAll('{{PROJECT_GOAL}}', replacements.projectGoal);
152
+ return false;
113
153
  }
114
154
 
115
- async function exists(value) {
116
- try {
117
- await stat(value);
155
+ async function shouldIncludeDefaultRoot(target, defaultRoot, alternatives) {
156
+ if (await exists(path.join(target, defaultRoot))) {
118
157
  return true;
119
- } catch (error) {
120
- if (error?.code === 'ENOENT') {
121
- return false;
122
- }
123
- throw error;
124
158
  }
159
+ return !(await hasDirectory(target, alternatives));
125
160
  }
126
-
127
- function normalizeRelative(value) {
128
- return value.split(path.sep).join('/');
129
- }
161
+
162
+ function normalizeRelative(value) {
163
+ return value.split(path.sep).join('/');
164
+ }
package/src/utils/copy.js CHANGED
@@ -1,100 +1,100 @@
1
- import { copyFile, chmod, mkdir, readdir, stat, writeFile } from 'node:fs/promises';
2
- import path from 'node:path';
3
-
4
- export async function copyTreeIdempotent(srcDir, destDir, options = {}) {
5
- const dryRun = Boolean(options.dryRun);
6
- const created = [];
7
- const skipped = [];
8
-
9
- const source = await stat(srcDir);
10
- if (!source.isDirectory()) {
11
- throw new Error(`source is not a directory: ${srcDir}`);
12
- }
13
-
14
- await walk(srcDir, '');
15
-
16
- return { created, skipped };
17
-
18
- async function walk(currentSourceDir, relativeDir) {
19
- const entries = await readdir(currentSourceDir, { withFileTypes: true });
20
- const currentDestDir = path.join(destDir, relativeDir);
21
-
22
- if (!dryRun) {
23
- await mkdir(currentDestDir, { recursive: true });
24
- }
25
-
26
- if (entries.length === 0) {
27
- const marker = normalizeRelative(path.join(relativeDir, '.gitkeep'));
28
- const markerDest = path.join(currentDestDir, '.gitkeep');
29
- if (await exists(markerDest)) {
30
- skipped.push(marker);
31
- } else {
32
- created.push(marker);
33
- if (!dryRun) {
34
- await writeFile(markerDest, '');
35
- }
36
- }
37
- return;
38
- }
39
-
40
- for (const entry of entries) {
41
- const relativePath = path.join(relativeDir, entry.name);
42
- const sourcePath = path.join(currentSourceDir, entry.name);
43
- const destPath = path.join(destDir, relativePath);
44
-
45
- if (entry.isDirectory()) {
46
- await walk(sourcePath, relativePath);
47
- } else if (entry.isFile()) {
48
- const normalized = normalizeRelative(relativePath);
49
- if (await exists(destPath)) {
50
- skipped.push(normalized);
51
- } else {
52
- created.push(normalized);
53
- if (!dryRun) {
54
- await mkdir(path.dirname(destPath), { recursive: true });
55
- await copyFile(sourcePath, destPath);
56
- const mode = (await stat(sourcePath)).mode & 0o777;
57
- await chmod(destPath, mode);
58
- }
59
- }
60
- }
61
- }
62
- }
63
- }
64
-
65
- export async function copyFileIdempotent(srcFile, destFile, options = {}) {
66
- const dryRun = Boolean(options.dryRun);
67
- const source = await stat(srcFile);
68
- if (!source.isFile()) {
69
- throw new Error(`source is not a file: ${srcFile}`);
70
- }
71
-
72
- const relative = path.basename(destFile);
73
- if (await exists(destFile)) {
74
- return { created: [], skipped: [relative] };
75
- }
76
-
77
- if (!dryRun) {
78
- await mkdir(path.dirname(destFile), { recursive: true });
79
- await copyFile(srcFile, destFile);
80
- await chmod(destFile, source.mode & 0o777);
81
- }
82
-
83
- return { created: [relative], skipped: [] };
84
- }
85
-
86
- async function exists(value) {
87
- try {
88
- await stat(value);
89
- return true;
90
- } catch (error) {
91
- if (error?.code === 'ENOENT') {
92
- return false;
93
- }
94
- throw error;
95
- }
96
- }
97
-
98
- function normalizeRelative(value) {
99
- return value.split(path.sep).join('/');
100
- }
1
+ import { copyFile, chmod, mkdir, readdir, stat, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ export async function copyTreeIdempotent(srcDir, destDir, options = {}) {
5
+ const dryRun = Boolean(options.dryRun);
6
+ const created = [];
7
+ const skipped = [];
8
+
9
+ const source = await stat(srcDir);
10
+ if (!source.isDirectory()) {
11
+ throw new Error(`source is not a directory: ${srcDir}`);
12
+ }
13
+
14
+ await walk(srcDir, '');
15
+
16
+ return { created, skipped };
17
+
18
+ async function walk(currentSourceDir, relativeDir) {
19
+ const entries = await readdir(currentSourceDir, { withFileTypes: true });
20
+ const currentDestDir = path.join(destDir, relativeDir);
21
+
22
+ if (!dryRun) {
23
+ await mkdir(currentDestDir, { recursive: true });
24
+ }
25
+
26
+ if (entries.length === 0) {
27
+ const marker = normalizeRelative(path.join(relativeDir, '.gitkeep'));
28
+ const markerDest = path.join(currentDestDir, '.gitkeep');
29
+ if (await exists(markerDest)) {
30
+ skipped.push(marker);
31
+ } else {
32
+ created.push(marker);
33
+ if (!dryRun) {
34
+ await writeFile(markerDest, '');
35
+ }
36
+ }
37
+ return;
38
+ }
39
+
40
+ for (const entry of entries) {
41
+ const relativePath = path.join(relativeDir, entry.name);
42
+ const sourcePath = path.join(currentSourceDir, entry.name);
43
+ const destPath = path.join(destDir, relativePath);
44
+
45
+ if (entry.isDirectory()) {
46
+ await walk(sourcePath, relativePath);
47
+ } else if (entry.isFile()) {
48
+ const normalized = normalizeRelative(relativePath);
49
+ if (await exists(destPath)) {
50
+ skipped.push(normalized);
51
+ } else {
52
+ created.push(normalized);
53
+ if (!dryRun) {
54
+ await mkdir(path.dirname(destPath), { recursive: true });
55
+ await copyFile(sourcePath, destPath);
56
+ const mode = (await stat(sourcePath)).mode & 0o777;
57
+ await chmod(destPath, mode);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ export async function copyFileIdempotent(srcFile, destFile, options = {}) {
66
+ const dryRun = Boolean(options.dryRun);
67
+ const source = await stat(srcFile);
68
+ if (!source.isFile()) {
69
+ throw new Error(`source is not a file: ${srcFile}`);
70
+ }
71
+
72
+ const relative = path.basename(destFile);
73
+ if (await exists(destFile)) {
74
+ return { created: [], skipped: [relative] };
75
+ }
76
+
77
+ if (!dryRun) {
78
+ await mkdir(path.dirname(destFile), { recursive: true });
79
+ await copyFile(srcFile, destFile);
80
+ await chmod(destFile, source.mode & 0o777);
81
+ }
82
+
83
+ return { created: [relative], skipped: [] };
84
+ }
85
+
86
+ async function exists(value) {
87
+ try {
88
+ await stat(value);
89
+ return true;
90
+ } catch (error) {
91
+ if (error?.code === 'ENOENT') {
92
+ return false;
93
+ }
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ function normalizeRelative(value) {
99
+ return value.split(path.sep).join('/');
100
+ }
@@ -1,60 +1,60 @@
1
- import { existsSync } from 'node:fs';
2
- import { access } from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
-
6
- let cachedPackageRoot;
7
-
8
- export async function packageRootAsync() {
9
- if (cachedPackageRoot) {
10
- return cachedPackageRoot;
11
- }
12
-
13
- let current = path.dirname(fileURLToPath(import.meta.url));
14
- while (true) {
15
- const candidate = path.join(current, 'package.json');
16
- try {
17
- await access(candidate);
18
- cachedPackageRoot = current;
19
- return cachedPackageRoot;
20
- } catch {
21
- const parent = path.dirname(current);
22
- if (parent === current) {
23
- throw new Error('Could not find package.json from current module path.');
24
- }
25
- current = parent;
26
- }
27
- }
28
- }
29
-
30
- export function packageRoot() {
31
- if (cachedPackageRoot) {
32
- return cachedPackageRoot;
33
- }
34
-
35
- let current = path.dirname(fileURLToPath(import.meta.url));
36
- while (true) {
37
- if (existsSync(path.join(current, 'package.json'))) {
38
- cachedPackageRoot = current;
39
- return cachedPackageRoot;
40
- }
41
-
42
- const parent = path.dirname(current);
43
- if (parent === current) {
44
- throw new Error('Could not find package.json from current module path.');
45
- }
46
- current = parent;
47
- }
48
- }
49
-
50
- export function assetPath(name) {
51
- return path.join(packageRoot(), name);
52
- }
53
-
54
- export function skillsPath() {
55
- return path.join(packageRoot(), 'skills');
56
- }
57
-
58
- export function templatesPath(subdir = '') {
59
- return path.join(packageRoot(), 'templates', subdir);
60
- }
1
+ import { existsSync } from 'node:fs';
2
+ import { access } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ let cachedPackageRoot;
7
+
8
+ export async function packageRootAsync() {
9
+ if (cachedPackageRoot) {
10
+ return cachedPackageRoot;
11
+ }
12
+
13
+ let current = path.dirname(fileURLToPath(import.meta.url));
14
+ while (true) {
15
+ const candidate = path.join(current, 'package.json');
16
+ try {
17
+ await access(candidate);
18
+ cachedPackageRoot = current;
19
+ return cachedPackageRoot;
20
+ } catch {
21
+ const parent = path.dirname(current);
22
+ if (parent === current) {
23
+ throw new Error('Could not find package.json from current module path.');
24
+ }
25
+ current = parent;
26
+ }
27
+ }
28
+ }
29
+
30
+ export function packageRoot() {
31
+ if (cachedPackageRoot) {
32
+ return cachedPackageRoot;
33
+ }
34
+
35
+ let current = path.dirname(fileURLToPath(import.meta.url));
36
+ while (true) {
37
+ if (existsSync(path.join(current, 'package.json'))) {
38
+ cachedPackageRoot = current;
39
+ return cachedPackageRoot;
40
+ }
41
+
42
+ const parent = path.dirname(current);
43
+ if (parent === current) {
44
+ throw new Error('Could not find package.json from current module path.');
45
+ }
46
+ current = parent;
47
+ }
48
+ }
49
+
50
+ export function assetPath(name) {
51
+ return path.join(packageRoot(), name);
52
+ }
53
+
54
+ export function skillsPath() {
55
+ return path.join(packageRoot(), 'skills');
56
+ }
57
+
58
+ export function templatesPath(subdir = '') {
59
+ return path.join(packageRoot(), 'templates', subdir);
60
+ }
@@ -1,30 +1,30 @@
1
- export function formatSetupReport({ created, skipped, target, dryRun }) {
2
- const installedLabel = dryRun ? 'Would install' : 'Installed';
3
- return [
4
- dryRun ? 'AE setup dry run complete' : 'AE setup complete',
5
- `- ${installedLabel}: ${created.length} (${summarize(created)})`,
6
- `- Skipped: ${skipped.length} (${summarize(skipped)})`,
7
- `- Target: ${target}`,
8
- `- Next: run \`ae init "<goal>"\`.`,
9
- ].join('\n');
10
- }
11
-
12
- export function formatInitReport({ created, skipped, target, dryRun }) {
13
- const createdLabel = dryRun ? 'Would create' : 'Created';
14
- return [
15
- dryRun ? 'AE init dry run complete' : 'AE init complete',
16
- `- ${createdLabel}: ${created.length} (${summarize(created)})`,
17
- `- Skipped: ${skipped.length} (${summarize(skipped)})`,
18
- `- Target: ${target}`,
19
- ].join('\n');
20
- }
21
-
22
- function summarize(paths) {
23
- if (paths.length === 0) {
24
- return 'none';
25
- }
26
- if (paths.length <= 8) {
27
- return paths.join(', ');
28
- }
29
- return `${paths.slice(0, 8).join(', ')}, ...`;
30
- }
1
+ export function formatSetupReport({ created, skipped, target, dryRun }) {
2
+ const installedLabel = dryRun ? 'Would install' : 'Installed';
3
+ return [
4
+ dryRun ? 'AE setup dry run complete' : 'AE setup complete',
5
+ `- ${installedLabel}: ${created.length} (${summarize(created)})`,
6
+ `- Skipped: ${skipped.length} (${summarize(skipped)})`,
7
+ `- Target: ${target}`,
8
+ `- Next: run \`ae init "<goal>"\`.`,
9
+ ].join('\n');
10
+ }
11
+
12
+ export function formatInitReport({ created, skipped, target, dryRun }) {
13
+ const createdLabel = dryRun ? 'Would create' : 'Created';
14
+ return [
15
+ dryRun ? 'AE init dry run complete' : 'AE init complete',
16
+ `- ${createdLabel}: ${created.length} (${summarize(created)})`,
17
+ `- Skipped: ${skipped.length} (${summarize(skipped)})`,
18
+ `- Target: ${target}`,
19
+ ].join('\n');
20
+ }
21
+
22
+ function summarize(paths) {
23
+ if (paths.length === 0) {
24
+ return 'none';
25
+ }
26
+ if (paths.length <= 8) {
27
+ return paths.join(', ');
28
+ }
29
+ return `${paths.slice(0, 8).join(', ')}, ...`;
30
+ }
@@ -1,3 +1,5 @@
1
1
  # {{PROJECT_NAME}}
2
2
 
3
3
  > {{PROJECT_GOAL}}
4
+
5
+ - **目录按职责聚合、信息渐进披露**:除已有明确规范、框架约定或工具生成的目录外,手工维护的文件按职责或主题组织,避免无关文件长期平铺;入口保留概要与导航,细节随复杂度按需下沉,不为分层而分层;新增或修改时检查涉及范围的归属与边界,调整结构同步相关引用与说明。
@@ -1,5 +1,5 @@
1
- # {{PROJECT_NAME}}
2
-
3
- **Always read [AGENTS.md](./AGENTS.md) at the start of every session before doing any work.**
4
-
5
- This project's instructions, rules, structure, and operating guidelines are all defined in `AGENTS.md`.
1
+ # {{PROJECT_NAME}}
2
+
3
+ **Always read [AGENTS.md](./AGENTS.md) at the start of every session before doing any work.**
4
+
5
+ This project's instructions, rules, structure, and operating guidelines are all defined in `AGENTS.md`.