@relipa/ai-flow-kit 0.1.5 → 0.1.7-beta.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/bin/aiflow.js +71 -33
- package/custom/rules/ml-conventions.md +11 -8
- package/custom/rules/project-conventions.md +18 -2
- package/custom/skills/design-experiment/SKILL.md +2 -2
- package/custom/skills/evaluate-model/SKILL.md +2 -2
- package/custom/skills/explore-data/SKILL.md +1 -1
- package/custom/skills/figma-to-component/SKILL.md +222 -20
- package/custom/skills/frame-ml-problem/SKILL.md +1 -1
- package/custom/skills/generate-spec/SKILL.md +19 -0
- package/custom/skills/read-study-requirement/SKILL.md +69 -1
- package/custom/skills/review-plan/SKILL.md +42 -0
- package/custom/templates/memory/CODEOWNERS +8 -0
- package/custom/templates/memory/ci/memory-finalize.yml +9 -0
- package/custom/templates/memory/ci/memory-lint.yml +10 -0
- package/custom/templates/memory/gitlab/merge_request_templates/memory.md +18 -0
- package/custom/templates/memory/memory-item.md +25 -0
- package/custom/templates/memory/skeleton/00.Shared/architecture/_global/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/00.Shared/decisions/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/00.Shared/domain/_global/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/00.Shared/glossary/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/01.Lessons/ba/_global/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/01.Lessons/dev/_global/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/01.Lessons/pm/_global/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/01.Lessons/qa/_global/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/02.Instincts/approved/_global/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/03.Retro/.gitkeep +0 -0
- package/custom/templates/memory/skeleton/MEMORY.md +7 -0
- package/custom/templates/memory/skeleton/_deprecated/.gitkeep +0 -0
- package/custom/templates/shared/create-spec-workflow.md +68 -1
- package/custom/templates/shared/create-testcase-workflow.md +67 -0
- package/custom/templates/shared/gate-workflow.md +122 -3
- package/custom/templates/shared/ml-gate-workflow.md +16 -9
- package/docs/common/AIFLOW.md +11 -1
- package/docs/common/CHANGELOG.md +32 -0
- package/docs/common/cli-reference.md +3 -1
- package/docs/common/workflows/figma.md +176 -105
- package/package.json +2 -2
- package/scripts/create-score-excel.js +135 -14
- package/scripts/detect.js +11 -0
- package/scripts/docs-branch.js +264 -0
- package/scripts/hooks/figma-rate-limit.js +83 -0
- package/scripts/hooks/session-start.js +146 -8
- package/scripts/init.js +53 -1
- package/scripts/memory-store.js +391 -0
- package/scripts/memory.js +176 -247
- package/scripts/prompt.js +45 -0
- package/scripts/task.js +30 -15
- package/scripts/update.js +12 -0
- package/scripts/use.js +7 -5
package/scripts/memory.js
CHANGED
|
@@ -1,247 +1,176 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
.replace(/-+/g, '-')
|
|
178
|
-
.substring(0, 50);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
module.exports = async function memory(command, args = {}) {
|
|
182
|
-
try {
|
|
183
|
-
if (command === 'save' && args.key && args.value) {
|
|
184
|
-
return await saveMemory(args.key, args.value, args.metadata);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (command === 'get' && args.key) {
|
|
188
|
-
const m = await getMemory(args.key);
|
|
189
|
-
if (m) {
|
|
190
|
-
await displayMemory(m);
|
|
191
|
-
return m;
|
|
192
|
-
} else {
|
|
193
|
-
console.log(chalk.yellow(`! Memory not found: ${args.key}`));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (command === 'list') {
|
|
198
|
-
const memories = await listMemories();
|
|
199
|
-
if (memories.length === 0) {
|
|
200
|
-
console.log(chalk.gray('No memories saved yet'));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
console.log(chalk.cyan('\n📚 Saved Memories:\n'));
|
|
204
|
-
memories.forEach((m, i) => {
|
|
205
|
-
console.log(`${i + 1}. ${chalk.white(m.key)}`);
|
|
206
|
-
console.log(` ${chalk.gray(m.value)}`);
|
|
207
|
-
console.log(` ${chalk.gray('Created: ' + new Date(m.created).toLocaleDateString())}\n`);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (command === 'search' && args.query) {
|
|
212
|
-
const results = await searchMemories(args.query);
|
|
213
|
-
if (results.length === 0) {
|
|
214
|
-
console.log(chalk.gray(`No memories matching "${args.query}"`));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
console.log(chalk.cyan(`\n🔍 Found ${results.length} memories matching "${args.query}":\n`));
|
|
218
|
-
results.forEach((m, i) => {
|
|
219
|
-
console.log(`${i + 1}. ${chalk.white(m.key)}`);
|
|
220
|
-
console.log(` ${chalk.gray(m.value)}\n`);
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (command === 'delete' && args.key) {
|
|
225
|
-
return await deleteMemory(args.key);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (command === 'clear') {
|
|
229
|
-
return await clearAllMemories();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (command === 'auto-load' && args.context) {
|
|
233
|
-
return await loadRelevantMemories(args.context);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
} catch (err) {
|
|
237
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
module.exports.saveMemory = saveMemory;
|
|
242
|
-
module.exports.getMemory = getMemory;
|
|
243
|
-
module.exports.listMemories = listMemories;
|
|
244
|
-
module.exports.searchMemories = searchMemories;
|
|
245
|
-
module.exports.deleteMemory = deleteMemory;
|
|
246
|
-
module.exports.clearAllMemories = clearAllMemories;
|
|
247
|
-
module.exports.loadRelevantMemories = loadRelevantMemories;
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const store = require('./memory-store');
|
|
5
|
+
const docsBranchCommand = require('./docs-branch');
|
|
6
|
+
|
|
7
|
+
// CLI for the "99.Memory/" Project Brain (docs/internal/Memory-Architecture-v1.0.md).
|
|
8
|
+
// Thin dispatch layer: business logic lives in memory-store.js, git/MR logic is reused
|
|
9
|
+
// from docs-branch.js (same "AI drafts — human approves via MR" pattern as `ak docs`).
|
|
10
|
+
//
|
|
11
|
+
// Replaces the legacy JSON key/value store this file used to implement
|
|
12
|
+
// (`.aiflow/memory/*.json`, save/get/search/delete/clear) — unrelated to 99.Memory/,
|
|
13
|
+
// retired outright (no backward-compat shim; nothing in the kit still reads that store).
|
|
14
|
+
|
|
15
|
+
const PROJECT_DIR = process.cwd();
|
|
16
|
+
|
|
17
|
+
module.exports = async function memory(action, options = {}) {
|
|
18
|
+
try {
|
|
19
|
+
if (action === 'draft') return await draftCmd(options);
|
|
20
|
+
if (action === 'list') return await listCmd(options);
|
|
21
|
+
if (action === 'submit') return await submitCmd(options);
|
|
22
|
+
if (action === 'remove') return await removeCmd(options);
|
|
23
|
+
|
|
24
|
+
console.log(chalk.yellow('Usage:'));
|
|
25
|
+
console.log(chalk.gray(' ak memory draft --category <cat> --slug <slug> --content <text> [options]'));
|
|
26
|
+
console.log(chalk.gray(' ak memory list [--approved]'));
|
|
27
|
+
console.log(chalk.gray(' ak memory submit <pendingPath> --title "..." [--yes]'));
|
|
28
|
+
console.log(chalk.gray(' ak memory remove <path> [--hard] [--reason "..."] --title "..." [--yes]'));
|
|
29
|
+
console.log(chalk.gray(`\n Categories: ${Object.keys(store.CATEGORIES).join(', ')}`));
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
async function draftCmd(options) {
|
|
37
|
+
if (!options.category || !options.slug || !options.content) {
|
|
38
|
+
console.log(chalk.red('✗ Cần --category, --slug, và --content.'));
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const tags = splitList(options.tags);
|
|
43
|
+
const workflows = splitList(options.workflows, ['all']);
|
|
44
|
+
|
|
45
|
+
const result = await store.createDraft(PROJECT_DIR, {
|
|
46
|
+
category: options.category,
|
|
47
|
+
functionId: options.functionId,
|
|
48
|
+
scope: options.scope,
|
|
49
|
+
slug: options.slug,
|
|
50
|
+
content: options.content,
|
|
51
|
+
source: options.source || '',
|
|
52
|
+
tags, workflows,
|
|
53
|
+
confidence: options.confidence !== undefined ? Number(options.confidence) : 0.5,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (result.conflict) {
|
|
57
|
+
console.log(chalk.yellow(`⚠ Memory tương tự đã có tại: ${result.existingPath}`));
|
|
58
|
+
console.log(chalk.gray(' Sửa file đó trực tiếp nếu muốn cập nhật, thay vì tạo bản mới.'));
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(chalk.green(`✓ Draft đã tạo: ${result.path}`));
|
|
63
|
+
console.log(chalk.gray(` id: ${result.id} (local — chỉ máy này recall được cho tới khi submit)`));
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function listCmd(options) {
|
|
68
|
+
const memories = options.approved
|
|
69
|
+
? await store.listApproved(PROJECT_DIR)
|
|
70
|
+
: await store.listDrafts(PROJECT_DIR);
|
|
71
|
+
|
|
72
|
+
if (memories.length === 0) {
|
|
73
|
+
console.log(chalk.gray(options.approved ? 'Chưa có memory nào được approve.' : 'Chưa có draft nào trong _pending/.'));
|
|
74
|
+
return memories;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.cyan(`\n${options.approved ? 'Approved memories' : 'Pending drafts (local)'}:\n`));
|
|
78
|
+
for (const m of memories) {
|
|
79
|
+
console.log(` ${chalk.white(m.id || '(no id)')} ${chalk.gray(m.relPath)}`);
|
|
80
|
+
console.log(` ${chalk.gray(`type=${m.type} scope=${m.scope} workflows=${m.workflows}`)}`);
|
|
81
|
+
}
|
|
82
|
+
console.log();
|
|
83
|
+
return memories;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Submit a pending draft: create a branch in AK-Docs, move the file from _pending/
|
|
88
|
+
* to its destination path, commit, push, open an MR — same two-step CLI already
|
|
89
|
+
* used by `ak docs branch` + `ak docs submit --yes` (custom/templates/shared/*.md).
|
|
90
|
+
*/
|
|
91
|
+
async function submitCmd(options) {
|
|
92
|
+
const pendingRelPath = options._positional && options._positional[0];
|
|
93
|
+
if (!pendingRelPath || !options.title) {
|
|
94
|
+
console.log(chalk.red('✗ Cần <pendingPath> (từ `ak memory list`) và --title.'));
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const base = store.memoryDir(PROJECT_DIR);
|
|
100
|
+
const pendingAbs = path.join(base, pendingRelPath);
|
|
101
|
+
if (!(await fs.pathExists(pendingAbs))) {
|
|
102
|
+
console.log(chalk.red(`✗ Không tìm thấy draft: ${pendingRelPath}`));
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { data } = store.parseFrontmatter(await fs.readFile(pendingAbs, 'utf-8'));
|
|
108
|
+
const destRelPath = pendingRelPath.replace(/^_pending[\\/]/, '');
|
|
109
|
+
const destAbs = path.join(base, destRelPath);
|
|
110
|
+
|
|
111
|
+
const slug = path.basename(destRelPath, '.md');
|
|
112
|
+
const branchResult = await docsBranchCommand.createBranch({
|
|
113
|
+
functionId: 'memory', taskId: slug, repo: 'AK-Docs', yes: options.yes,
|
|
114
|
+
});
|
|
115
|
+
if (!branchResult) return; // createBranch already printed the reason / plan
|
|
116
|
+
|
|
117
|
+
await fs.ensureDir(path.dirname(destAbs));
|
|
118
|
+
await fs.move(pendingAbs, destAbs, { overwrite: false });
|
|
119
|
+
|
|
120
|
+
return docsBranchCommand.submitDocs({
|
|
121
|
+
title: options.title,
|
|
122
|
+
description: options.description || `Memory: ${data.id || slug}`,
|
|
123
|
+
repo: 'AK-Docs', yes: options.yes,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Propose removal (Luồng 4): same branch+MR mechanism as submit, but moves the
|
|
129
|
+
* target file into _deprecated/ (soft, default) or deletes it outright (--hard).
|
|
130
|
+
*/
|
|
131
|
+
async function removeCmd(options) {
|
|
132
|
+
const targetRelPath = options._positional && options._positional[0];
|
|
133
|
+
if (!targetRelPath || !options.title) {
|
|
134
|
+
console.log(chalk.red('✗ Cần <path đã approve> (từ `ak memory list --approved`) và --title.'));
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const base = store.memoryDir(PROJECT_DIR);
|
|
140
|
+
const targetAbs = path.join(base, targetRelPath);
|
|
141
|
+
if (!(await fs.pathExists(targetAbs))) {
|
|
142
|
+
console.log(chalk.red(`✗ Không tìm thấy memory: ${targetRelPath}`));
|
|
143
|
+
process.exitCode = 1;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const slug = path.basename(targetRelPath, '.md');
|
|
148
|
+
const branchResult = await docsBranchCommand.createBranch({
|
|
149
|
+
functionId: 'memory', taskId: `remove-${slug}`, repo: 'AK-Docs', yes: options.yes,
|
|
150
|
+
});
|
|
151
|
+
if (!branchResult) return;
|
|
152
|
+
|
|
153
|
+
if (options.hard) {
|
|
154
|
+
await fs.remove(targetAbs);
|
|
155
|
+
console.log(chalk.yellow(`⚠ Hard-remove: đã xóa file khỏi working tree. Lý do: ${options.reason || '(không ghi lý do)'}`));
|
|
156
|
+
} else {
|
|
157
|
+
const deprecatedAbs = path.join(base, '_deprecated', path.basename(targetRelPath));
|
|
158
|
+
await fs.ensureDir(path.dirname(deprecatedAbs));
|
|
159
|
+
await fs.move(targetAbs, deprecatedAbs, { overwrite: false });
|
|
160
|
+
console.log(chalk.gray(` Soft-remove: chuyển vào _deprecated/${path.basename(targetRelPath)}`));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return docsBranchCommand.submitDocs({
|
|
164
|
+
title: options.title,
|
|
165
|
+
description: options.description || `Remove memory: ${slug}. Lý do: ${options.reason || '(không ghi lý do)'}`,
|
|
166
|
+
repo: 'AK-Docs', yes: options.yes,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function splitList(value, fallback = []) {
|
|
171
|
+
if (!value) return fallback;
|
|
172
|
+
if (Array.isArray(value)) return value;
|
|
173
|
+
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports.store = store;
|
package/scripts/prompt.js
CHANGED
|
@@ -281,6 +281,51 @@ Write documentation based on approved outline.
|
|
|
281
281
|
**INVOKE:** \`superpowers:verification-before-completion\`
|
|
282
282
|
Verify docs match code. No outdated examples.
|
|
283
283
|
Display: "GATE 3: Documentation complete. Type APPROVED."
|
|
284
|
+
`,
|
|
285
|
+
},
|
|
286
|
+
'gen-doc': {
|
|
287
|
+
header: 'Generate Document',
|
|
288
|
+
instruction: 'Generate a document from source code / feature analysis as described below.',
|
|
289
|
+
skillWorkflow: `
|
|
290
|
+
## STRICT GATE WORKFLOW — Generate Document (2-Gate)
|
|
291
|
+
|
|
292
|
+
> ⚡ gen-doc uses a **2-gate flow**: Gate 1 (Requirement) → APPROVED → Gate 2 (Generate + Auto Review).
|
|
293
|
+
> Gate 2 runs immediately after APPROVED — no plan.md, no TDD, no extra coding gates.
|
|
294
|
+
|
|
295
|
+
> gen-doc reuses the same \`AK-Docs/04.Coding/\` folders as the standard Dev workflow (see \`custom/rules/project-conventions.md\`) — only Gate 1–2, no separate section.
|
|
296
|
+
|
|
297
|
+
### GATE 1 — AI Analyze & Plan Document
|
|
298
|
+
**INVOKE:** \`read-study-requirement\` skill
|
|
299
|
+
1. Load ticket context + read source code / files to understand scope
|
|
300
|
+
2. Confirm \`functionId\` (BẮT BUỘC — see "functionId & ticketId" in \`project-conventions.md\`; if it can't be derived for an ad-hoc task, ask directly) and ensure AK-Docs is on branch \`feature/[functionId]/[ticketId]\` (create via \`ak docs branch [functionId] [ticketId] --yes\` only after explicit user confirmation)
|
|
301
|
+
3. Understand the document goal, target audience, and output format
|
|
302
|
+
4. If unclear → ask ONE question at a time, wait for reply
|
|
303
|
+
5. Output \`AK-Docs/04.Coding/01.Requirements/[functionId]/[ticketId].md\` with:
|
|
304
|
+
- Document scope and target audience
|
|
305
|
+
- Content outline (sections to cover)
|
|
306
|
+
- Source references (files, features, flows to analyze)
|
|
307
|
+
- Output format: Markdown or Excel (note template path if applicable)
|
|
308
|
+
- Effort estimate
|
|
309
|
+
6. Display "GATE 1: Document plan ready" → wait for **APPROVED**
|
|
310
|
+
|
|
311
|
+
> **Telemetry:** Run \`ak gate 1 start --ticket [ticket-id]\` when starting.
|
|
312
|
+
> Run \`ak gate 1 approved --ticket [ticket-id]\` immediately when APPROVED is received.
|
|
313
|
+
|
|
314
|
+
### GATE 2 — Generate Document + Auto Review (runs immediately after APPROVED)
|
|
315
|
+
|
|
316
|
+
**DO NOT create plan.md. DO NOT run TDD. Proceed directly to document generation.**
|
|
317
|
+
|
|
318
|
+
1. Re-read \`AK-Docs/04.Coding/01.Requirements/[functionId]/[ticketId].md\` (the approved outline)
|
|
319
|
+
2. Read source code, trace data flows, analyze features as needed
|
|
320
|
+
3. Generate the output document:
|
|
321
|
+
- **Markdown:** Save to \`AK-Docs/04.Coding/02.Plans/[functionId]/[ticketId].md\` (or the custom path/format noted in the requirement doc — still write a short pointer + summary into this file)
|
|
322
|
+
4. Self-review: verify content completeness against the approved requirement outline
|
|
323
|
+
5. Create \`AK-Docs/04.Coding/02.Plans/[functionId]/[ticketId]-summary.md\` with a brief summary of what was generated
|
|
324
|
+
6. Submit AK-Docs via Merge Request: show the commit/MR title+description, wait for explicit user confirmation, then run \`ak docs submit --title "..." --description "..." --yes\` — PM reviews & merges into \`main\` (never merge it yourself)
|
|
325
|
+
7. Run: \`ak gate 2 approved --ticket [ticket-id]\` to close the task
|
|
326
|
+
8. Display: \`GATE 2 DONE: Document generated at [path]\`
|
|
327
|
+
|
|
328
|
+
**Do NOT wait for extra approval before generating the document — only the branch/MR confirmations above gate on the user; run the gate 2 approved command immediately after.**
|
|
284
329
|
`,
|
|
285
330
|
},
|
|
286
331
|
};
|
package/scripts/task.js
CHANGED
|
@@ -331,17 +331,22 @@ async function nextGate(taskId) {
|
|
|
331
331
|
const nextGateNum = currentGate + 1;
|
|
332
332
|
|
|
333
333
|
// Save context snapshot if current task is active
|
|
334
|
+
let taskType = null;
|
|
334
335
|
if (await fs.pathExists(CURRENT_FILE)) {
|
|
335
336
|
const ctx = await fs.readJson(CURRENT_FILE).catch(() => null);
|
|
336
337
|
if (ctx && ctx.taskId === resolvedId) {
|
|
338
|
+
taskType = ctx.taskType || null;
|
|
337
339
|
await fs.writeJson(path.join(taskDir, 'context.json'), ctx, { spaces: 2 });
|
|
338
340
|
}
|
|
339
341
|
}
|
|
340
342
|
|
|
343
|
+
const isGenDoc = taskType === 'gen-doc';
|
|
344
|
+
const maxGate = isGenDoc ? 2 : 5;
|
|
345
|
+
|
|
341
346
|
const taskState = {
|
|
342
347
|
...existing,
|
|
343
348
|
taskId: resolvedId,
|
|
344
|
-
status: 'pending',
|
|
349
|
+
status: nextGateNum > maxGate ? 'done' : 'pending',
|
|
345
350
|
updatedAt: now,
|
|
346
351
|
pausedAt: now,
|
|
347
352
|
currentGate: nextGateNum,
|
|
@@ -354,20 +359,26 @@ async function nextGate(taskId) {
|
|
|
354
359
|
|
|
355
360
|
// Generate cumulative task-summary.md (task-internal state — lives in .aiflow/, not gate docs)
|
|
356
361
|
const summaryPath = path.join(TASKS_DIR, resolvedId, 'task-summary.md');
|
|
357
|
-
const summaryContent = await generateMarkdownSummary(taskState);
|
|
362
|
+
const summaryContent = await generateMarkdownSummary(taskState, taskType);
|
|
358
363
|
await fs.writeFile(summaryPath, summaryContent, 'utf-8');
|
|
359
364
|
|
|
360
|
-
const taskType = existing.taskType || 'feature';
|
|
361
365
|
console.log(chalk.green(`✓ Gate ${currentGate} approved for ${resolvedId}.`));
|
|
362
366
|
console.log(chalk.gray(` Summary saved to: .aiflow/tasks/${resolvedId}/task-summary.md`));
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
367
|
+
|
|
368
|
+
if (isGenDoc && currentGate >= maxGate) {
|
|
369
|
+
console.log(chalk.green('\n ✅ Document workflow complete. Task is done.'));
|
|
370
|
+
} else {
|
|
371
|
+
const nextLabel = gateLabel(nextGateNum, taskType);
|
|
372
|
+
console.log(chalk.white(`\n Next: Gate ${nextGateNum} — ${nextLabel}`));
|
|
373
|
+
console.log(chalk.cyan(`\n To continue in a fresh session (Recommended to avoid context pollution):`));
|
|
374
|
+
console.log(chalk.gray(` 1. Open a NEW chatbox or terminal session.`));
|
|
375
|
+
console.log(chalk.gray(` 2. Run: aiflow task resume ${resolvedId} (to load context).`));
|
|
376
|
+
console.log(chalk.gray(` 3. Type "start" or "continue from the current plan".`));
|
|
377
|
+
if (!isGenDoc) {
|
|
378
|
+
console.log(chalk.yellow(` (Note: Gate 3 progress is saved via [x] checkboxes in the Gate 2 plan doc (AK-Docs/04.Coding/02.Plans/).`));
|
|
379
|
+
console.log(chalk.yellow(` The AI will automatically resume the exact task you left off.)`));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
371
382
|
console.log();
|
|
372
383
|
}
|
|
373
384
|
|
|
@@ -466,9 +477,9 @@ async function detectCurrentGate(taskId) {
|
|
|
466
477
|
return 1;
|
|
467
478
|
}
|
|
468
479
|
|
|
469
|
-
async function generateMarkdownSummary(taskState) {
|
|
470
|
-
|
|
471
|
-
const maxGate
|
|
480
|
+
async function generateMarkdownSummary(taskState, taskType = null) {
|
|
481
|
+
taskType = taskType || taskState.taskType || 'feature';
|
|
482
|
+
const maxGate = taskType === 'testing' ? 4 : (taskType === 'gen-doc' ? 2 : 5);
|
|
472
483
|
const lines = [];
|
|
473
484
|
lines.push(`# Task Summary: ${taskState.taskId}`);
|
|
474
485
|
lines.push(`**Title:** ${taskState.title}`);
|
|
@@ -492,7 +503,7 @@ async function generateMarkdownSummary(taskState) {
|
|
|
492
503
|
return lines.join('\n');
|
|
493
504
|
}
|
|
494
505
|
|
|
495
|
-
function gateLabel(n, taskType) {
|
|
506
|
+
function gateLabel(n, taskType = null) {
|
|
496
507
|
if (taskType === 'testing') {
|
|
497
508
|
const labels = {
|
|
498
509
|
1: 'Phân tích & Confirm',
|
|
@@ -502,6 +513,10 @@ function gateLabel(n, taskType) {
|
|
|
502
513
|
};
|
|
503
514
|
return labels[n] || `Gate ${n}`;
|
|
504
515
|
}
|
|
516
|
+
if (taskType === 'gen-doc') {
|
|
517
|
+
if (n === 2) return 'Generate Document';
|
|
518
|
+
if (n >= 3) return 'Done';
|
|
519
|
+
}
|
|
505
520
|
const labels = {
|
|
506
521
|
1: 'AI Analyze Requirement',
|
|
507
522
|
2: 'Implementation Plan',
|
package/scripts/update.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { confirm, select } = require('@inquirer/prompts');
|
|
5
5
|
const { syncDocsRepos } = require('./docs-repo');
|
|
6
|
+
const memoryStore = require('./memory-store');
|
|
6
7
|
|
|
7
8
|
const PKG_DIR = path.join(__dirname, '..');
|
|
8
9
|
const PKG_VERSION = require('../package.json').version;
|
|
@@ -29,6 +30,17 @@ module.exports = async function update(options = {}) {
|
|
|
29
30
|
// ── Sync AK-Docs / Shared-Docs sibling repos ──────────────────
|
|
30
31
|
await syncDocsRepos(projectDir);
|
|
31
32
|
|
|
33
|
+
// ── Bootstrap 99.Memory/ skeleton if AK-Docs was cloned after init ────
|
|
34
|
+
const memorySkeleton = await memoryStore.ensureSkeleton(projectDir);
|
|
35
|
+
if (memorySkeleton.created) {
|
|
36
|
+
console.log(chalk.green('✓ Đã tạo khung 99.Memory/ trong AK-Docs (chưa có ghi nhớ nào).'));
|
|
37
|
+
const gitignoreResult = await memoryStore.ensureMemoryGitignored(projectDir);
|
|
38
|
+
if (memorySkeleton.committed || gitignoreResult.committed) {
|
|
39
|
+
console.log(chalk.yellow(' ⚠ Đã commit local trên nhánh hiện tại của AK-Docs — `main` là protected branch,'));
|
|
40
|
+
console.log(chalk.yellow(' hãy tự `git push` khi sẵn sàng (không tự động push).'));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
const state = await fs.readJson(stateFile);
|
|
33
45
|
const currentVersion = state.current_version;
|
|
34
46
|
|
package/scripts/use.js
CHANGED
|
@@ -234,7 +234,7 @@ async function loadFromBacklog(issueKey, options = {}) {
|
|
|
234
234
|
options.cto != null ||
|
|
235
235
|
options.commentsTo != null ||
|
|
236
236
|
options["comments-to"] != null;
|
|
237
|
-
|
|
237
|
+
|
|
238
238
|
if (loadComments) {
|
|
239
239
|
console.log(chalk.gray(" ℹ Comments requested..."));
|
|
240
240
|
}
|
|
@@ -349,13 +349,13 @@ async function fetchBacklogComments(domain, apiKey, issueKey) {
|
|
|
349
349
|
if (minId) {
|
|
350
350
|
url += `&minId=${minId}`;
|
|
351
351
|
}
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
const batch = await backlogGet(url);
|
|
354
354
|
if (!batch || batch.length === 0) break;
|
|
355
|
-
|
|
355
|
+
|
|
356
356
|
all.push(...batch);
|
|
357
357
|
if (batch.length < 100) break;
|
|
358
|
-
|
|
358
|
+
|
|
359
359
|
// Use the last ID as minId for the next batch
|
|
360
360
|
minId = batch[batch.length - 1].id;
|
|
361
361
|
}
|
|
@@ -467,7 +467,7 @@ async function loadFromJira(issueKey, options = {}) {
|
|
|
467
467
|
options.cto != null ||
|
|
468
468
|
options.commentsTo != null ||
|
|
469
469
|
options["comments-to"] != null;
|
|
470
|
-
|
|
470
|
+
|
|
471
471
|
if (loadComments) {
|
|
472
472
|
console.log(chalk.gray(" ℹ Comments requested..."));
|
|
473
473
|
}
|
|
@@ -636,6 +636,7 @@ async function promptForTaskType(detectedDefault) {
|
|
|
636
636
|
{ name: td("🔍 Investigation", "Analysis", "Investigate, analyze root cause"), value: "investigation" },
|
|
637
637
|
{ name: td("📊 Impact Analysis", "Analysis", "Assess scope and risk of changes"), value: "impact-analysis" },
|
|
638
638
|
{ name: td("📖 Documentation", "Document", "Write docs, README, API reference"), value: "documentation" },
|
|
639
|
+
{ name: td("📝 Generate Doc/Spec", "Document", "Generate new documentation/Spec (2-gate)"), value: "gen-doc" },
|
|
639
640
|
],
|
|
640
641
|
default: detectedDefault || "feature",
|
|
641
642
|
});
|
|
@@ -709,6 +710,7 @@ async function manualContext(prefillId = "") {
|
|
|
709
710
|
{ name: td("🔍 Investigation", "Analysis", "Investigate, analyze root cause"), value: "investigation" },
|
|
710
711
|
{ name: td("📊 Impact Analysis", "Analysis", "Assess scope and risk of changes"), value: "impact-analysis" },
|
|
711
712
|
{ name: td("📖 Documentation", "Document", "Write docs, README, API reference"), value: "documentation" },
|
|
713
|
+
{ name: td("📝 Generate Doc/Spec", "Document", "Generate new documentation/Spec (2-gate)"), value: "gen-doc" },
|
|
712
714
|
],
|
|
713
715
|
default: existing.taskType || undefined,
|
|
714
716
|
});
|