@relipa/ai-flow-kit 0.2.0 → 0.2.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/custom/rules/java/spring-boot-rules.md +209 -0
- package/custom/rules/javascript/nestjs-examples.md +41 -0
- package/custom/rules/javascript/nestjs-rules.md +42 -0
- package/custom/rules/javascript/nodejs-express-examples.md +35 -0
- package/custom/rules/javascript/nodejs-express-rules.md +49 -0
- package/custom/rules/javascript/reactjs-examples.md +380 -0
- package/custom/rules/javascript/reactjs-rules.md +173 -0
- package/custom/rules/php/php-examples.md +161 -0
- package/custom/rules/php/php-rules.md +127 -0
- package/custom/rules/python/python-django-examples.md +34 -0
- package/custom/rules/python/python-django-rules.md +48 -0
- package/custom/rules/python/python-examples.md +32 -0
- package/custom/rules/python/python-fastapi-examples.md +30 -0
- package/custom/rules/python/python-fastapi-rules.md +35 -0
- package/custom/rules/python/python-ml-examples.md +187 -0
- package/custom/rules/python/python-ml-rules.md +121 -0
- package/custom/rules/python/python-rules.md +58 -0
- package/custom/skills/ba-skills/skill-ba-qna-template-v1.md +4 -4
- package/custom/skills/ba-skills/skill-ba-qna-v1.md +6 -0
- package/custom/skills/create-system-requirement/SKILL.md +52 -16
- package/custom/skills/create-system-requirement/system-requirement-template-v1.md +128 -0
- package/custom/skills/impact-analysis/SKILL.md +106 -106
- package/custom/skills/report-customer/SKILL.md +99 -99
- package/custom/templates/nestjs.md +5 -72
- package/custom/templates/nodejs-express.md +5 -73
- package/custom/templates/php-plain.md +5 -261
- package/custom/templates/php.md +5 -261
- package/custom/templates/python-django.md +5 -71
- package/custom/templates/python-fastapi.md +5 -54
- package/custom/templates/python-ml.md +1 -269
- package/custom/templates/python.md +5 -79
- package/custom/templates/reactjs.md +5 -492
- package/custom/templates/shared/gate-workflow.md +1 -0
- package/custom/templates/shared/ml-gate-workflow.md +1 -0
- package/custom/templates/spring-boot.md +5 -224
- package/docs/common/CHANGELOG.md +20 -10
- package/package.json +1 -1
- package/scripts/init.js +143 -40
|
@@ -1,224 +1,5 @@
|
|
|
1
|
-
# Spring Boot AI System Prompt
|
|
2
|
-
|
|
3
|
-
You are an expert Java Spring Boot developer. Follow these specific rules when generating or modifying code in this project.
|
|
4
|
-
|
|
5
|
-
> **
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Project Stack
|
|
10
|
-
|
|
11
|
-
- **Java:** 17+
|
|
12
|
-
- **Spring Boot:** 3.x
|
|
13
|
-
- **Build Tool:** Maven (prefer) or Gradle
|
|
14
|
-
- **ORM:** Spring Data JPA + Hibernate
|
|
15
|
-
- **Database:** MySQL / PostgreSQL
|
|
16
|
-
- **Testing:** JUnit 5 + Mockito + AssertJ
|
|
17
|
-
- **Utilities:** Lombok, MapStruct
|
|
18
|
-
- **API Style:** RESTful JSON
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Architecture
|
|
23
|
-
|
|
24
|
-
Follow strict **Layered Architecture**:
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
Controller (HTTP layer)
|
|
28
|
-
↓
|
|
29
|
-
Service (Business logic)
|
|
30
|
-
↓
|
|
31
|
-
Repository (Data access - Spring Data JPA)
|
|
32
|
-
↓
|
|
33
|
-
Entity / Domain Model
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Package Structure
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
com.company.project/
|
|
40
|
-
├── controller/ # \@RestController — HTTP endpoints only
|
|
41
|
-
├── service/
|
|
42
|
-
│ ├── impl/ # \@Service — business logic implementation
|
|
43
|
-
│ └── [Interface].java
|
|
44
|
-
├── repository/ # \@Repository — extends JpaRepository
|
|
45
|
-
├── entity/ # \@Entity — JPA entities
|
|
46
|
-
├── dto/
|
|
47
|
-
│ ├── request/ # Input DTOs (e.g. CreateUserRequest)
|
|
48
|
-
│ └── response/ # Output DTOs (e.g. UserResponse)
|
|
49
|
-
├── mapper/ # MapStruct mappers (Entity ↔ DTO)
|
|
50
|
-
├── exception/
|
|
51
|
-
│ ├── GlobalExceptionHandler.java # \@RestControllerAdvice
|
|
52
|
-
│ └── [CustomException].java
|
|
53
|
-
├── config/ # \@Configuration classes
|
|
54
|
-
└── util/ # Pure utility helpers (stateless)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## Controller Rules
|
|
60
|
-
|
|
61
|
-
- Annotate with `\@RestController` + `\@RequestMapping`
|
|
62
|
-
- **Only** handle HTTP concerns: parse request, call service, return response
|
|
63
|
-
- Never put business logic in Controller
|
|
64
|
-
- Always use DTOs — never expose Entity directly
|
|
65
|
-
- Use `ResponseEntity<T>` for explicit HTTP status control
|
|
66
|
-
- Validate input with `\@Valid` + Bean Validation annotations
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Service Rules
|
|
71
|
-
|
|
72
|
-
- Always define an **interface**, implement in the `impl/` package
|
|
73
|
-
- Annotate implementation with `\@Service`
|
|
74
|
-
- All business logic lives here
|
|
75
|
-
- Use `\@Transactional` at the method level (not class level)
|
|
76
|
-
- Throw specific custom exceptions, not generic `RuntimeException`
|
|
77
|
-
- Never return an Entity — always convert to DTO via Mapper
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## Repository Rules
|
|
82
|
-
|
|
83
|
-
- Extend `JpaRepository<Entity, ID>`
|
|
84
|
-
- Use **method name queries** for simple queries
|
|
85
|
-
- Use `\@Query` (JPQL) for complex queries — avoid native SQL unless necessary
|
|
86
|
-
- Never add business logic here
|
|
87
|
-
- Use `\@EntityGraph` to solve N+1 problems
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Entity Rules
|
|
92
|
-
|
|
93
|
-
- Use Lombok: `\@Getter`, `\@Setter`, `\@NoArgsConstructor`, `\@AllArgsConstructor`, `\@Builder`
|
|
94
|
-
- Avoid `\@Data` on entities (causes issues with `equals/hashCode` + lazy loading)
|
|
95
|
-
- Always use `\@Table(name = "snake_case_table_name")`
|
|
96
|
-
- Use `\@Column(name = "snake_case_column_name")` explicitly
|
|
97
|
-
- For soft delete: add `deleted` boolean + `deletedAt` timestamp
|
|
98
|
-
- Extend `BaseEntity` for audit fields (`createdAt`, `updatedAt`)
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## DTO Rules
|
|
103
|
-
|
|
104
|
-
- Use separate DTOs for **Request** and **Response** — never share
|
|
105
|
-
- Use Lombok: `\@Getter`, `\@Builder`, `\@AllArgsConstructor`, `\@NoArgsConstructor`
|
|
106
|
-
- Validate in the Request DTO with Bean Validation (`\@NotBlank`, `\@Email`, `\@NotNull`, `\@Size`)
|
|
107
|
-
- Never expose internal fields (password hash, audit timestamps) in the Response DTO
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## MapStruct Mapper Rules
|
|
112
|
-
|
|
113
|
-
- Use `\@Mapper(componentModel = "spring")` — inject as a Spring bean
|
|
114
|
-
- Define explicit mappings with `\@Mapping` when field names differ
|
|
115
|
-
- Never do manual mapping (`new DTO(); dto.setField(entity.getField())`)
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Exception Handling
|
|
120
|
-
|
|
121
|
-
- Create custom exceptions extending `RuntimeException`
|
|
122
|
-
- Handle all exceptions in one `\@RestControllerAdvice` class
|
|
123
|
-
- Return a consistent error response format
|
|
124
|
-
- Never expose stack traces to the client
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## Testing Rules
|
|
129
|
-
|
|
130
|
-
### Unit Tests (Service layer)
|
|
131
|
-
- Test class: `[ServiceImpl]Test.java`
|
|
132
|
-
- Mock all dependencies with `\@ExtendWith(MockitoExtension.class)` + `\@Mock`
|
|
133
|
-
- Test happy path + edge cases + exception scenarios
|
|
134
|
-
- Use AssertJ: `assertThat(result).isEqualTo(expected)`
|
|
135
|
-
|
|
136
|
-
### Integration Tests (Controller layer)
|
|
137
|
-
- Use `\@SpringBootTest` + `\@AutoConfigureMockMvc`
|
|
138
|
-
- Test full HTTP flow with `MockMvc`
|
|
139
|
-
- Use `\@Sql` or Testcontainers for database state
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Naming Conventions
|
|
144
|
-
|
|
145
|
-
| Element | Convention | Example |
|
|
146
|
-
|---------|-----------|---------|
|
|
147
|
-
| Class | PascalCase | `UserService`, `OrderController` |
|
|
148
|
-
| Method | camelCase | `findById`, `createOrder` |
|
|
149
|
-
| Variable | camelCase | `userResponse`, `orderId` |
|
|
150
|
-
| Constant | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
|
151
|
-
| Package | lowercase | `com.company.project.service` |
|
|
152
|
-
| DB Table | snake_case | `user_orders`, `product_items` |
|
|
153
|
-
| DB Column | snake_case | `created_at`, `user_id` |
|
|
154
|
-
| REST endpoint | kebab-case | `/api/v1/user-profiles` |
|
|
155
|
-
| Request DTO | `[Action][Resource]Request` | `CreateOrderRequest` |
|
|
156
|
-
| Response DTO | `[Resource]Response` | `OrderResponse` |
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## API Design
|
|
161
|
-
|
|
162
|
-
- Version all APIs: `/api/v1/...`
|
|
163
|
-
- Use plural nouns for resources: `/users`, `/orders`
|
|
164
|
-
- HTTP methods: `GET` (read), `POST` (create), `PUT` (full update), `PATCH` (partial), `DELETE`
|
|
165
|
-
- Return `201 Created` for POST, `200 OK` for GET/PUT/PATCH, `204 No Content` for DELETE
|
|
166
|
-
- Use consistent pagination: `?page=0&size=20&sort=createdAt,desc`
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
GET /api/v1/users → 200 list
|
|
170
|
-
POST /api/v1/users → 201 created
|
|
171
|
-
GET /api/v1/users/{id} → 200 or 404
|
|
172
|
-
PUT /api/v1/users/{id} → 200 or 404
|
|
173
|
-
DELETE /api/v1/users/{id} → 204 or 404
|
|
174
|
-
GET /api/v1/users/{id}/orders → 200 nested resource
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## Performance Rules
|
|
180
|
-
|
|
181
|
-
- Always use pagination — never return unbounded lists
|
|
182
|
-
- Avoid N+1: use `\@EntityGraph` or `JOIN FETCH` in JPQL
|
|
183
|
-
- Add database indexes on frequently queried columns
|
|
184
|
-
- Use `\@Transactional(readOnly = true)` on read-only service methods
|
|
185
|
-
- For heavy read operations, consider projection interfaces
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## Security Rules
|
|
190
|
-
|
|
191
|
-
- Never log passwords, tokens, or sensitive PII
|
|
192
|
-
- Hash passwords with BCrypt: `passwordEncoder.encode(rawPassword)`
|
|
193
|
-
- Validate and sanitize all user inputs via Bean Validation
|
|
194
|
-
- Use `\@PreAuthorize` for method-level security
|
|
195
|
-
- Never return stack traces to API consumers
|
|
196
|
-
- Store secrets in environment variables / Vault — never in code
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## Logging Rules
|
|
201
|
-
|
|
202
|
-
- Use SLF4J with Lombok `\@Slf4j`
|
|
203
|
-
- `log.info` — normal business events
|
|
204
|
-
- `log.warn` — recoverable issues (not found, validation fail)
|
|
205
|
-
- `log.error` — unexpected exceptions (always include `ex` as the second argument)
|
|
206
|
-
- Never log sensitive data (password, credit card, token)
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Common Anti-Patterns to Avoid
|
|
211
|
-
|
|
212
|
-
- ❌ `\@Autowired` field injection → use constructor injection (Lombok `\@RequiredArgsConstructor`)
|
|
213
|
-
- ❌ `\@Data` on JPA entities → use `\@Getter \@Setter` separately
|
|
214
|
-
- ❌ Returning `Entity` directly from Controller → always use DTO
|
|
215
|
-
- ❌ `SELECT *` or unbounded `findAll()` → always paginate
|
|
216
|
-
- ❌ Business logic in Controller → move to Service
|
|
217
|
-
- ❌ Catching and swallowing exceptions → handle properly or rethrow
|
|
218
|
-
- ❌ `new RuntimeException("something")` → create a specific custom exception
|
|
219
|
-
- ❌ Hardcoding config values → use `\@Value` or `\@ConfigurationProperties`
|
|
220
|
-
- ❌ `\@Transactional` on Controller → only on Service methods
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
When explaining changes, refer to the [Spring Boot Official Documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/) and [Spring Data JPA](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/) conventions.
|
|
1
|
+
# Spring Boot AI System Prompt
|
|
2
|
+
|
|
3
|
+
You are an expert Java Spring Boot developer. Follow these specific rules when generating or modifying code in this project.
|
|
4
|
+
|
|
5
|
+
> **Rules & code examples:** Read `.rules/java/spring-boot-rules.md` (architecture, layer rules, naming, security, anti-patterns) and `.rules/java/spring-boot-examples.md` (code samples per layer) **in full** before writing or modifying any Java code in this project.
|
package/docs/common/CHANGELOG.md
CHANGED
|
@@ -7,17 +7,9 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## [
|
|
11
|
-
|
|
12
|
-
### Added
|
|
13
|
-
|
|
14
|
-
- **`ak gate <n> skip --ticket <id> [--reason <text>]`** — a gate can now close as "skipped" (legitimately produced no deliverable) instead of only `start`/`approved`. First consumer: `ingest-data` Gate 3, when the AI decides the ingested content is reference-only and no task list is needed — it now runs `ak gate 3 skip` instead of leaving the gate untouched. `scripts/task.js`'s `updateTaskGateState()` gained a `skip` branch that records `skippedGates[gate] = {at, reason}` in `task-state.json` and advances `currentGate` exactly like `approved` does, so every existing "is this task done" check (which only ever inspects `currentGate`) keeps working with zero changes. Prompted by an impact assessment for the `ai-flow-ex` VS Code extension's dashboard, which was showing skipped-Gate-3 ingest-data tasks stuck as "ready to run Gate 3" forever — see `docs/superpowers/specs/2026-08-06-ingest-data-gate3-skipped-state-design.md` in `ai-flow-ex` for the paired UI-side change (reads `skippedGates` to render a distinct "⏭ Skipped" badge instead of the generic "✓ Approved").
|
|
15
|
-
|
|
16
|
-
### Fixed
|
|
17
|
-
|
|
18
|
-
- **`ak task next` treated `ingest-data` as a 2-gate flow** — a stale assumption left over from before Gate 3 (task creation & ticket generation) was added to `ingest-data` earlier in the same release cycle. `scripts/task.js` grouped it with `gen-doc`/`create-system-requirement` (`maxGate = 2`), so pausing via `ak task next` on Gate 2 would prematurely mark the task `status: 'done'` and `gateLabel(3, 'ingest-data')` returned `'Done'` instead of `'Sinh Task & Tạo Ticket'`. Split into its own 3-gate branch (`maxGate = 3`); `gen-doc`/`create-system-requirement` are unaffected.
|
|
10
|
+
## [Unreleased]
|
|
19
11
|
|
|
20
|
-
## [0.2.1] - 2026-08-
|
|
12
|
+
## [0.2.1] - 2026-08-17
|
|
21
13
|
|
|
22
14
|
### Added
|
|
23
15
|
|
|
@@ -30,6 +22,24 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
30
22
|
- Approved tickets get their link written back into the originating `Meetings-Log`/`QnA-Log`/`Confirmations-Log` entry (`→ Tasks created: TICKET-101 (coding), ...`), keeping the traceability chain from PM Workflow §6 intact end-to-end.
|
|
31
23
|
- **Still open, tracked in the PM Workflow doc's Vấn đề 5 implementation note:** no fixed feedback→task-type rule table (AI still judges per-context); new tickets don't auto-attach `functionId`/labels (Dev/BA/QA still assign on `ak use TICKET-XXX`); Backlog issueType/priority default to the project's first issueType and a "Normal"-named priority unless overridden explicitly in the task JSON.
|
|
32
24
|
|
|
25
|
+
- **`ak gate <n> skip --ticket <id> [--reason <text>]`** — a gate can now close as "skipped" (legitimately produced no deliverable) instead of only `start`/`approved`. First consumer: `ingest-data` Gate 3, when the AI decides the ingested content is reference-only and no task list is needed — it now runs `ak gate 3 skip` instead of leaving the gate untouched. `scripts/task.js`'s `updateTaskGateState()` gained a `skip` branch that records `skippedGates[gate] = {at, reason}` in `task-state.json` and advances `currentGate` exactly like `approved` does, so every existing "is this task done" check (which only ever inspects `currentGate`) keeps working with zero changes. Prompted by an impact assessment for the `ai-flow-ex` VS Code extension's dashboard, which was showing skipped-Gate-3 ingest-data tasks stuck as "ready to run Gate 3" forever — see `docs/superpowers/specs/2026-08-06-ingest-data-gate3-skipped-state-design.md` in `ai-flow-ex` for the paired UI-side change (reads `skippedGates` to render a distinct "⏭ Skipped" badge instead of the generic "✓ Approved").
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **Per-framework rule sections (Architecture, layer rules, naming, security, anti-patterns) moved out of `CLAUDE.md`/`AGENTS.md` into `.rules/<lang>/<framework>-rules.md`, alongside the existing `-examples.md`.** Extends the same on-demand `.rules/` pattern already used for code samples to the rules themselves: `custom/templates/<framework>.md` now holds only the title, a one-line intro, and a single "> **Rules & code examples:**" pointer to both files — everything else (Controller/Service/Repository/Entity/DTO/Mapper/Exception/Testing/Naming/API/Performance/Security/Logging/anti-patterns, per framework) lives in the new `-rules.md` file. Applied to all 9 templates that had this structure (`spring-boot`, `php`, `php-plain`, `reactjs`, `nestjs`, `nodejs-express`, `python`, `python-django`, `python-fastapi`, `python-ml`) — `nextjs`/`vue-nuxt`/`laravel` were already ~15 lines with no such structure and are untouched. Unlike the workflow-file pointer (read once at a clear Gate-start trigger), these rules apply continuously across every code edit in Gate 3, so `custom/templates/shared/gate-workflow.md` and `ml-gate-workflow.md` both gained an explicit bullet at the top of their Gate 3 (`Code Generation` / `Implement, Train & Iterate`) step: read the rules + examples files pointed to at the top of `CLAUDE.md`/`AGENTS.md` before the first line of code, and keep applying them to every subsequent edit, not just the first. Also fixed `generateSkillNameList()`'s "Codex indexes these automatically" wording, which became inaccurate once `claude` also uses the compact skill list (see previous entry) — reworded to name neither tool specifically. Measured on the kit's own dogfood skill set: solo `spring-boot`/`reactjs`/`php` 247/211/~205 → **41 lines each**; multi `spring-boot`+`php` 385 → **55 lines**.
|
|
30
|
+
|
|
31
|
+
- **`CLAUDE.md` now uses the same compact skill list as `AGENTS.md`, and 2 techstack templates dropped a redundant "Project Stack" section.** `AI_TOOL_SKILL_LIST_STYLE` gained `'claude': 'compact'` — Claude Code auto-discovers each skill's name + description from `.claude/skills/*/SKILL.md` frontmatter on its own (same mechanism the original Codex-only rationale assumed only Codex had), so restating the full description table in `CLAUDE.md` was duplicating information the tool already surfaces natively. `custom/templates/spring-boot.md` and `reactjs.md` also dropped their "## Project Stack" bullet list (Java/Spring Boot/Maven version pins, React/TypeScript/Vite/Router/etc.) — for spring-boot every item was already restated in a more specific rule section further down (Testing Rules, Entity Rules, Mapper Rules...); for reactjs most items were too (State Management, Form, Styling, API Layer Rules), except **Router (React Router v6), the shadcn/ui pairing with Tailwind, and Vitest as the specific test runner**, which had no other home in the file and are now gone rather than relocated — worth restoring into an existing rule section later if that specificity is missed. Measured on the kit's own dogfood skill set (~46 skills): solo `spring-boot` 306 → 247 lines, solo `reactjs` ~264 → 211 lines, multi `spring-boot`+`php` 444 → 385 lines.
|
|
32
|
+
|
|
33
|
+
- **`CLAUDE.md` now uses the pointer layout too, not just `AGENTS.md`.** `AI_TOOL_LAYOUT` in `scripts/init.js` gained `'claude': 'pointer'` alongside the existing `'codex': 'pointer'` — CLAUDE.md is loaded in full at the start of every session regardless of which gate (if any) is active, so inlining the ~115 KB DEV/TESTER/EXECUTE/gen-doc/ingest-data + BA + QA workflow set spent context on gates most sessions never touch. The workflow bodies now live in `.aiflow/instructions/` and CLAUDE.md just links to them, same as Codex. Unlike Codex's pointer layout, the skill registry keeps its full description table for `claude` (`resolveSkillListStyle()`/`resolveSkillsRoot()`) — Claude Code has no skill index of its own the way Codex does, so only the workflow bodies move out, not the registry.
|
|
34
|
+
- **Techstack rule templates (`custom/templates/<framework>.md`) no longer inline full code samples for every layer.** Following the pattern already used for `spring-boot.md`/`custom/rules/java/spring-boot-examples.md`, the same split was applied to the other 9 templates that had grown a "✅ Good / ❌ Bad" code block per rule area: `reactjs`, `php`, `php-plain`, `python-ml`, `nestjs`, `nodejs-express`, `python`, `python-django`, `python-fastapi`. Each template keeps its bullet rules, naming table, and anti-pattern list, with a one-line pointer to a new `custom/rules/<lang>/<framework>-examples.md` file holding the actual code samples — read on demand when generating code for that specific layer instead of being inlined into every session's context. `FRAMEWORK_LANGUAGE` in `scripts/init.js` also gained entries for `nodejs-express`, `python`, `python-django`, `python-fastapi`, and `php-plain` (previously missing, so `.rules/<lang>/` was never copied into projects using those frameworks).
|
|
35
|
+
- **Multi-framework projects no longer duplicate the tool header, skill registry, and workflow-files pointer once per framework.** A project with 2+ frameworks (e.g. a `spring-boot` backend + a `reactjs` frontend) calls `setupFramework()` once per framework with `multi: true`; each call used to emit a fully self-contained block (tool header + full skill registry + framework rules + "Installed workflow files" pointer note), so all three non-framework-specific sections got repeated verbatim per framework — the ~46-skill registry was the dominant cost in `CLAUDE.md` once the per-framework code samples above were split out. `setupFramework()` now builds all three via `buildSharedBlock()` once and writes it under its own fixed `<!-- aiflow-kit-start:shared -->` marker; per-framework blocks (`buildToolBlock(tool, ..., { includeShared: false })`) carry only the framework-specific rules and (for inline tools) the inlined gate-workflow body. The "Installed workflow files" note moved to the shared block because its 3 listed paths (`.aiflow/instructions/gate-workflow.md`, `create-spec-workflow.md`, `create-testcase-workflow.md`) are always the same 3 filenames regardless of framework — only their on-disk *content* varies per framework (e.g. ML vs standard gate workflow), not the pointer text itself. Re-running for an already-present framework upserts its block in place (new `upsertMarkerBlock()` helper) instead of appending another copy. Single-framework projects are unaffected (`includeShared` defaults to `true`, same single block as before). Measured on the kit's own dogfood project (spring-boot + php, ~46 real skills): 452 → 444 lines.
|
|
36
|
+
|
|
37
|
+
- **QnA-Log table (`skill-ba-qna-template-v1.md`) gained 3 response-tracking columns — Người trả lời, Ngày trả lời, Nguồn.** `skill-ba-qna-v1.md`'s Bước 4 now spells out how each is filled: **Người trả lời** defaults to `git config user.email` (fallback `user.name`) of whoever is running the session, overridden only when the BA explicitly names a different responder (e.g. answering on the customer's behalf); **Ngày trả lời** is stamped automatically from the current system date (`dd/mm/yyyy`), never asked; **Nguồn** is always BA-entered manually (e.g. "Khách hàng", "Nội bộ team", "Team Marketing") with no default or inference. Questions still **Open** leave all three columns blank (`—`). Checklist gained a matching item to catch missing values on **Confirmed** rows.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **`ak task next` treated `ingest-data` as a 2-gate flow** — a stale assumption left over from before Gate 3 (task creation & ticket generation) was added to `ingest-data` earlier in the same release cycle. `scripts/task.js` grouped it with `gen-doc`/`create-system-requirement` (`maxGate = 2`), so pausing via `ak task next` on Gate 2 would prematurely mark the task `status: 'done'` and `gateLabel(3, 'ingest-data')` returned `'Done'` instead of `'Sinh Task & Tạo Ticket'`. Split into its own 3-gate branch (`maxGate = 3`); `gen-doc`/`create-system-requirement` are unaffected.
|
|
42
|
+
|
|
33
43
|
## [0.2.0] - 2026-08-04
|
|
34
44
|
|
|
35
45
|
### Added
|
package/package.json
CHANGED
package/scripts/init.js
CHANGED
|
@@ -25,14 +25,19 @@ function commandExists(cmd) {
|
|
|
25
25
|
|
|
26
26
|
// Map framework → language family for picking the right rules
|
|
27
27
|
const FRAMEWORK_LANGUAGE = {
|
|
28
|
-
'laravel':
|
|
29
|
-
'php':
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
28
|
+
'laravel': 'php',
|
|
29
|
+
'php': 'php',
|
|
30
|
+
'php-plain': 'php',
|
|
31
|
+
'spring-boot': 'java',
|
|
32
|
+
'reactjs': 'javascript',
|
|
33
|
+
'nextjs': 'javascript',
|
|
34
|
+
'vue-nuxt': 'javascript',
|
|
35
|
+
'nestjs': 'javascript',
|
|
36
|
+
'nodejs-express': 'javascript',
|
|
37
|
+
'python-ml': 'python',
|
|
38
|
+
'python': 'python',
|
|
39
|
+
'python-django': 'python',
|
|
40
|
+
'python-fastapi': 'python',
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
// Frameworks that use the ML-specific gate workflow instead of the default one
|
|
@@ -69,8 +74,16 @@ const AI_TOOL_FILES = {
|
|
|
69
74
|
// Codex is 'pointer' because it caps the combined AGENTS.md chain at
|
|
70
75
|
// project_doc_max_bytes (32 KiB default) and silently drops whatever exceeds it.
|
|
71
76
|
// The assembled workflow set is ~115 KB, so inlining would truncate it mid-gate.
|
|
77
|
+
//
|
|
78
|
+
// Claude is 'pointer' for a softer reason: no hard byte cap, but CLAUDE.md is
|
|
79
|
+
// loaded in full at the start of every session regardless of which gate (or none)
|
|
80
|
+
// is actually active, so inlining ~115 KB of DEV/TESTER/EXECUTE/gen-doc/ingest-data
|
|
81
|
+
// + BA + QA workflow text spends context on gates most sessions never touch.
|
|
82
|
+
// Claude Code already reads files on demand via Read/Skill, so a short pointer
|
|
83
|
+
// works the same way a Skill reference does.
|
|
72
84
|
const AI_TOOL_LAYOUT = {
|
|
73
85
|
'codex': 'pointer',
|
|
86
|
+
'claude': 'pointer',
|
|
74
87
|
};
|
|
75
88
|
|
|
76
89
|
/** Return 'inline' or 'pointer' for a tool key. */
|
|
@@ -78,6 +91,39 @@ function resolveToolLayout(tool) {
|
|
|
78
91
|
return AI_TOOL_LAYOUT[tool] || 'inline';
|
|
79
92
|
}
|
|
80
93
|
|
|
94
|
+
// Skill-list rendering per tool.
|
|
95
|
+
// 'full' — verbose markdown table with description per skill (default, for
|
|
96
|
+
// tools with no native skill index — cursor/gemini/copilot/generic).
|
|
97
|
+
// 'compact' — bare skill-name list.
|
|
98
|
+
//
|
|
99
|
+
// Both Codex (.codex/skills/) and Claude Code (.claude/skills/) auto-discover
|
|
100
|
+
// name + description from each SKILL.md's frontmatter on their own, so restating
|
|
101
|
+
// the full table in the generated instruction file just burns context on every
|
|
102
|
+
// turn for information the tool already surfaces natively. The compact list still
|
|
103
|
+
// tells the AI which skill names exist and where to `Read` the full SKILL.md when
|
|
104
|
+
// a workflow step names one — it just stops duplicating the description.
|
|
105
|
+
const AI_TOOL_SKILL_LIST_STYLE = {
|
|
106
|
+
'codex': 'compact',
|
|
107
|
+
'claude': 'compact',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** Return 'full' or 'compact' skill-list style for a tool key. */
|
|
111
|
+
function resolveSkillListStyle(tool) {
|
|
112
|
+
return AI_TOOL_SKILL_LIST_STYLE[tool] || 'full';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Where each tool actually reads its skills from, for re-rooting paths in the
|
|
116
|
+
// generated registry/name-list. Only Codex mirrors skills into its own directory
|
|
117
|
+
// (.codex/skills/); every other tool reads the canonical .claude/skills/ directly.
|
|
118
|
+
const AI_TOOL_SKILLS_ROOT = {
|
|
119
|
+
'codex': '.codex/skills',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Return the skills-root path to embed in a tool's generated instruction file. */
|
|
123
|
+
function resolveSkillsRoot(tool) {
|
|
124
|
+
return AI_TOOL_SKILLS_ROOT[tool] || '.claude/skills';
|
|
125
|
+
}
|
|
126
|
+
|
|
81
127
|
// Shared workflow templates copied to .aiflow/instructions/ for pointer-layout tools.
|
|
82
128
|
// Keys are the destination basenames; gate workflow resolves per framework.
|
|
83
129
|
const WORKFLOW_INSTRUCTION_FILES = [
|
|
@@ -612,7 +658,8 @@ async function generateSkillNameList(projectDir, skillsRoot = '.codex/skills') {
|
|
|
612
658
|
if (!names.length) return '';
|
|
613
659
|
|
|
614
660
|
let out = `## Available skills\n\n`;
|
|
615
|
-
out += `
|
|
661
|
+
out += `These skills are indexed automatically from \`${skillsRoot}/\` (name + description come from `;
|
|
662
|
+
out += `each SKILL.md's frontmatter). When a workflow step says `;
|
|
616
663
|
out += `**INVOKE:** \`<name>\`, read \`${skillsRoot}/<name>/SKILL.md\` in full and follow it:\n\n`;
|
|
617
664
|
out += names.sort().map(n => `\`${n}\``).join(' · ') + '\n';
|
|
618
665
|
return out;
|
|
@@ -653,6 +700,29 @@ async function generateSkillRegistry(projectDir, skillsRoot = '.claude/skills')
|
|
|
653
700
|
return registry;
|
|
654
701
|
}
|
|
655
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Write `finalContent` into `targetPath` under its own marker pair: replace the
|
|
705
|
+
* existing block in place if the markers are already present, append (separated
|
|
706
|
+
* by `sep`) if the file exists without them, or create the file fresh otherwise.
|
|
707
|
+
* Used for multi-framework projects, where several independent markers
|
|
708
|
+
* (one per framework, plus the shared tool/registry block) share one file.
|
|
709
|
+
*/
|
|
710
|
+
async function upsertMarkerBlock(targetPath, markerStart, markerEnd, finalContent, sep) {
|
|
711
|
+
const exists = await fs.pathExists(targetPath);
|
|
712
|
+
if (!exists) {
|
|
713
|
+
await fs.writeFile(targetPath, finalContent);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const existingContent = await fs.readFile(targetPath, 'utf-8');
|
|
717
|
+
const hasBlock = existingContent.includes(markerStart) && existingContent.includes(markerEnd);
|
|
718
|
+
if (hasBlock) {
|
|
719
|
+
const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`, 'g');
|
|
720
|
+
await fs.writeFile(targetPath, existingContent.replace(regex, finalContent));
|
|
721
|
+
} else {
|
|
722
|
+
await fs.appendFile(targetPath, sep + finalContent);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
656
726
|
async function setupFramework(projectDir, framework, multi = false, selectedTools = Object.keys(AI_TOOL_FILES), options = {}) {
|
|
657
727
|
if (!framework) return;
|
|
658
728
|
const frameworkTemplatePath = path.join(PKG_DIR, 'custom', 'templates', `${framework}.md`);
|
|
@@ -683,12 +753,19 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
|
|
|
683
753
|
const workflowContent = [gateWorkflowContent, createSpecContent, createTestcaseContent].filter(Boolean).join('\n\n---\n\n');
|
|
684
754
|
|
|
685
755
|
const separator = `\n\n---\n\n`;
|
|
686
|
-
const skillRegistry = await generateSkillRegistry(projectDir);
|
|
687
|
-
const codexSkillList = await generateSkillNameList(projectDir, '.codex/skills');
|
|
688
756
|
|
|
689
757
|
const markerStart = '<!-- aiflow-kit-start -->';
|
|
690
758
|
const markerEnd = '<!-- aiflow-kit-end -->';
|
|
691
759
|
|
|
760
|
+
// Multi-framework projects get exactly one shared block per tool (tool header +
|
|
761
|
+
// skill registry) instead of one copy per framework block — neither depends on
|
|
762
|
+
// which framework triggered this call, so repeating them per framework was pure
|
|
763
|
+
// duplication (e.g. spring-boot + php in the same project doubled the ~44-skill
|
|
764
|
+
// registry). Re-running for a second framework upserts this block in place via
|
|
765
|
+
// its fixed markers rather than appending another copy.
|
|
766
|
+
const sharedMarkerStart = '<!-- aiflow-kit-start:shared -->';
|
|
767
|
+
const sharedMarkerEnd = '<!-- aiflow-kit-end:shared -->';
|
|
768
|
+
|
|
692
769
|
// Pointer-layout tools need the workflows on disk instead of inlined.
|
|
693
770
|
const needsPointer = selectedTools.some(t => resolveToolLayout(t) === 'pointer');
|
|
694
771
|
const instructionPaths = needsPointer
|
|
@@ -696,12 +773,13 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
|
|
|
696
773
|
: [];
|
|
697
774
|
|
|
698
775
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
776
|
+
* Tool header + skill registry + (for pointer tools) the "installed workflow
|
|
777
|
+
* files" pointer note — none of this depends on which framework is active:
|
|
778
|
+
* the 3 instruction-file paths are always the same 3 filenames regardless of
|
|
779
|
+
* framework (only their on-disk *content* varies), so this used to be
|
|
780
|
+
* duplicated per framework block for no reason.
|
|
702
781
|
*/
|
|
703
|
-
async function
|
|
704
|
-
const pointer = resolveToolLayout(tool) === 'pointer';
|
|
782
|
+
async function buildSharedBlock(tool) {
|
|
705
783
|
let body = '';
|
|
706
784
|
|
|
707
785
|
const toolTemplatePath = path.join(PKG_DIR, 'custom', 'templates', 'tools', `${tool}.md`);
|
|
@@ -709,19 +787,46 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
|
|
|
709
787
|
body += await fs.readFile(toolTemplatePath, 'utf-8') + '\n\n';
|
|
710
788
|
}
|
|
711
789
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
790
|
+
const skillsRoot = resolveSkillsRoot(tool);
|
|
791
|
+
const registry = resolveSkillListStyle(tool) === 'compact'
|
|
792
|
+
? await generateSkillNameList(projectDir, skillsRoot)
|
|
793
|
+
: await generateSkillRegistry(projectDir, skillsRoot);
|
|
715
794
|
if (registry) body += registry + '\n\n';
|
|
716
795
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
796
|
+
const pointer = resolveToolLayout(tool) === 'pointer';
|
|
797
|
+
if (pointer && instructionPaths.length) {
|
|
798
|
+
body += `## Installed workflow files\n\n`;
|
|
799
|
+
body += `Generated by ai-flow-kit v${PKG_VERSION}. `;
|
|
800
|
+
body += `These are the full DEV/TESTER/EXECUTE (+ gen-doc/ingest-data), BA spec, and QA `;
|
|
801
|
+
body += `testcase gate workflows — too large to inline here. Read the relevant file `;
|
|
802
|
+
body += `**in full** before starting a gate; do not work from a summary and do not skip, `;
|
|
803
|
+
body += `shorten, or merge gates:\n\n`;
|
|
804
|
+
body += instructionPaths.map(p => `- \`${p}\``).join('\n');
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return body.trim();
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Assemble the marker block for one tool.
|
|
812
|
+
* 'inline' tools get the framework's gate workflow embedded inline; 'pointer'
|
|
813
|
+
* tools already got their "read from disk" note via buildSharedBlock() above.
|
|
814
|
+
* Pass includeShared: false for multi-framework projects — the tool header,
|
|
815
|
+
* skill registry, and workflow-files note are written once via
|
|
816
|
+
* buildSharedBlock() instead.
|
|
817
|
+
*/
|
|
818
|
+
async function buildToolBlock(tool, blockMarkerStart, blockMarkerEnd, { includeShared = true } = {}) {
|
|
819
|
+
const pointer = resolveToolLayout(tool) === 'pointer';
|
|
820
|
+
let body = '';
|
|
821
|
+
|
|
822
|
+
if (includeShared) {
|
|
823
|
+
const shared = await buildSharedBlock(tool);
|
|
824
|
+
if (shared) body += shared + '\n\n';
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (frameworkContent) body += frameworkContent + '\n\n';
|
|
828
|
+
|
|
829
|
+
if (!pointer && workflowContent) {
|
|
725
830
|
body += workflowContent + '\n\n';
|
|
726
831
|
}
|
|
727
832
|
|
|
@@ -802,7 +907,7 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
|
|
|
802
907
|
const blockMarkerStart = multi ? `<!-- aiflow-kit-start:${framework} -->` : markerStart;
|
|
803
908
|
const blockMarkerEnd = multi ? `<!-- aiflow-kit-end:${framework} -->` : markerEnd;
|
|
804
909
|
|
|
805
|
-
const finalContent = await buildToolBlock(tool, blockMarkerStart, blockMarkerEnd);
|
|
910
|
+
const finalContent = await buildToolBlock(tool, blockMarkerStart, blockMarkerEnd, { includeShared: !multi });
|
|
806
911
|
|
|
807
912
|
const fileExists = await fs.pathExists(targetPath);
|
|
808
913
|
|
|
@@ -861,19 +966,15 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
|
|
|
861
966
|
}
|
|
862
967
|
}
|
|
863
968
|
} else {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
} else {
|
|
872
|
-
await fs.appendFile(targetPath, separator + finalContent);
|
|
873
|
-
}
|
|
874
|
-
} else {
|
|
875
|
-
await fs.writeFile(targetPath, finalContent);
|
|
969
|
+
// Upsert the shared block (tool header + skill registry) once per tool —
|
|
970
|
+
// fixed markers mean a second framework's call replaces it in place
|
|
971
|
+
// instead of appending another copy.
|
|
972
|
+
const sharedBody = await buildSharedBlock(tool);
|
|
973
|
+
if (sharedBody) {
|
|
974
|
+
const sharedFinal = `${sharedMarkerStart}\n${sharedBody}\n${sharedMarkerEnd}`;
|
|
975
|
+
await upsertMarkerBlock(targetPath, sharedMarkerStart, sharedMarkerEnd, sharedFinal, separator);
|
|
876
976
|
}
|
|
977
|
+
await upsertMarkerBlock(targetPath, blockMarkerStart, blockMarkerEnd, finalContent, separator);
|
|
877
978
|
written.push(AI_TOOL_FILES[tool]);
|
|
878
979
|
}
|
|
879
980
|
}
|
|
@@ -1523,6 +1624,8 @@ module.exports = init;
|
|
|
1523
1624
|
module.exports.AI_TOOL_FILES = AI_TOOL_FILES;
|
|
1524
1625
|
module.exports.AI_TOOL_LAYOUT = AI_TOOL_LAYOUT;
|
|
1525
1626
|
module.exports.resolveToolLayout = resolveToolLayout;
|
|
1627
|
+
module.exports.resolveSkillListStyle = resolveSkillListStyle;
|
|
1628
|
+
module.exports.resolveSkillsRoot = resolveSkillsRoot;
|
|
1526
1629
|
module.exports.writeWorkflowInstructions = writeWorkflowInstructions;
|
|
1527
1630
|
module.exports.setupCodex = setupCodex;
|
|
1528
1631
|
module.exports.setupCodexSkills = setupCodexSkills;
|