@ecc-hgy/ae 0.2.0 → 0.3.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 CHANGED
@@ -10,7 +10,6 @@ Agentic Engineering is a lightweight personal software lifecycle method: SDD rec
10
10
  ```bash
11
11
  cd /your/project
12
12
  npx @ecc-hgy/ae setup
13
- # Optional: edit config/ae-rules.md
14
13
  npx @ecc-hgy/ae init "one-line project goal"
15
14
  ```
16
15
 
@@ -18,23 +17,27 @@ npx @ecc-hgy/ae init "one-line project goal"
18
17
 
19
18
  - `skills/` -> `.claude/skills/`
20
19
  - `skills/` -> `.codex/skills/`
21
- - `templates/ae-rules.md` -> `config/ae-rules.md`
22
20
 
23
- `ae init` creates the platform-neutral project skeleton, renders entry files, and syncs the contents of `config/ae-rules.md` into the managed `AE:RULES` block in `AGENTS.md` and `CLAUDE.md`.
21
+ `ae init` creates the platform-neutral project skeleton and renders entry files. Project-specific rules live directly in `AGENTS.md`.
24
22
 
25
23
  ## CLI
26
24
 
27
25
  ```bash
28
26
  ae setup
29
27
  ae init "one-line project goal"
30
- ae init "goal" --rules /path/to/rules.md
31
28
  ae --help
32
29
  ae --version
33
30
  ```
34
31
 
35
- Both commands are idempotent. They create missing files, skip existing files, and never overwrite user edits outside the managed `AE:RULES` block. There is no `--force` mode.
32
+ Both commands are idempotent. They create missing files, skip existing files, and never overwrite existing entry files. There is no `--force` mode.
36
33
 
37
- `ae init` requires `ae setup` to have run first. This keeps the two-step workflow explicit so you can edit `config/ae-rules.md` before it is injected.
34
+ `ae init` requires `ae setup` to have run first. This keeps asset installation explicit before skeleton creation.
35
+
36
+ ## Single Source Of Truth
37
+
38
+ - `AGENTS.md` is the canonical project instructions file.
39
+ - `CLAUDE.md` is a short pointer to `AGENTS.md`; do not maintain a separate copy there.
40
+ - All project-specific rules live in `AGENTS.md` under `## Project-Specific Rules`.
38
41
 
39
42
  ## Platform Scope
40
43
 
@@ -90,7 +93,7 @@ The two hard gates are:
90
93
  bin/ npm executable entry
91
94
  src/ zero-dependency CLI implementation
92
95
  skills/ Selected Agentic Engineering skills
93
- templates/ Entry file templates and the ae-rules.md starter
96
+ templates/ Entry file templates
94
97
  docs/ User-facing methodology docs
95
98
  design/ Design records for this repository
96
99
  tests/ node:test coverage for CLI behavior
@@ -100,8 +103,9 @@ tests/ node:test coverage for CLI behavior
100
103
 
101
104
  1. Stage 2: npm CLI asset distribution with `init` and `ingest-mother`. Superseded by v0.2.
102
105
  2. v0.2: split CLI into `setup` + `init`, remove slash command assets, and move rule sync into `ae init`.
103
- 3. Stage 3: design `ae update` for skill/template upgrades and implement `ae index-rebuild`.
104
- 4. Add CI and Windows path validation before broader distribution.
106
+ 3. v0.3: remove rules injection; make `AGENTS.md` the single source of truth and `CLAUDE.md` a pointer.
107
+ 4. Stage 3: design `ae update` for skill/template upgrades and implement `ae index-rebuild`.
108
+ 5. Add CI and Windows path validation before broader distribution.
105
109
 
106
110
  ## Limits
107
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecc-hgy/ae",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Agentic Engineering is a lightweight personal software lifecycle method: SDD records what to build, TDD drives how to prove it works.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -5,9 +5,9 @@ import { packageRoot } from './utils/paths.js';
5
5
  const HELP = `Usage: ae <command> [options]
6
6
 
7
7
  Commands:
8
- setup Install skills to .claude/.codex and drop a config/ae-rules.md template.
9
- init [goal] Create spec/ skeleton, render entry files, sync AE:RULES block from
10
- config/ae-rules.md. Requires \`ae setup\` to have run.
8
+ setup Install skills to .claude/.codex.
9
+ init [goal] Create spec/ skeleton and render entry files.
10
+ Requires \`ae setup\` to have run.
11
11
 
12
12
  Options:
13
13
  -h, --help Show help
@@ -15,7 +15,6 @@ Options:
15
15
 
16
16
  Typical workflow:
17
17
  ae setup
18
- [optional] edit config/ae-rules.md
19
18
  ae init "<one-line project goal>"`;
20
19
 
21
20
  export async function run(argv = []) {
@@ -1,6 +1,6 @@
1
- import { mkdir, readFile, stat } from 'node:fs/promises';
1
+ import { mkdir, stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { createDirectories, renderEntryFiles, syncRulesBlock, RulesMarkerError } from '../skeleton.js';
3
+ import { createDirectories, renderEntryFiles } from '../skeleton.js';
4
4
  import { templatesPath } from '../utils/paths.js';
5
5
  import { formatInitReport } from '../utils/report.js';
6
6
 
@@ -10,9 +10,7 @@ const HELP = `Usage: ae init [goal] [options]
10
10
 
11
11
  Options:
12
12
  --target <path> Initialize this project directory. Defaults to cwd.
13
- --rules <path> Read AE:RULES content from this markdown file.
14
- Defaults to <target>/config/ae-rules.md when present.
15
- --dry-run Show what would be created or synced without writing files.
13
+ --dry-run Show what would be created without writing files.
16
14
  -h, --help Show help`;
17
15
 
18
16
  export async function run(argv = []) {
@@ -51,16 +49,10 @@ export async function run(argv = []) {
51
49
  created.push(...entries.created);
52
50
  skipped.push(...entries.skipped);
53
51
 
54
- const rules = await syncRules(target, options);
55
-
56
- console.log(formatInitReport({ created, skipped, target, rules, dryRun: options.dryRun }));
52
+ console.log(formatInitReport({ created, skipped, target, dryRun: options.dryRun }));
57
53
  return 0;
58
54
  } catch (error) {
59
- if (error instanceof RulesMarkerError) {
60
- console.error(`AE init failed: ${error.message}`);
61
- } else {
62
- console.error(`AE init failed: ${error instanceof Error ? error.message : String(error)}`);
63
- }
55
+ console.error(`AE init failed: ${error instanceof Error ? error.message : String(error)}`);
64
56
  process.exitCode = 1;
65
57
  return 1;
66
58
  }
@@ -69,7 +61,6 @@ export async function run(argv = []) {
69
61
  function parseArgs(argv) {
70
62
  const options = {
71
63
  target: undefined,
72
- rules: undefined,
73
64
  goalParts: [],
74
65
  dryRun: false,
75
66
  help: false,
@@ -88,13 +79,6 @@ function parseArgs(argv) {
88
79
  }
89
80
  options.target = value;
90
81
  index += 1;
91
- } else if (arg === '--rules') {
92
- const value = argv[index + 1];
93
- if (!value) {
94
- throw new Error('--rules requires a path');
95
- }
96
- options.rules = value;
97
- index += 1;
98
82
  } else if (arg.startsWith('-')) {
99
83
  throw new Error(`Unknown option for init: ${arg}`);
100
84
  } else {
@@ -104,7 +88,6 @@ function parseArgs(argv) {
104
88
 
105
89
  return {
106
90
  target: options.target,
107
- rules: options.rules,
108
91
  dryRun: options.dryRun,
109
92
  help: options.help,
110
93
  goal: options.goalParts.length > 0 ? options.goalParts.join(' ') : undefined,
@@ -134,41 +117,6 @@ async function hasInstalledAssets(target) {
134
117
  );
135
118
  }
136
119
 
137
- async function syncRules(target, options) {
138
- const rulesPath = options.rules
139
- ? path.resolve(process.cwd(), options.rules)
140
- : path.join(target, 'config', 'ae-rules.md');
141
-
142
- const rulesInfo = await statMaybe(rulesPath);
143
- if (!rulesInfo) {
144
- if (options.rules) {
145
- throw new Error(`rules file does not exist: ${rulesPath}`);
146
- }
147
- return 'skipped (no rules file)';
148
- }
149
-
150
- if (rulesInfo.isDirectory()) {
151
- throw new Error(`expected a file, got directory: ${rulesPath}`);
152
- }
153
- if (!rulesInfo.isFile()) {
154
- throw new Error(`expected a file: ${rulesPath}`);
155
- }
156
-
157
- const rulesContent = await readFile(rulesPath, 'utf8');
158
- const agents = path.join(target, 'AGENTS.md');
159
- const claude = path.join(target, 'CLAUDE.md');
160
- const agentsExists = await exists(agents);
161
- const claudeExists = await exists(claude);
162
-
163
- if (options.dryRun && (!agentsExists || !claudeExists)) {
164
- return 'would sync';
165
- }
166
-
167
- await syncRulesBlock(agents, rulesContent, { dryRun: options.dryRun });
168
- await syncRulesBlock(claude, rulesContent, { dryRun: options.dryRun });
169
- return options.dryRun ? 'would sync' : 'synced';
170
- }
171
-
172
120
  async function statMaybe(value) {
173
121
  try {
174
122
  return await stat(value);
@@ -180,10 +128,6 @@ async function statMaybe(value) {
180
128
  }
181
129
  }
182
130
 
183
- async function exists(value) {
184
- return Boolean(await statMaybe(value));
185
- }
186
-
187
131
  async function isDirectory(value) {
188
132
  const current = await statMaybe(value);
189
133
  return Boolean(current?.isDirectory());
@@ -1,7 +1,7 @@
1
1
  import { mkdir, stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { copyFileIdempotent, copyTreeIdempotent } from '../utils/copy.js';
4
- import { skillsPath, templatesPath } from '../utils/paths.js';
3
+ import { copyTreeIdempotent } from '../utils/copy.js';
4
+ import { skillsPath } from '../utils/paths.js';
5
5
  import { formatSetupReport } from '../utils/report.js';
6
6
 
7
7
  const HELP = `Usage: ae setup [options]
@@ -45,14 +45,6 @@ export async function run(argv = []) {
45
45
  skipped.push(...result.skipped.map((item) => toDisplayPath(path.join(group.prefix, item))));
46
46
  }
47
47
 
48
- const rulesResult = await copyFileIdempotent(
49
- templatesPath('ae-rules.md'),
50
- path.join(target, 'config', 'ae-rules.md'),
51
- { dryRun: options.dryRun },
52
- );
53
- created.push(...rulesResult.created.map((item) => toDisplayPath(path.join('config', item))));
54
- skipped.push(...rulesResult.skipped.map((item) => toDisplayPath(path.join('config', item))));
55
-
56
48
  console.log(formatSetupReport({ created, skipped, target, dryRun: options.dryRun }));
57
49
  return 0;
58
50
  } catch (error) {
package/src/skeleton.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
 
4
- export const RULES_BEGIN = '<!-- AE:RULES:BEGIN -->';
5
- export const RULES_END = '<!-- AE:RULES:END -->';
6
- const RULES_COMMENT = '<!-- 由 ae init 从 config/ae-rules.md 或 --rules 指定文件同步,勿手改 -->';
7
4
  const DEFAULT_DRY_RUN = false;
8
5
 
9
6
  const SKELETON_DIRS = [
@@ -32,13 +29,6 @@ const ENTRY_FILES = [
32
29
  path.join('spec', 'INDEX.md'),
33
30
  ];
34
31
 
35
- export class RulesMarkerError extends Error {
36
- constructor(message) {
37
- super(message);
38
- this.name = 'RulesMarkerError';
39
- }
40
- }
41
-
42
32
  export async function createDirectories(target, options = {}) {
43
33
  const dryRun = Boolean(options.dryRun ?? DEFAULT_DRY_RUN);
44
34
  const created = [];
@@ -112,82 +102,12 @@ export async function renderEntryFiles(target, templateRoot, options = {}) {
112
102
  return { created, skipped, updated };
113
103
  }
114
104
 
115
- export async function syncRulesBlock(targetFile, rulesContent, options = {}) {
116
- const dryRun = Boolean(options.dryRun ?? DEFAULT_DRY_RUN);
117
- const content = await readFile(targetFile, 'utf8');
118
- const ranges = findRulesRanges(content, targetFile);
119
- const block = buildRulesBlock(rulesContent);
120
- const created = [];
121
- const skipped = [];
122
- const updated = [];
123
- const display = path.basename(targetFile);
124
-
125
- if (ranges.length === 0) {
126
- const next = content.endsWith('\n') ? `${content}\n${block}` : `${content}\n\n${block}`;
127
- if (next === content) {
128
- skipped.push(display);
129
- return { created, skipped, updated };
130
- }
131
- created.push(`${display}#AE:RULES`);
132
- if (!dryRun) {
133
- await writeFile(targetFile, next);
134
- }
135
- return { created, skipped, updated };
136
- }
137
-
138
- const [{ start, end }] = ranges;
139
- const next = `${content.slice(0, start)}${block}${content.slice(end)}`;
140
-
141
- if (next === content) {
142
- skipped.push(`${display}#AE:RULES`);
143
- return { created, skipped, updated };
144
- }
145
-
146
- updated.push(`${display}#AE:RULES`);
147
- if (!dryRun) {
148
- await writeFile(targetFile, next);
149
- }
150
-
151
- return { created, skipped, updated };
152
- }
153
-
154
- export function buildRulesBlock(rulesContent) {
155
- const normalized = rulesContent.endsWith('\n') ? rulesContent : `${rulesContent}\n`;
156
- return `${RULES_BEGIN}\n${RULES_COMMENT}\n${normalized}${RULES_END}\n`;
157
- }
158
-
159
105
  function renderTemplate(template, replacements) {
160
106
  return template
161
107
  .replaceAll('{{PROJECT_NAME}}', replacements.projectName)
162
108
  .replaceAll('{{PROJECT_GOAL}}', replacements.projectGoal);
163
109
  }
164
110
 
165
- function findRulesRanges(content, targetFile) {
166
- const beginMatches = [...content.matchAll(new RegExp(escapeRegExp(RULES_BEGIN), 'g'))];
167
- const endMatches = [...content.matchAll(new RegExp(escapeRegExp(RULES_END), 'g'))];
168
-
169
- if (beginMatches.length !== endMatches.length || beginMatches.length > 1) {
170
- throw new RulesMarkerError(`malformed AE:RULES markers in ${targetFile}`);
171
- }
172
-
173
- if (beginMatches.length === 0) {
174
- return [];
175
- }
176
-
177
- const start = beginMatches[0].index;
178
- const end = endMatches[0].index + RULES_END.length;
179
- if (start > endMatches[0].index) {
180
- throw new RulesMarkerError(`malformed AE:RULES markers in ${targetFile}`);
181
- }
182
-
183
- let rangeEnd = end;
184
- if (content.slice(rangeEnd, rangeEnd + 1) === '\n') {
185
- rangeEnd += 1;
186
- }
187
-
188
- return [{ start, end: rangeEnd }];
189
- }
190
-
191
111
  async function exists(value) {
192
112
  try {
193
113
  await stat(value);
@@ -203,7 +123,3 @@ async function exists(value) {
203
123
  function normalizeRelative(value) {
204
124
  return value.split(path.sep).join('/');
205
125
  }
206
-
207
- function escapeRegExp(value) {
208
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
209
- }
@@ -5,17 +5,16 @@ export function formatSetupReport({ created, skipped, target, dryRun }) {
5
5
  `- ${installedLabel}: ${created.length} (${summarize(created)})`,
6
6
  `- Skipped: ${skipped.length} (${summarize(skipped)})`,
7
7
  `- Target: ${target}`,
8
- `- Next: edit config/ae-rules.md (optional), then run \`ae init "<goal>"\`.`,
8
+ `- Next: run \`ae init "<goal>"\`.`,
9
9
  ].join('\n');
10
10
  }
11
11
 
12
- export function formatInitReport({ created, skipped, target, rules, dryRun }) {
12
+ export function formatInitReport({ created, skipped, target, dryRun }) {
13
13
  const createdLabel = dryRun ? 'Would create' : 'Created';
14
14
  return [
15
15
  dryRun ? 'AE init dry run complete' : 'AE init complete',
16
16
  `- ${createdLabel}: ${created.length} (${summarize(created)})`,
17
17
  `- Skipped: ${skipped.length} (${summarize(skipped)})`,
18
- `- Rules: ${rules}`,
19
18
  `- Target: ${target}`,
20
19
  ].join('\n');
21
20
  }
@@ -1,21 +1,24 @@
1
- # Project Goal
1
+ # {{PROJECT_NAME}}
2
2
 
3
- {{PROJECT_GOAL}}
3
+ > {{PROJECT_GOAL}}
4
4
 
5
- ## R1 Progressive Loading
5
+ ## Project-Specific Rules
6
6
 
7
- Keep this file short because every agent session loads it. Do trivial work directly. For non-trivial product, architecture, implementation, or debug work, first load the relevant local guidance or skill, then proceed.
7
+ <!-- 在这里写本项目的稳定规则,例如:
8
+ - 代码风格 / 注释语言
9
+ - 提交规范
10
+ - 协作偏好
11
+ 这些规则会被 Claude Code / Codex 在每次 session 自动加载。
12
+ 未来新规则直接追加到本段,不需要其他工具命令。 -->
8
13
 
9
- ## Spec Landing Rules
14
+ ## Stack
10
15
 
11
- - SDD owns what to build; TDD owns how to prove it works.
12
- - After writing or changing anything under `spec/needs/<need-name>/`, update `spec/INDEX.md`.
13
- - If `spec/INDEX.md` cannot be updated confidently, run the future `index-rebuild` command or leave an explicit handoff note.
14
- - Do not write `prd.md` before requirement alignment.
15
- - Do not fix a bug before expected behavior is aligned.
16
+ TODO: 简述项目技术栈与运行环境。
16
17
 
17
- ## Project Specific Rules
18
+ ## Structure
18
19
 
19
- <!-- AE:PROJECT:BEGIN -->
20
- <!-- Add project-specific rules here. `ae init` manages AE:RULES separately. -->
21
- <!-- AE:PROJECT:END -->
20
+ TODO: 简述目录结构,标注关键入口。
21
+
22
+ ## Operating Guidelines
23
+
24
+ TODO: 简述代码、测试、提交、PR 的操作规范。
@@ -1,21 +1,5 @@
1
- # Project Goal
1
+ # {{PROJECT_NAME}}
2
2
 
3
- {{PROJECT_GOAL}}
3
+ **Always read [AGENTS.md](./AGENTS.md) at the start of every session before doing any work.**
4
4
 
5
- ## R1 Progressive Loading
6
-
7
- Keep this file short because every agent session loads it. Do trivial work directly. For non-trivial product, architecture, implementation, or debug work, first load the relevant local guidance or skill, then proceed.
8
-
9
- ## Spec Landing Rules
10
-
11
- - SDD owns what to build; TDD owns how to prove it works.
12
- - After writing or changing anything under `spec/needs/<need-name>/`, update `spec/INDEX.md`.
13
- - If `spec/INDEX.md` cannot be updated confidently, run the future `index-rebuild` command or leave an explicit handoff note.
14
- - Do not write `prd.md` before requirement alignment.
15
- - Do not fix a bug before expected behavior is aligned.
16
-
17
- ## Project Specific Rules
18
-
19
- <!-- AE:PROJECT:BEGIN -->
20
- <!-- Add project-specific rules here. `ae init` manages AE:RULES separately. -->
21
- <!-- AE:PROJECT:END -->
5
+ This project's instructions, rules, structure, and operating guidelines are all defined in `AGENTS.md`.
@@ -1,17 +0,0 @@
1
- # Project-Specific Rules
2
-
3
- <!-- 这个文件在 `ae init` 时会被读取,内容会注入到 AGENTS.md 与 CLAUDE.md 的
4
- AE:RULES block 中。可以在 `ae setup` 之后、`ae init` 之前编辑。 -->
5
-
6
- ## 通用约束
7
-
8
- - 例如:所有代码注释使用中文
9
- - 例如:提交信息遵循 Conventional Commits
10
-
11
- ## 项目栈
12
-
13
- - 例如:Node.js 18+,TypeScript strict mode
14
-
15
- ## 协作偏好
16
-
17
- - 例如:小步提交,每个 commit 必须包含测试