@ecc-hgy/ae 0.4.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.
- package/LICENSE +21 -21
- package/README.md +168 -138
- package/bin/ae.js +2 -2
- package/package.json +43 -43
- package/skills/brainstorming/SKILL.md +133 -133
- package/skills/diagnose/SKILL.md +146 -146
- package/skills/diagnose/assets/issue-7-sections.md +35 -35
- package/skills/diagnose/scripts/hitl-loop.template.sh +41 -41
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/handoff/SKILL.md +19 -19
- package/skills/improve-codebase-architecture/DEEPENING.md +37 -37
- package/skills/improve-codebase-architecture/HTML-REPORT.md +123 -123
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -44
- package/skills/improve-codebase-architecture/LANGUAGE.md +53 -53
- package/skills/improve-codebase-architecture/SKILL.md +101 -101
- package/skills/karpathy-guidelines/SKILL.md +63 -63
- package/skills/powerautomate-email-to-sharepoint-excel/powerautomate-email-to-sharepoint-excel-skill.md +496 -0
- package/skills/review/SKILL.md +119 -119
- package/skills/tdd/SKILL.md +157 -157
- package/skills/tdd/deep-modules.md +33 -33
- package/skills/tdd/interface-design.md +31 -31
- package/skills/tdd/mocking.md +59 -59
- package/skills/tdd/refactoring.md +10 -10
- package/skills/tdd/tests.md +61 -61
- package/skills/to-issues/SKILL.md +79 -79
- package/skills/to-issues/todo-template.md +25 -25
- package/skills/to-prd/SKILL.md +108 -108
- package/skills/using-agentic-engineering/SKILL.md +62 -62
- package/skills/verification-before-completion/SKILL.md +153 -153
- package/skills/writing-plans/SKILL.md +115 -115
- package/skills/zoom-out/SKILL.md +7 -7
- package/src/cli.js +61 -61
- package/src/commands/init.js +137 -134
- package/src/commands/setup.js +162 -103
- package/src/platforms.js +132 -0
- package/src/skeleton.js +134 -99
- package/src/utils/copy.js +100 -100
- package/src/utils/paths.js +60 -60
- package/src/utils/report.js +30 -30
- package/templates/entries/AGENTS.md +2 -0
- package/templates/entries/CLAUDE.md +5 -5
- package/templates/entries/README.md +33 -31
- package/templates/entries/handoff.md +1 -1
- package/templates/entries/spec/ADR/AGENTS.md +30 -30
- package/templates/entries/spec/ADR/CLAUDE.md +5 -5
- package/templates/entries/spec/AGENTS.md +34 -34
- package/templates/entries/spec/CLAUDE.md +5 -5
- package/templates/entries/spec/INDEX.md +28 -28
- package/templates/entries/spec/README.md +23 -23
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
|
+
}
|
package/src/utils/paths.js
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/report.js
CHANGED
|
@@ -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,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`.
|
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
# {{PROJECT_NAME}}
|
|
2
|
-
|
|
3
|
-
> {{PROJECT_GOAL}}
|
|
4
|
-
|
|
5
|
-
<!--
|
|
6
|
-
根 README 的核心:让别人 30 秒搞清「为什么有用、能做什么、怎么用」。
|
|
7
|
-
下面是建议骨架,按需增删、不必都填,也不必拘泥小节名:
|
|
8
|
-
- 它是什么 + 一句话价值 → 上面的标题 + 一行(已占位)
|
|
9
|
-
- 为什么 / 给谁 → 解决什么问题、面向谁
|
|
10
|
-
- Quick Start → 最快见效的一段命令 / 步骤(装好 + 第一个能见效的操作)
|
|
11
|
-
- 使用示例 → 典型场景(必要时再贴一段真实用法)
|
|
12
|
-
- 目录地图 / 文档入口 → 关键目录 + 深入文档的链接
|
|
13
|
-
-->
|
|
14
|
-
|
|
15
|
-
## 这是什么
|
|
16
|
-
|
|
17
|
-
TODO: 一段话说清它解决什么问题、给谁用、用它能做什么。
|
|
18
|
-
|
|
19
|
-
## Quick Start
|
|
20
|
-
|
|
21
|
-
TODO: 最快跑起来的命令或步骤。
|
|
22
|
-
|
|
23
|
-
<!-- ## 使用示例 —— 有典型场景时再加,贴一段真实用法 -->
|
|
24
|
-
|
|
25
|
-
## 目录地图
|
|
26
|
-
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
> {{PROJECT_GOAL}}
|
|
4
|
+
|
|
5
|
+
<!--
|
|
6
|
+
根 README 的核心:让别人 30 秒搞清「为什么有用、能做什么、怎么用」。
|
|
7
|
+
下面是建议骨架,按需增删、不必都填,也不必拘泥小节名:
|
|
8
|
+
- 它是什么 + 一句话价值 → 上面的标题 + 一行(已占位)
|
|
9
|
+
- 为什么 / 给谁 → 解决什么问题、面向谁
|
|
10
|
+
- Quick Start → 最快见效的一段命令 / 步骤(装好 + 第一个能见效的操作)
|
|
11
|
+
- 使用示例 → 典型场景(必要时再贴一段真实用法)
|
|
12
|
+
- 目录地图 / 文档入口 → 关键目录 + 深入文档的链接
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
## 这是什么
|
|
16
|
+
|
|
17
|
+
TODO: 一段话说清它解决什么问题、给谁用、用它能做什么。
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
TODO: 最快跑起来的命令或步骤。
|
|
22
|
+
|
|
23
|
+
<!-- ## 使用示例 —— 有典型场景时再加,贴一段真实用法 -->
|
|
24
|
+
|
|
25
|
+
## 目录地图
|
|
26
|
+
|
|
27
27
|
- `spec/`:SDD/TDD 规格产物——需求索引看 `spec/INDEX.md`,项目地图看 `spec/README.md`。
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
- `src/`:业务代码、程序入口与脚本;按职责组织,不要求与 `spec/needs/` 一一对应。
|
|
29
|
+
- `tests/`:测试代码与测试样例。
|
|
30
|
+
- `docs/`:原始文档资料、说明与长期维护的知识。
|
|
31
|
+
- `data/`:各类数据与运行结果,内部按项目需要渐进组织。
|
|
32
|
+
- `dist/`:构建产物;有构建时由工具生成。
|
|
33
|
+
|
|
34
|
+
<!-- 文档入口:AGENTS.md(项目指令)· handoff.md(当前态快照)· spec/INDEX.md(需求索引) -->
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!-- 跨会话/跨项目进展交接,由 handoff skill 在会话结束时人工触发更新。 -->
|
|
1
|
+
<!-- 跨会话/跨项目进展交接,由 handoff skill 在会话结束时人工触发更新。 -->
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
# spec/ADR/ 规则
|
|
2
|
-
|
|
3
|
-
> 架构决策记录(ADR)规则。在 `spec/ADR/` 下创建或修改 ADR 时遵守本文件。
|
|
4
|
-
|
|
5
|
-
## 命名
|
|
6
|
-
|
|
7
|
-
`NNNN-<标题>.md`,**4 位序号**,kebab-case 标题。例:`0001-postgres-as-primary-store.md`。
|
|
8
|
-
|
|
9
|
-
## frontmatter
|
|
10
|
-
|
|
11
|
-
```yaml
|
|
12
|
-
---
|
|
13
|
-
status: accepted # proposed | accepted | superseded
|
|
14
|
-
date: 2026-06-14
|
|
15
|
-
supersedes: 0000-xxx.md # 仅当取代旧 ADR 时填
|
|
16
|
-
---
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
正文按具体决策自然组织(背景 / 决策 / 理由 / 影响),不强加重模板。
|
|
20
|
-
|
|
21
|
-
## 状态机
|
|
22
|
-
|
|
23
|
-
```text
|
|
24
|
-
proposed ─(评审接受)─► accepted ─(新 ADR 取代)─► superseded
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
- `proposed`:新提案,未拍板(由 writing-plans / review / improve-codebase-architecture 创建时默认)。
|
|
28
|
-
- `accepted`:已评审接受;视为**约束**,其他需求不得违背。
|
|
29
|
-
- `superseded`:被新 ADR 取代;新 ADR 的 `supersedes` 指回旧编号,**不删旧文件**。
|
|
30
|
-
|
|
1
|
+
# spec/ADR/ 规则
|
|
2
|
+
|
|
3
|
+
> 架构决策记录(ADR)规则。在 `spec/ADR/` 下创建或修改 ADR 时遵守本文件。
|
|
4
|
+
|
|
5
|
+
## 命名
|
|
6
|
+
|
|
7
|
+
`NNNN-<标题>.md`,**4 位序号**,kebab-case 标题。例:`0001-postgres-as-primary-store.md`。
|
|
8
|
+
|
|
9
|
+
## frontmatter
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
---
|
|
13
|
+
status: accepted # proposed | accepted | superseded
|
|
14
|
+
date: 2026-06-14
|
|
15
|
+
supersedes: 0000-xxx.md # 仅当取代旧 ADR 时填
|
|
16
|
+
---
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
正文按具体决策自然组织(背景 / 决策 / 理由 / 影响),不强加重模板。
|
|
20
|
+
|
|
21
|
+
## 状态机
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
proposed ─(评审接受)─► accepted ─(新 ADR 取代)─► superseded
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `proposed`:新提案,未拍板(由 writing-plans / review / improve-codebase-architecture 创建时默认)。
|
|
28
|
+
- `accepted`:已评审接受;视为**约束**,其他需求不得违背。
|
|
29
|
+
- `superseded`:被新 ADR 取代;新 ADR 的 `supersedes` 指回旧编号,**不删旧文件**。
|
|
30
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# spec/ADR/
|
|
2
|
-
|
|
3
|
-
**Always read [AGENTS.md](./AGENTS.md) in this directory before creating or editing an ADR.**
|
|
4
|
-
|
|
5
|
-
`spec/ADR/AGENTS.md` defines ADR naming, frontmatter, and the proposed/accepted/superseded state machine.
|
|
1
|
+
# spec/ADR/
|
|
2
|
+
|
|
3
|
+
**Always read [AGENTS.md](./AGENTS.md) in this directory before creating or editing an ADR.**
|
|
4
|
+
|
|
5
|
+
`spec/ADR/AGENTS.md` defines ADR naming, frontmatter, and the proposed/accepted/superseded state machine.
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
# spec/ 规则
|
|
2
|
-
|
|
3
|
-
> 本目录(`spec/`)的工作规则。在 `spec/` 下读写文件时遵守本文件。
|
|
4
|
-
|
|
5
|
-
## 总领
|
|
6
|
-
|
|
7
|
-
S1 节点 → 产物 → skill 的路由总览,见随包的 `using-agentic-engineering` skill——它判定"进不进 AE 流程"并分发到各节点。
|
|
8
|
-
|
|
9
|
-
两道硬门禁:**A1 未对齐不写 prd**;**B2 期望态未对齐不修复**。
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## R1:落盘后同步 INDEX
|
|
13
|
-
落盘 `spec/needs/<需求名>/` 下任何文件后,**必须同步更新 `spec/INDEX.md`** 对应行。若不确定如何更新,按上面「INDEX 推导规则」手动重算(未来可用 `ae index-rebuild` 幂等重扫)。
|
|
14
|
-
|
|
15
|
-
### INDEX 推导规则
|
|
16
|
-
|
|
17
|
-
`spec/INDEX.md` 给模型看:一次扫描看到所有需求 + 状态 + 进度。`当前节点` 列**不存储**在任何 frontmatter,由该需求下文件状态**实时推导**:
|
|
18
|
-
|
|
19
|
-
| 条件 | 当前节点 |
|
|
20
|
-
|------|---------|
|
|
21
|
-
| prd 不存在 或 status=draft | A1 / A2 |
|
|
22
|
-
| prd active + design 不存在/draft | A3 设计 |
|
|
23
|
-
| prd+design active + todo 不存在 | A4 拆解 |
|
|
24
|
-
| prd+design active + todo 有未完成项 | A5 执行 |
|
|
25
|
-
| todo 全勾 + 未端到端验证 | A6 verify |
|
|
26
|
-
| 所有 issues 已验收 + 已交付 | 已完成 |
|
|
27
|
-
| 存在 active 的 issues | 进 B 子路径,按 issues 状态推 B2…B7 |
|
|
28
|
-
| prd / design 任一 archived | 已归档 |
|
|
29
|
-
|
|
30
|
-
- INDEX 是**推导态**:文件状态变就重新推导,**不要手编 `当前节点` 列**。
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## 何时升 ADR
|
|
34
|
-
|
|
1
|
+
# spec/ 规则
|
|
2
|
+
|
|
3
|
+
> 本目录(`spec/`)的工作规则。在 `spec/` 下读写文件时遵守本文件。
|
|
4
|
+
|
|
5
|
+
## 总领
|
|
6
|
+
|
|
7
|
+
S1 节点 → 产物 → skill 的路由总览,见随包的 `using-agentic-engineering` skill——它判定"进不进 AE 流程"并分发到各节点。
|
|
8
|
+
|
|
9
|
+
两道硬门禁:**A1 未对齐不写 prd**;**B2 期望态未对齐不修复**。
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## R1:落盘后同步 INDEX
|
|
13
|
+
落盘 `spec/needs/<需求名>/` 下任何文件后,**必须同步更新 `spec/INDEX.md`** 对应行。若不确定如何更新,按上面「INDEX 推导规则」手动重算(未来可用 `ae index-rebuild` 幂等重扫)。
|
|
14
|
+
|
|
15
|
+
### INDEX 推导规则
|
|
16
|
+
|
|
17
|
+
`spec/INDEX.md` 给模型看:一次扫描看到所有需求 + 状态 + 进度。`当前节点` 列**不存储**在任何 frontmatter,由该需求下文件状态**实时推导**:
|
|
18
|
+
|
|
19
|
+
| 条件 | 当前节点 |
|
|
20
|
+
|------|---------|
|
|
21
|
+
| prd 不存在 或 status=draft | A1 / A2 |
|
|
22
|
+
| prd active + design 不存在/draft | A3 设计 |
|
|
23
|
+
| prd+design active + todo 不存在 | A4 拆解 |
|
|
24
|
+
| prd+design active + todo 有未完成项 | A5 执行 |
|
|
25
|
+
| todo 全勾 + 未端到端验证 | A6 verify |
|
|
26
|
+
| 所有 issues 已验收 + 已交付 | 已完成 |
|
|
27
|
+
| 存在 active 的 issues | 进 B 子路径,按 issues 状态推 B2…B7 |
|
|
28
|
+
| prd / design 任一 archived | 已归档 |
|
|
29
|
+
|
|
30
|
+
- INDEX 是**推导态**:文件状态变就重新推导,**不要手编 `当前节点` 列**。
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## 何时升 ADR
|
|
34
|
+
|
|
35
35
|
决策影响**多个需求**或属项目级不变量时,从 `design.md` 升到`spec/ADR`(判断:推翻它会改动 >1 个需求)。单需求的技术决策留在该需求的 `design.md`。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# spec/
|
|
2
|
-
|
|
3
|
-
**Always read [AGENTS.md](./AGENTS.md) in this directory before working under `spec/`.**
|
|
4
|
-
|
|
5
|
-
`spec/AGENTS.md` defines the S1 node map, the INDEX derivation table, and the post-write INDEX sync rule (R1).
|
|
1
|
+
# spec/
|
|
2
|
+
|
|
3
|
+
**Always read [AGENTS.md](./AGENTS.md) in this directory before working under `spec/`.**
|
|
4
|
+
|
|
5
|
+
`spec/AGENTS.md` defines the S1 node map, the INDEX derivation table, and the post-write INDEX sync rule (R1).
|