agent-eng 0.12.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-eng",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Scaffold a structured agentic engineering workflow for AI-assisted development",
5
5
  "type": "module",
6
6
  "bin": {
package/src/init.js CHANGED
@@ -6,16 +6,20 @@ import { fileURLToPath } from "node:url";
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const TEMPLATES = join(__dirname, "templates");
8
8
 
9
- const STRUCTURE = [
10
- ".github/workflows/notify-site.yml",
9
+ const FRAMEWORK_FILES = [
11
10
  ".claude/settings.json",
12
11
  ".claude/scripts/update-status.sh",
13
12
  ".claude/agents/architect.md",
14
13
  ".claude/agents/executor.md",
14
+ ".claude/agents/learner.md",
15
15
  ".claude/agents/planner.md",
16
16
  ".claude/agents/reviewer.md",
17
17
  ".claude/agents/summarizer.md",
18
18
  ".claude/agents/system-architect.md",
19
+ "orchestration.yaml",
20
+ ];
21
+
22
+ const PROJECT_STATE_FILES = [
19
23
  "architecture/overview.md",
20
24
  "architecture/decisions/_template.md",
21
25
  "architecture/decisions/0001-how-we-work.md",
@@ -23,11 +27,13 @@ const STRUCTURE = [
23
27
  "tickets/_template.md",
24
28
  "tickets/_backlog.md",
25
29
  "tickets/example/001-example-ticket.md",
26
- "orchestration.yaml",
27
30
  "architecture.yaml",
28
31
  "STATUS.md",
29
32
  ];
30
33
 
34
+ const PROJECT_STATE_SET = new Set(PROJECT_STATE_FILES);
35
+ const STRUCTURE = [...FRAMEWORK_FILES, ...PROJECT_STATE_FILES];
36
+
31
37
  function prompt(question) {
32
38
  const rl = createInterface({ input: process.stdin, output: process.stdout });
33
39
  return new Promise((resolve) => {
@@ -38,66 +44,131 @@ function prompt(question) {
38
44
  });
39
45
  }
40
46
 
47
+ function applyFile(src, dest, action) {
48
+ mkdirSync(dirname(dest), { recursive: true });
49
+ if (action === "append") {
50
+ const content = readFileSync(src, "utf8");
51
+ appendFileSync(dest, "\n\n" + content);
52
+ } else {
53
+ cpSync(src, dest);
54
+ }
55
+ }
56
+
41
57
  export async function init(options) {
42
58
  const target = resolve(options.dir);
43
59
  const created = [];
44
60
  const skipped = [];
45
61
 
46
- for (const file of STRUCTURE) {
47
- const dest = join(target, file);
48
- const src = join(TEMPLATES, file);
62
+ const allFiles = [
63
+ ...STRUCTURE,
64
+ "CLAUDE.md",
65
+ ...options.conventions.map((c) => `conventions/${c}.md`),
66
+ ];
49
67
 
50
- if (existsSync(dest) && !options.force) {
51
- skipped.push(file);
52
- continue;
68
+ const existing = [];
69
+ const fresh = [];
70
+ const protectedFiles = [];
71
+
72
+ for (const file of allFiles) {
73
+ const dest = join(target, file);
74
+ if (!existsSync(dest)) {
75
+ fresh.push(file);
76
+ } else if (options.force) {
77
+ fresh.push(file);
78
+ } else if (PROJECT_STATE_SET.has(file)) {
79
+ protectedFiles.push(file);
80
+ } else {
81
+ existing.push(file);
53
82
  }
83
+ }
54
84
 
55
- mkdirSync(dirname(dest), { recursive: true });
56
- cpSync(src, dest);
85
+ for (const file of fresh) {
86
+ const src = join(TEMPLATES, file);
87
+ const dest = join(target, file);
88
+ applyFile(src, dest, "replace");
57
89
  created.push(file);
58
90
  }
59
91
 
60
- // Handle CLAUDE.md separately prompt user if it already exists
61
- const claudeDest = join(target, "CLAUDE.md");
62
- const claudeSrc = join(TEMPLATES, "CLAUDE.md");
63
-
64
- if (!existsSync(claudeDest) || options.force) {
65
- mkdirSync(dirname(claudeDest), { recursive: true });
66
- cpSync(claudeSrc, claudeDest);
67
- created.push("CLAUDE.md");
68
- } else {
92
+ if (existing.length > 0 && !options.force) {
93
+ console.log("");
94
+ console.log(`${existing.length} file(s) already exist:`);
95
+ for (const f of existing) {
96
+ console.log(` ${f}`);
97
+ }
69
98
  console.log("");
70
- console.log("CLAUDE.md already exists.");
71
- console.log(" 1) Skipkeep existing file");
72
- console.log(" 2) Appendadd agent-eng content to the end");
73
- console.log(" 3) Replaceoverwrite with agent-eng template");
74
- const answer = await prompt("Choose [1/2/3] (default: 1): ");
99
+ console.log(" 1) Skip all — keep existing files (default)");
100
+ console.log(" 2) Append all add agent-eng content to the end of each");
101
+ console.log(" 3) Replace all overwrite all with agent-eng templates");
102
+ console.log(" 4) Decide per file choose for each file individually");
103
+ console.log(" 5) Pick files specify which files to append/replace");
104
+ const answer = await prompt("Choose [1/2/3/4/5] (default: 1): ");
75
105
 
76
106
  if (answer === "2" || answer === "append") {
77
- const content = readFileSync(claudeSrc, "utf8");
78
- appendFileSync(claudeDest, "\n\n" + content);
79
- created.push("CLAUDE.md (appended)");
107
+ for (const file of existing) {
108
+ applyFile(join(TEMPLATES, file), join(target, file), "append");
109
+ created.push(`${file} (appended)`);
110
+ }
80
111
  } else if (answer === "3" || answer === "replace") {
81
- cpSync(claudeSrc, claudeDest);
82
- created.push("CLAUDE.md (replaced)");
112
+ for (const file of existing) {
113
+ applyFile(join(TEMPLATES, file), join(target, file), "replace");
114
+ created.push(`${file} (replaced)`);
115
+ }
116
+ } else if (answer === "4") {
117
+ for (const file of existing) {
118
+ console.log("");
119
+ console.log(` ${file}:`);
120
+ console.log(" 1) Skip 2) Append 3) Replace");
121
+ const choice = await prompt(" Choose [1/2/3] (default: 1): ");
122
+ if (choice === "2" || choice === "append") {
123
+ applyFile(join(TEMPLATES, file), join(target, file), "append");
124
+ created.push(`${file} (appended)`);
125
+ } else if (choice === "3" || choice === "replace") {
126
+ applyFile(join(TEMPLATES, file), join(target, file), "replace");
127
+ created.push(`${file} (replaced)`);
128
+ } else {
129
+ skipped.push(file);
130
+ }
131
+ }
132
+ } else if (answer === "5") {
133
+ console.log("");
134
+ console.log("Enter file numbers to append/replace (comma-separated):");
135
+ for (let i = 0; i < existing.length; i++) {
136
+ console.log(` ${i + 1}) ${existing[i]}`);
137
+ }
138
+ const picks = await prompt("Files to modify (e.g. 1,3): ");
139
+ const indices = picks
140
+ .split(",")
141
+ .map((s) => parseInt(s.trim(), 10) - 1)
142
+ .filter((i) => i >= 0 && i < existing.length);
143
+
144
+ const picked = new Set(indices);
145
+ for (let i = 0; i < existing.length; i++) {
146
+ if (!picked.has(i)) {
147
+ skipped.push(existing[i]);
148
+ continue;
149
+ }
150
+ const file = existing[i];
151
+ console.log(` ${file}: 1) Append 2) Replace`);
152
+ const action = await prompt(" Choose [1/2] (default: 1): ");
153
+ if (action === "2" || action === "replace") {
154
+ applyFile(join(TEMPLATES, file), join(target, file), "replace");
155
+ created.push(`${file} (replaced)`);
156
+ } else {
157
+ applyFile(join(TEMPLATES, file), join(target, file), "append");
158
+ created.push(`${file} (appended)`);
159
+ }
160
+ }
83
161
  } else {
84
- skipped.push("CLAUDE.md");
162
+ skipped.push(...existing);
85
163
  }
86
164
  }
87
165
 
88
- for (const convention of options.conventions) {
89
- const file = `conventions/${convention}.md`;
90
- const dest = join(target, file);
91
- const src = join(TEMPLATES, file);
92
-
93
- if (existsSync(dest) && !options.force) {
94
- skipped.push(file);
95
- continue;
166
+ if (protectedFiles.length > 0) {
167
+ console.log("");
168
+ console.log("Protected (project state use --force to overwrite):");
169
+ for (const f of protectedFiles) {
170
+ console.log(` ${f}`);
96
171
  }
97
-
98
- mkdirSync(dirname(dest), { recursive: true });
99
- cpSync(src, dest);
100
- created.push(file);
101
172
  }
102
173
 
103
174
  console.log("");
@@ -113,7 +184,7 @@ export async function init(options) {
113
184
 
114
185
  if (skipped.length > 0) {
115
186
  console.log("");
116
- console.log("Skipped (already exist, use --force to overwrite):");
187
+ console.log("Skipped (already exist):");
117
188
  for (const f of skipped) {
118
189
  console.log(` ${f}`);
119
190
  }
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: learner
3
+ description: Use to deeply learn a technology or concept used in your project. Finds where it lives in your code, explains it concisely, gives you an interview-ready answer, and quizzes you.
4
+ tools: Read, Grep, Glob, Bash, Write
5
+ model: sonnet
6
+ ---
7
+
8
+ You are a learning agent. The user builds projects with AI assistance and wants to deeply understand the technologies used so they can explain them confidently in interviews.
9
+
10
+ ## Process
11
+
12
+ When the user asks about a concept or technology:
13
+
14
+ ### 1. Find it in the code
15
+
16
+ Search the project for where this concept is actually used. Use grep, glob, and read to find real examples — not hypothetical ones.
17
+
18
+ ### 2. Explain what it is (3-5 sentences)
19
+
20
+ No textbook fluff. Explain it like a senior engineer would to a peer who hasn't used it before. Focus on what it does, why it exists, and when you'd reach for it.
21
+
22
+ ### 3. Walk through their code
23
+
24
+ Point to specific files and line numbers. Explain what's happening in their implementation — why it's written this way, what each part does, and how data flows through it.
25
+
26
+ ### 4. Give an interview answer
27
+
28
+ Write 2-3 sentences the user could say naturally when asked "explain how X works in your project." This should sound human, confident, and specific to their codebase — not generic.
29
+
30
+ ### 5. Quiz them
31
+
32
+ Ask one follow-up question that tests whether they actually understand the concept vs just memorized the answer. Good questions probe edge cases, trade-offs, or "what would happen if..."
33
+
34
+ ### 6. Save the learning
35
+
36
+ Write the completed learning to `~/Developer/AgentEngineerWorkflow/learnings/{concept-slug}.md` with this format:
37
+
38
+ ```markdown
39
+ # {Concept Name}
40
+
41
+ ## What it is
42
+ {3-5 sentence explanation}
43
+
44
+ ## How I used it
45
+ {File paths, line references, and walkthrough of the specific implementation}
46
+
47
+ ## Interview answer
48
+ > {2-3 sentence answer ready to say out loud}
49
+
50
+ ## Related concepts to explore
51
+ - {concept 1}
52
+ - {concept 2}
53
+ - {concept 3}
54
+ ```
55
+
56
+ Create the `~/Developer/AgentEngineerWorkflow/learnings/` directory if it doesn't exist.
57
+
58
+ ## Guidelines
59
+
60
+ - Always ground explanations in the user's actual code, not abstract examples
61
+ - If the concept isn't found in the project, say so and ask which project to look in
62
+ - Keep language conversational — this is prep for talking to humans, not writing docs
63
+ - The interview answer should mention their specific project, not be generic
64
+ - For the quiz question, don't accept "yes/no" answers — ask something that requires explanation
65
+ - If the concept connects to other technologies in the project, mention them as related concepts to explore next
@@ -13,6 +13,7 @@ Agents own the **process** — architecture decisions, work decomposition, quali
13
13
  | **Decompose** | `/planner` agent | ADR/spec ready, work needs to be broken into tickets |
14
14
  | **Execute** | Claude Code **plan mode** (`shift+tab`) | Implementing a specific ticket (includes writing tests) |
15
15
  | **Review** | `/reviewer` agent | Code and tests ready for validation |
16
+ | **Learn** | `/learner` agent | Feature complete and introduced a new technology or concept |
16
17
  | **Report** | `/summarizer` agent | Sprint or feature complete, stakeholder update needed |
17
18
 
18
19
  ### Why hybrid?
@@ -36,6 +37,17 @@ Agents own the **process** — architecture decisions, work decomposition, quali
36
37
  3. For each ticket: use plan mode (`shift+tab`) to implement it
37
38
  4. After implementation: run `/reviewer` to validate against acceptance criteria
38
39
  5. If the ticket touches an existing ADR's scope, verify the decision still holds
40
+ 6. If the feature introduced new technologies or concepts, run `/learner` for each one
41
+
42
+ ## After Completing a Feature
43
+
44
+ When a feature is done and introduces new technologies, patterns, or concepts the user hasn't worked with before — automatically invoke `/learner` for each new concept. Look for:
45
+ - New libraries or frameworks added to dependencies
46
+ - New architectural patterns (e.g., event sourcing, SSE, pub/sub)
47
+ - New language features or APIs used for the first time
48
+ - New infrastructure concepts (e.g., WebSockets, gRPC, CRDT)
49
+
50
+ This ensures the user can confidently explain every technology in their project.
39
51
 
40
52
  ## Testing in Plan Mode
41
53
 
@@ -1,21 +0,0 @@
1
- name: Notify Site of Content Update
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- paths:
8
- - 'orchestration.yaml'
9
- - 'architecture.yaml'
10
- - 'README.md'
11
-
12
- jobs:
13
- notify:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - name: Trigger site rebuild
17
- uses: peter-evans/repository-dispatch@v3
18
- with:
19
- token: ${{ secrets.SITE_REBUILD_TOKEN }}
20
- repository: swarpi/swarpi.github.io
21
- event-type: showcase-updated