@relipa/ai-flow-kit 0.0.9 → 0.1.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.
Files changed (235) hide show
  1. package/README.md +99 -13
  2. package/bin/aiflow.js +525 -475
  3. package/bin/ak.js +0 -0
  4. package/custom/mcp-presets/README.md +0 -0
  5. package/custom/mcp-presets/backlog.json +0 -0
  6. package/custom/mcp-presets/figma-desktop.json +9 -9
  7. package/custom/mcp-presets/figma.json +0 -0
  8. package/custom/mcp-presets/gitnexus.json +0 -0
  9. package/custom/mcp-presets/google-sheets.json +0 -0
  10. package/custom/mcp-presets/jira.json +0 -0
  11. package/custom/prompts/bug-fix.md +0 -0
  12. package/custom/prompts/feature.md +0 -0
  13. package/custom/prompts/investigation.md +0 -0
  14. package/custom/rules/code-style.md +0 -0
  15. package/custom/rules/java/code-style.md +0 -0
  16. package/custom/rules/java/naming.md +0 -0
  17. package/custom/rules/java/review-checklist.md +0 -0
  18. package/custom/rules/java/spring-boot-examples.md +0 -0
  19. package/custom/rules/javascript/code-style.md +0 -0
  20. package/custom/rules/javascript/naming.md +0 -0
  21. package/custom/rules/naming.md +0 -0
  22. package/custom/rules/php/code-style.md +0 -0
  23. package/custom/rules/php/naming.md +0 -0
  24. package/custom/rules/project-conventions.md +51 -51
  25. package/custom/rules/review-checklist.md +0 -0
  26. package/custom/skills/figma-to-component/SKILL.md +0 -0
  27. package/custom/skills/generate-spec/SKILL.md +0 -0
  28. package/custom/skills/impact-analysis/SKILL.md +0 -0
  29. package/custom/skills/investigate-bug/SKILL.md +0 -0
  30. package/custom/skills/read-study-requirement/SKILL.md +22 -3
  31. package/custom/skills/report-customer/SKILL.md +0 -0
  32. package/custom/skills/review-plan/SKILL.md +0 -0
  33. package/custom/templates/laravel.md +0 -0
  34. package/custom/templates/nestjs.md +0 -0
  35. package/custom/templates/nextjs.md +0 -0
  36. package/custom/templates/nodejs-express.md +0 -0
  37. package/custom/templates/php-plain.md +0 -0
  38. package/custom/templates/php.md +0 -0
  39. package/custom/templates/python-django.md +0 -0
  40. package/custom/templates/python-fastapi.md +0 -0
  41. package/custom/templates/python.md +0 -0
  42. package/custom/templates/reactjs.md +0 -0
  43. package/custom/templates/shared/gate-workflow.md +5 -4
  44. package/custom/templates/spring-boot.md +0 -0
  45. package/custom/templates/tools/claude.md +0 -0
  46. package/custom/templates/tools/copilot.md +0 -0
  47. package/custom/templates/tools/cursor.md +0 -0
  48. package/custom/templates/tools/gemini.md +22 -22
  49. package/custom/templates/tools/generic.md +0 -0
  50. package/custom/templates/vue-nuxt.md +0 -0
  51. package/docs/common/AIFLOW.md +0 -0
  52. package/docs/common/CHANGELOG.md +30 -0
  53. package/docs/common/QUICK_START.md +8 -0
  54. package/docs/common/ai-integration.md +0 -0
  55. package/docs/common/cli-reference.md +60 -8
  56. package/docs/common/configuration.md +0 -0
  57. package/docs/common/getting-started.md +0 -0
  58. package/docs/common/troubleshooting.md +0 -0
  59. package/docs/common/workflows/bug-fix.md +0 -0
  60. package/docs/common/workflows/feature.md +0 -0
  61. package/docs/common/workflows/figma.md +105 -105
  62. package/docs/common/workflows/impact-analysis.md +0 -0
  63. package/docs/common/workflows/investigation.md +0 -0
  64. package/docs/common/workflows/refactor.md +0 -0
  65. package/docs/project/ARCHITECTURE.md +0 -0
  66. package/index.js +0 -0
  67. package/package.json +9 -2
  68. package/scripts/checkpoint.js +0 -0
  69. package/scripts/config.js +0 -0
  70. package/scripts/context.js +0 -0
  71. package/scripts/create-score-excel.js +354 -0
  72. package/scripts/detect.js +0 -0
  73. package/scripts/doctor.js +0 -0
  74. package/scripts/gitnexus-worker.js +0 -0
  75. package/scripts/guide.js +2 -0
  76. package/scripts/hooks/block-git-write.js +0 -0
  77. package/scripts/hooks/session-start.js +315 -295
  78. package/scripts/hooks/session-stop.js +122 -122
  79. package/scripts/init.js +0 -0
  80. package/scripts/link-resolver.js +179 -0
  81. package/scripts/memory.js +0 -0
  82. package/scripts/prompt.js +0 -0
  83. package/scripts/remove.js +0 -0
  84. package/scripts/task.js +0 -0
  85. package/scripts/telemetry/cli.js +0 -0
  86. package/scripts/telemetry/config.js +0 -0
  87. package/scripts/telemetry/crypto.js +0 -0
  88. package/scripts/telemetry/flush.js +0 -0
  89. package/scripts/telemetry/record.js +0 -0
  90. package/scripts/update.js +0 -0
  91. package/scripts/use.js +117 -11
  92. package/scripts/validate.js +0 -0
  93. package/upstream/.claude-plugin/marketplace.json +0 -0
  94. package/upstream/.claude-plugin/plugin.json +0 -0
  95. package/upstream/.codex/INSTALL.md +0 -0
  96. package/upstream/.cursor-plugin/plugin.json +0 -0
  97. package/upstream/.gitattributes +0 -0
  98. package/upstream/.github/FUNDING.yml +0 -0
  99. package/upstream/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  100. package/upstream/.github/ISSUE_TEMPLATE/config.yml +0 -0
  101. package/upstream/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  102. package/upstream/.github/ISSUE_TEMPLATE/platform_support.md +0 -0
  103. package/upstream/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  104. package/upstream/.opencode/INSTALL.md +0 -0
  105. package/upstream/.opencode/plugins/superpowers.js +0 -0
  106. package/upstream/.version-bump.json +0 -0
  107. package/upstream/AGENTS.md +0 -0
  108. package/upstream/CHANGELOG.md +0 -0
  109. package/upstream/CLAUDE.md +0 -0
  110. package/upstream/CODE_OF_CONDUCT.md +0 -0
  111. package/upstream/GEMINI.md +0 -0
  112. package/upstream/LICENSE +0 -0
  113. package/upstream/README.md +0 -0
  114. package/upstream/RELEASE-NOTES.md +0 -0
  115. package/upstream/agents/code-reviewer.md +0 -0
  116. package/upstream/commands/brainstorm.md +0 -0
  117. package/upstream/commands/execute-plan.md +0 -0
  118. package/upstream/commands/write-plan.md +0 -0
  119. package/upstream/docs/README.codex.md +0 -0
  120. package/upstream/docs/README.opencode.md +0 -0
  121. package/upstream/docs/plans/2025-11-22-opencode-support-design.md +0 -0
  122. package/upstream/docs/plans/2025-11-22-opencode-support-implementation.md +0 -0
  123. package/upstream/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md +0 -0
  124. package/upstream/docs/plans/2026-01-17-visual-brainstorming.md +0 -0
  125. package/upstream/docs/superpowers/plans/2026-01-22-document-review-system.md +0 -0
  126. package/upstream/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md +0 -0
  127. package/upstream/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md +0 -0
  128. package/upstream/docs/superpowers/plans/2026-03-23-codex-app-compatibility.md +0 -0
  129. package/upstream/docs/superpowers/specs/2026-01-22-document-review-system-design.md +0 -0
  130. package/upstream/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md +0 -0
  131. package/upstream/docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md +0 -0
  132. package/upstream/docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md +0 -0
  133. package/upstream/docs/testing.md +0 -0
  134. package/upstream/docs/windows/polyglot-hooks.md +0 -0
  135. package/upstream/gemini-extension.json +0 -0
  136. package/upstream/hooks/hooks-cursor.json +0 -0
  137. package/upstream/hooks/hooks.json +0 -0
  138. package/upstream/hooks/run-hook.cmd +0 -0
  139. package/upstream/hooks/session-start +0 -0
  140. package/upstream/package.json +0 -0
  141. package/upstream/scripts/bump-version.sh +0 -0
  142. package/upstream/skills/brainstorming/SKILL.md +0 -0
  143. package/upstream/skills/brainstorming/scripts/frame-template.html +0 -0
  144. package/upstream/skills/brainstorming/scripts/helper.js +0 -0
  145. package/upstream/skills/brainstorming/scripts/server.cjs +0 -0
  146. package/upstream/skills/brainstorming/scripts/start-server.sh +0 -0
  147. package/upstream/skills/brainstorming/scripts/stop-server.sh +0 -0
  148. package/upstream/skills/brainstorming/spec-document-reviewer-prompt.md +0 -0
  149. package/upstream/skills/brainstorming/visual-companion.md +0 -0
  150. package/upstream/skills/dispatching-parallel-agents/SKILL.md +0 -0
  151. package/upstream/skills/executing-plans/SKILL.md +0 -0
  152. package/upstream/skills/finishing-a-development-branch/SKILL.md +0 -0
  153. package/upstream/skills/receiving-code-review/SKILL.md +0 -0
  154. package/upstream/skills/requesting-code-review/SKILL.md +0 -0
  155. package/upstream/skills/requesting-code-review/code-reviewer.md +0 -0
  156. package/upstream/skills/subagent-driven-development/SKILL.md +0 -0
  157. package/upstream/skills/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
  158. package/upstream/skills/subagent-driven-development/implementer-prompt.md +0 -0
  159. package/upstream/skills/subagent-driven-development/spec-reviewer-prompt.md +0 -0
  160. package/upstream/skills/systematic-debugging/CREATION-LOG.md +0 -0
  161. package/upstream/skills/systematic-debugging/SKILL.md +0 -0
  162. package/upstream/skills/systematic-debugging/condition-based-waiting-example.ts +0 -0
  163. package/upstream/skills/systematic-debugging/condition-based-waiting.md +0 -0
  164. package/upstream/skills/systematic-debugging/defense-in-depth.md +0 -0
  165. package/upstream/skills/systematic-debugging/find-polluter.sh +0 -0
  166. package/upstream/skills/systematic-debugging/root-cause-tracing.md +0 -0
  167. package/upstream/skills/systematic-debugging/test-academic.md +0 -0
  168. package/upstream/skills/systematic-debugging/test-pressure-1.md +0 -0
  169. package/upstream/skills/systematic-debugging/test-pressure-2.md +0 -0
  170. package/upstream/skills/systematic-debugging/test-pressure-3.md +0 -0
  171. package/upstream/skills/tdd-lean/SKILL.md +0 -0
  172. package/upstream/skills/test-driven-development/SKILL.md +0 -0
  173. package/upstream/skills/test-driven-development/testing-anti-patterns.md +0 -0
  174. package/upstream/skills/using-git-worktrees/SKILL.md +0 -0
  175. package/upstream/skills/using-superpowers/SKILL.md +0 -0
  176. package/upstream/skills/using-superpowers/references/codex-tools.md +0 -0
  177. package/upstream/skills/using-superpowers/references/copilot-tools.md +0 -0
  178. package/upstream/skills/using-superpowers/references/gemini-tools.md +0 -0
  179. package/upstream/skills/verification-before-completion/SKILL.md +0 -0
  180. package/upstream/skills/writing-plans/SKILL.md +0 -0
  181. package/upstream/skills/writing-plans/plan-document-reviewer-prompt.md +0 -0
  182. package/upstream/skills/writing-skills/SKILL.md +0 -0
  183. package/upstream/skills/writing-skills/anthropic-best-practices.md +0 -0
  184. package/upstream/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -0
  185. package/upstream/skills/writing-skills/graphviz-conventions.dot +0 -0
  186. package/upstream/skills/writing-skills/persuasion-principles.md +0 -0
  187. package/upstream/skills/writing-skills/render-graphs.js +0 -0
  188. package/upstream/skills/writing-skills/testing-skills-with-subagents.md +0 -0
  189. package/upstream/tests/brainstorm-server/package-lock.json +0 -0
  190. package/upstream/tests/brainstorm-server/package.json +0 -0
  191. package/upstream/tests/brainstorm-server/server.test.js +0 -0
  192. package/upstream/tests/brainstorm-server/windows-lifecycle.test.sh +0 -0
  193. package/upstream/tests/brainstorm-server/ws-protocol.test.js +0 -0
  194. package/upstream/tests/claude-code/README.md +0 -0
  195. package/upstream/tests/claude-code/analyze-token-usage.py +0 -0
  196. package/upstream/tests/claude-code/run-skill-tests.sh +0 -0
  197. package/upstream/tests/claude-code/test-document-review-system.sh +0 -0
  198. package/upstream/tests/claude-code/test-helpers.sh +0 -0
  199. package/upstream/tests/claude-code/test-subagent-driven-development-integration.sh +0 -0
  200. package/upstream/tests/claude-code/test-subagent-driven-development.sh +0 -0
  201. package/upstream/tests/explicit-skill-requests/prompts/action-oriented.txt +0 -0
  202. package/upstream/tests/explicit-skill-requests/prompts/after-planning-flow.txt +0 -0
  203. package/upstream/tests/explicit-skill-requests/prompts/claude-suggested-it.txt +0 -0
  204. package/upstream/tests/explicit-skill-requests/prompts/i-know-what-sdd-means.txt +0 -0
  205. package/upstream/tests/explicit-skill-requests/prompts/mid-conversation-execute-plan.txt +0 -0
  206. package/upstream/tests/explicit-skill-requests/prompts/please-use-brainstorming.txt +0 -0
  207. package/upstream/tests/explicit-skill-requests/prompts/skip-formalities.txt +0 -0
  208. package/upstream/tests/explicit-skill-requests/prompts/subagent-driven-development-please.txt +0 -0
  209. package/upstream/tests/explicit-skill-requests/prompts/use-systematic-debugging.txt +0 -0
  210. package/upstream/tests/explicit-skill-requests/run-all.sh +0 -0
  211. package/upstream/tests/explicit-skill-requests/run-claude-describes-sdd.sh +0 -0
  212. package/upstream/tests/explicit-skill-requests/run-extended-multiturn-test.sh +0 -0
  213. package/upstream/tests/explicit-skill-requests/run-haiku-test.sh +0 -0
  214. package/upstream/tests/explicit-skill-requests/run-multiturn-test.sh +0 -0
  215. package/upstream/tests/explicit-skill-requests/run-test.sh +0 -0
  216. package/upstream/tests/opencode/run-tests.sh +0 -0
  217. package/upstream/tests/opencode/setup.sh +0 -0
  218. package/upstream/tests/opencode/test-plugin-loading.sh +0 -0
  219. package/upstream/tests/opencode/test-priority.sh +0 -0
  220. package/upstream/tests/opencode/test-tools.sh +0 -0
  221. package/upstream/tests/skill-triggering/prompts/dispatching-parallel-agents.txt +0 -0
  222. package/upstream/tests/skill-triggering/prompts/executing-plans.txt +0 -0
  223. package/upstream/tests/skill-triggering/prompts/requesting-code-review.txt +0 -0
  224. package/upstream/tests/skill-triggering/prompts/systematic-debugging.txt +0 -0
  225. package/upstream/tests/skill-triggering/prompts/test-driven-development.txt +0 -0
  226. package/upstream/tests/skill-triggering/prompts/writing-plans.txt +0 -0
  227. package/upstream/tests/skill-triggering/run-all.sh +0 -0
  228. package/upstream/tests/skill-triggering/run-test.sh +0 -0
  229. package/upstream/tests/subagent-driven-dev/go-fractals/design.md +0 -0
  230. package/upstream/tests/subagent-driven-dev/go-fractals/plan.md +0 -0
  231. package/upstream/tests/subagent-driven-dev/go-fractals/scaffold.sh +0 -0
  232. package/upstream/tests/subagent-driven-dev/run-test.sh +0 -0
  233. package/upstream/tests/subagent-driven-dev/svelte-todo/design.md +0 -0
  234. package/upstream/tests/subagent-driven-dev/svelte-todo/plan.md +0 -0
  235. package/upstream/tests/subagent-driven-dev/svelte-todo/scaffold.sh +0 -0
@@ -1,122 +1,122 @@
1
- #!/usr/bin/env node
2
- /**
3
- * SessionStop hook for ai-flow-kit
4
- * - Records session telemetry: model, token usage, prompt summary
5
- * - Prints a token usage summary from checkpoints.json
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
- const os = require('os');
11
-
12
- function loadTelemetry() {
13
- try { return require('../telemetry/record'); } catch (_) {}
14
- return { record: () => {} };
15
- }
16
-
17
- function loadTelemetryConfig() {
18
- try { return require('../telemetry/config'); } catch (_) {}
19
- return { loadConfig: () => ({}) };
20
- }
21
-
22
- const { record } = loadTelemetry();
23
- const { loadConfig } = loadTelemetryConfig();
24
-
25
- const GATE_LABELS = { 1: 'Analyze', 2: 'Plan', 3: 'Code', 4: 'Review', 5: 'PR' };
26
-
27
- let raw = '';
28
- process.stdin.setEncoding('utf-8');
29
- process.stdin.on('data', chunk => { raw += chunk; });
30
- process.stdin.on('end', () => {
31
- let hookData = {};
32
- try { hookData = JSON.parse(raw || '{}'); } catch (_) {}
33
-
34
- const cfg = loadConfig();
35
- let model = '';
36
- let tokens_input = 0;
37
- let tokens_output = 0;
38
- let prompt_summary = '';
39
-
40
- const transcriptPath = hookData.transcript_path;
41
- if (transcriptPath && fs.existsSync(transcriptPath)) {
42
- try {
43
- const lines = fs.readFileSync(transcriptPath, 'utf-8').split('\n');
44
- let firstUserDone = false;
45
- for (const line of lines) {
46
- if (!line.trim()) continue;
47
- let entry;
48
- try { entry = JSON.parse(line); } catch (_) { continue; }
49
-
50
- if (entry.type === 'user' && !firstUserDone) {
51
- const content = entry.message && entry.message.content;
52
- if (Array.isArray(content)) {
53
- const block = content.find(b => b.type === 'text');
54
- if (block && block.text) prompt_summary = block.text.substring(0, 200);
55
- } else if (typeof content === 'string') {
56
- prompt_summary = content.substring(0, 200);
57
- }
58
- firstUserDone = true;
59
- }
60
-
61
- if (entry.type === 'assistant') {
62
- const msg = entry.message || {};
63
- if (msg.usage) {
64
- tokens_input += msg.usage.input_tokens || 0;
65
- tokens_output += msg.usage.output_tokens || 0;
66
- }
67
- if (!model && msg.model) model = msg.model;
68
- }
69
- }
70
- } catch (_) {}
71
- }
72
-
73
- record('session.stop', {
74
- ai_email: cfg.email || '',
75
- model: model || undefined,
76
- tokens_input: tokens_input || undefined,
77
- tokens_output: tokens_output || undefined,
78
- prompt_summary: prompt_summary || undefined,
79
- });
80
-
81
- // ── Token usage summary from checkpoints ──────────────────
82
- const projectRoot = path.resolve(__dirname, '..', '..');
83
- const contextFile = path.join(projectRoot, '.aiflow', 'context', 'current.json');
84
- const tasksDir = path.join(projectRoot, '.aiflow', 'tasks');
85
-
86
- try {
87
- if (!fs.existsSync(contextFile)) process.exit(0);
88
- const ctx = JSON.parse(fs.readFileSync(contextFile, 'utf-8'));
89
- const ticketId = ctx && ctx.taskId;
90
- if (!ticketId) process.exit(0);
91
-
92
- const checkpointPath = path.join(tasksDir, ticketId, 'checkpoints.json');
93
- if (!fs.existsSync(checkpointPath)) process.exit(0);
94
-
95
- const data = JSON.parse(fs.readFileSync(checkpointPath, 'utf-8'));
96
- const checkpoints = data.checkpoints || [];
97
- if (checkpoints.length === 0) process.exit(0);
98
-
99
- const gateTokens = {};
100
- for (const cp of checkpoints) {
101
- const g = cp.gate;
102
- if (!gateTokens[g] || cp.tokens > gateTokens[g]) {
103
- gateTokens[g] = cp.tokens;
104
- }
105
- }
106
-
107
- const gates = Object.keys(gateTokens).sort();
108
- const total = gates.reduce((sum, g) => sum + gateTokens[g], 0);
109
-
110
- process.stderr.write('\n[aiflow] Token usage this session:\n');
111
- for (const g of gates) {
112
- const label = GATE_LABELS[parseInt(g)] || `Gate ${g}`;
113
- const t = gateTokens[g];
114
- const bar = '█'.repeat(Math.round((t / total) * 20)).padEnd(20, '░');
115
- const pct = Math.round((t / total) * 100);
116
- process.stderr.write(`[aiflow] Gate ${g} ${label.padEnd(8)} ~${t.toLocaleString().padStart(6)} ${bar} ${pct}%\n`);
117
- }
118
- process.stderr.write(`[aiflow] Total estimated: ~${total.toLocaleString()} tokens\n\n`);
119
- } catch (_) {}
120
-
121
- process.exit(0);
122
- });
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStop hook for ai-flow-kit
4
+ * - Records session telemetry: model, token usage, prompt summary
5
+ * - Prints a token usage summary from checkpoints.json
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+
12
+ function loadTelemetry() {
13
+ try { return require('../telemetry/record'); } catch (_) {}
14
+ return { record: () => {} };
15
+ }
16
+
17
+ function loadTelemetryConfig() {
18
+ try { return require('../telemetry/config'); } catch (_) {}
19
+ return { loadConfig: () => ({}) };
20
+ }
21
+
22
+ const { record } = loadTelemetry();
23
+ const { loadConfig } = loadTelemetryConfig();
24
+
25
+ const GATE_LABELS = { 1: 'Analyze', 2: 'Plan', 3: 'Code', 4: 'Review', 5: 'PR' };
26
+
27
+ let raw = '';
28
+ process.stdin.setEncoding('utf-8');
29
+ process.stdin.on('data', chunk => { raw += chunk; });
30
+ process.stdin.on('end', () => {
31
+ let hookData = {};
32
+ try { hookData = JSON.parse(raw || '{}'); } catch (_) {}
33
+
34
+ const cfg = loadConfig();
35
+ let model = '';
36
+ let tokens_input = 0;
37
+ let tokens_output = 0;
38
+ let prompt_summary = '';
39
+
40
+ const transcriptPath = hookData.transcript_path;
41
+ if (transcriptPath && fs.existsSync(transcriptPath)) {
42
+ try {
43
+ const lines = fs.readFileSync(transcriptPath, 'utf-8').split('\n');
44
+ let firstUserDone = false;
45
+ for (const line of lines) {
46
+ if (!line.trim()) continue;
47
+ let entry;
48
+ try { entry = JSON.parse(line); } catch (_) { continue; }
49
+
50
+ if (entry.type === 'user' && !firstUserDone) {
51
+ const content = entry.message && entry.message.content;
52
+ if (Array.isArray(content)) {
53
+ const block = content.find(b => b.type === 'text');
54
+ if (block && block.text) prompt_summary = block.text.substring(0, 200);
55
+ } else if (typeof content === 'string') {
56
+ prompt_summary = content.substring(0, 200);
57
+ }
58
+ firstUserDone = true;
59
+ }
60
+
61
+ if (entry.type === 'assistant') {
62
+ const msg = entry.message || {};
63
+ if (msg.usage) {
64
+ tokens_input += msg.usage.input_tokens || 0;
65
+ tokens_output += msg.usage.output_tokens || 0;
66
+ }
67
+ if (!model && msg.model) model = msg.model;
68
+ }
69
+ }
70
+ } catch (_) {}
71
+ }
72
+
73
+ record('session.stop', {
74
+ ai_email: cfg.email || '',
75
+ model: model || undefined,
76
+ tokens_input: tokens_input || undefined,
77
+ tokens_output: tokens_output || undefined,
78
+ prompt_summary: prompt_summary || undefined,
79
+ });
80
+
81
+ // ── Token usage summary from checkpoints ──────────────────
82
+ const projectRoot = path.resolve(__dirname, '..', '..');
83
+ const contextFile = path.join(projectRoot, '.aiflow', 'context', 'current.json');
84
+ const tasksDir = path.join(projectRoot, '.aiflow', 'tasks');
85
+
86
+ try {
87
+ if (!fs.existsSync(contextFile)) process.exit(0);
88
+ const ctx = JSON.parse(fs.readFileSync(contextFile, 'utf-8'));
89
+ const ticketId = ctx && ctx.taskId;
90
+ if (!ticketId) process.exit(0);
91
+
92
+ const checkpointPath = path.join(tasksDir, ticketId, 'checkpoints.json');
93
+ if (!fs.existsSync(checkpointPath)) process.exit(0);
94
+
95
+ const data = JSON.parse(fs.readFileSync(checkpointPath, 'utf-8'));
96
+ const checkpoints = data.checkpoints || [];
97
+ if (checkpoints.length === 0) process.exit(0);
98
+
99
+ const gateTokens = {};
100
+ for (const cp of checkpoints) {
101
+ const g = cp.gate;
102
+ if (!gateTokens[g] || cp.tokens > gateTokens[g]) {
103
+ gateTokens[g] = cp.tokens;
104
+ }
105
+ }
106
+
107
+ const gates = Object.keys(gateTokens).sort();
108
+ const total = gates.reduce((sum, g) => sum + gateTokens[g], 0);
109
+
110
+ process.stderr.write('\n[aiflow] Token usage this session:\n');
111
+ for (const g of gates) {
112
+ const label = GATE_LABELS[parseInt(g)] || `Gate ${g}`;
113
+ const t = gateTokens[g];
114
+ const bar = '█'.repeat(Math.round((t / total) * 20)).padEnd(20, '░');
115
+ const pct = Math.round((t / total) * 100);
116
+ process.stderr.write(`[aiflow] Gate ${g} ${label.padEnd(8)} ~${t.toLocaleString().padStart(6)} ${bar} ${pct}%\n`);
117
+ }
118
+ process.stderr.write(`[aiflow] Total estimated: ~${total.toLocaleString()} tokens\n\n`);
119
+ } catch (_) {}
120
+
121
+ process.exit(0);
122
+ });
package/scripts/init.js CHANGED
File without changes
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+ const https = require('https');
3
+ const chalk = require('chalk');
4
+
5
+ const BACKLOG_RE = /https?:\/\/([\w.-]+\.backlog(?:tool)?\.com)\/view\/([A-Z][A-Z0-9_]+-\d+)(?:#comment-(\d+))?/i;
6
+ const JIRA_RE = /https?:\/\/([\w.-]+\.atlassian\.net)\/browse\/([A-Z][A-Z0-9_]+-\d+)/i;
7
+ const JIRA_COMMENT_RE = /[?&]focusedCommentId=(\d+)/;
8
+ const URL_RE = /https?:\/\/[^\s\)\]"<>]+/g;
9
+
10
+ const MAX_AUTO_LINKS = 5;
11
+
12
+ function classifyLink(url) {
13
+ const bm = url.match(BACKLOG_RE);
14
+ if (bm) {
15
+ const [, domain, ticketId, commentId] = bm;
16
+ return { adapter: 'backlog', domain, ticketId, commentId: commentId || null, isComment: !!commentId };
17
+ }
18
+ const jm = url.match(JIRA_RE);
19
+ if (jm) {
20
+ const [, domain, ticketId] = jm;
21
+ const cm = url.match(JIRA_COMMENT_RE);
22
+ const commentId = cm ? cm[1] : null;
23
+ return { adapter: 'jira', domain, ticketId, commentId, isComment: !!commentId };
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function scanLinks(text) {
29
+ const found = [];
30
+ let m;
31
+ URL_RE.lastIndex = 0;
32
+ while ((m = URL_RE.exec(text)) !== null) {
33
+ const classified = classifyLink(m[0]);
34
+ if (classified) found.push({ url: m[0], ...classified });
35
+ }
36
+ return found;
37
+ }
38
+
39
+ function httpsGet(url, headers) {
40
+ return new Promise((resolve, reject) => {
41
+ const opts = headers ? { headers } : {};
42
+ https.get(url, opts, (res) => {
43
+ let data = '';
44
+ res.on('data', chunk => (data += chunk));
45
+ res.on('end', () => {
46
+ if (res.statusCode !== 200) {
47
+ reject(new Error(`HTTP ${res.statusCode}`));
48
+ return;
49
+ }
50
+ try { resolve(JSON.parse(data)); }
51
+ catch (e) { reject(new Error('Invalid JSON from API')); }
52
+ });
53
+ }).on('error', reject);
54
+ });
55
+ }
56
+
57
+ async function fetchBacklogTicket(domain, apiKey, ticketId) {
58
+ const issue = await httpsGet(`https://${domain}/api/v2/issues/${ticketId}?apiKey=${apiKey}`);
59
+ return {
60
+ sourceType: 'ticket',
61
+ sourceUrl: `https://${domain}/view/${ticketId}`,
62
+ ticketId,
63
+ title: issue.summary || '',
64
+ description: (issue.description || '').substring(0, 2000),
65
+ };
66
+ }
67
+
68
+ async function fetchBacklogComment(domain, apiKey, ticketId, commentId) {
69
+ const comments = await httpsGet(
70
+ `https://${domain}/api/v2/issues/${ticketId}/comments?apiKey=${apiKey}&count=100&order=asc`
71
+ );
72
+ const comment = (comments || []).find(c => String(c.id) === String(commentId));
73
+ if (!comment) throw new Error(`Comment ${commentId} not found in ${ticketId}`);
74
+ const date = comment.created ? new Date(comment.created).toLocaleDateString('vi-VN') : '';
75
+ return {
76
+ sourceType: 'comment',
77
+ sourceUrl: `https://${domain}/view/${ticketId}#comment-${commentId}`,
78
+ ticketId,
79
+ commentId: String(commentId),
80
+ author: comment.createdUser?.name || 'Unknown',
81
+ date,
82
+ content: (comment.content || '').substring(0, 2000),
83
+ };
84
+ }
85
+
86
+ function jiraAuth(email, token) {
87
+ return `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}`;
88
+ }
89
+
90
+ function extractJiraText(descField) {
91
+ if (!descField) return '';
92
+ return (descField.content || [])
93
+ .map(block => (block.content || []).map(c => c.text || '').join(''))
94
+ .join('\n');
95
+ }
96
+
97
+ async function fetchJiraTicket(domain, email, token, ticketId) {
98
+ const issue = await httpsGet(
99
+ `https://${domain}/rest/api/3/issue/${ticketId}`,
100
+ { Authorization: jiraAuth(email, token), Accept: 'application/json' }
101
+ );
102
+ const fields = issue.fields || {};
103
+ return {
104
+ sourceType: 'ticket',
105
+ sourceUrl: `https://${domain}/browse/${ticketId}`,
106
+ ticketId,
107
+ title: fields.summary || '',
108
+ description: extractJiraText(fields.description).substring(0, 2000),
109
+ };
110
+ }
111
+
112
+ async function fetchJiraComment(domain, email, token, ticketId, commentId) {
113
+ const data = await httpsGet(
114
+ `https://${domain}/rest/api/3/issue/${ticketId}/comment/${commentId}`,
115
+ { Authorization: jiraAuth(email, token), Accept: 'application/json' }
116
+ );
117
+ const date = data.created ? new Date(data.created).toLocaleDateString('vi-VN') : '';
118
+ return {
119
+ sourceType: 'comment',
120
+ sourceUrl: `https://${domain}/browse/${ticketId}?focusedCommentId=${commentId}`,
121
+ ticketId,
122
+ commentId: String(commentId),
123
+ author: data.author?.displayName || 'Unknown',
124
+ date,
125
+ content: extractJiraText(data.body).substring(0, 2000),
126
+ };
127
+ }
128
+
129
+ async function fetchLink(url, credentials = {}, alreadyLoadedIds = []) {
130
+ const classified = classifyLink(url);
131
+ if (!classified) return null;
132
+
133
+ const { adapter, domain, ticketId, commentId, isComment } = classified;
134
+ const dedupeKey = ticketId + (commentId ? '#' + commentId : '');
135
+ if (alreadyLoadedIds.includes(dedupeKey) || alreadyLoadedIds.includes(ticketId)) return null;
136
+
137
+ if (adapter === 'backlog') {
138
+ const apiKey = credentials.BACKLOG_API_KEY;
139
+ if (!apiKey) throw new Error('BACKLOG_API_KEY not set');
140
+ if (isComment) return await fetchBacklogComment(domain, apiKey, ticketId, commentId);
141
+ return await fetchBacklogTicket(domain, apiKey, ticketId);
142
+ }
143
+
144
+ if (adapter === 'jira') {
145
+ const email = credentials.JIRA_EMAIL;
146
+ const token = credentials.JIRA_API_TOKEN;
147
+ if (!email || !token) throw new Error('JIRA_EMAIL or JIRA_API_TOKEN not set');
148
+ if (isComment) return await fetchJiraComment(domain, email, token, ticketId, commentId);
149
+ return await fetchJiraTicket(domain, email, token, ticketId);
150
+ }
151
+
152
+ return null;
153
+ }
154
+
155
+ async function resolveLinks(description, credentials = {}, alreadyLoadedIds = []) {
156
+ const links = scanLinks(description).slice(0, MAX_AUTO_LINKS);
157
+ if (!links.length) return [];
158
+
159
+ const results = [];
160
+ const seen = [...alreadyLoadedIds];
161
+
162
+ for (const link of links) {
163
+ const dedupeKey = link.ticketId + (link.commentId ? '#' + link.commentId : '');
164
+ if (seen.includes(dedupeKey) || seen.includes(link.ticketId)) continue;
165
+ try {
166
+ const item = await fetchLink(link.url, credentials, seen);
167
+ if (item) {
168
+ results.push(item);
169
+ seen.push(dedupeKey);
170
+ console.log(chalk.gray(` ✓ Auto-resolved: ${link.ticketId}${link.commentId ? '#' + link.commentId : ''}`));
171
+ }
172
+ } catch (err) {
173
+ console.log(chalk.yellow(` ⚠ Could not resolve ${link.url}: ${err.message}`));
174
+ }
175
+ }
176
+ return results;
177
+ }
178
+
179
+ module.exports = { classifyLink, scanLinks, fetchBacklogTicket, fetchBacklogComment, fetchJiraTicket, fetchJiraComment, fetchLink, resolveLinks };
package/scripts/memory.js CHANGED
File without changes
package/scripts/prompt.js CHANGED
File without changes
package/scripts/remove.js CHANGED
File without changes
package/scripts/task.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/scripts/update.js CHANGED
File without changes
package/scripts/use.js CHANGED
@@ -14,7 +14,12 @@ const CONTEXT_DIR = path.join(PROJECT_DIR, ".aiflow", "context");
14
14
  // Entry point
15
15
  // ──────────────────────────────────────────────────────────────
16
16
 
17
- module.exports = async function use(target, options = {}) {
17
+ const useCommand = async function use(targets, options = {}) {
18
+ // Normalize: accept string (legacy) or array (new multi-target)
19
+ const targetList = Array.isArray(targets) ? targets.filter(Boolean) : (targets ? [targets] : []);
20
+ const primaryTarget = targetList[0] || null;
21
+ const supplementaryTargets = targetList.slice(1);
22
+
18
23
  if (!(await fs.pathExists(STATE_FILE))) {
19
24
  console.log(
20
25
  chalk.red("Project is not initialized. Run `aiflow init` first."),
@@ -36,28 +41,30 @@ module.exports = async function use(target, options = {}) {
36
41
  }
37
42
 
38
43
  // ── No target → version switcher ──
39
- if (!target) {
44
+ if (!primaryTarget) {
40
45
  return await switchVersion();
41
46
  }
42
47
 
43
48
  // ── Detect target type and dispatch ──
44
- if (isVersion(target)) {
45
- return await switchVersion(target);
49
+ if (isVersion(primaryTarget)) {
50
+ return await switchVersion(primaryTarget);
46
51
  }
47
52
 
48
- if (isTicketId(target)) {
53
+ if (isTicketId(primaryTarget)) {
49
54
  const adapter = await resolveTicketAdapter();
50
- if (adapter === "jira") return await loadFromJira(target, options);
51
- return await loadFromBacklog(target, options);
55
+ if (adapter === "jira") await loadFromJira(primaryTarget, options);
56
+ else await loadFromBacklog(primaryTarget, options);
57
+ return await enrichWithSupplementary(supplementaryTargets);
52
58
  }
53
59
 
54
- if (isBacklogUrl(target)) {
55
- const id = extractBacklogId(target);
56
- return await loadFromBacklog(id, options);
60
+ if (isBacklogUrl(primaryTarget)) {
61
+ const id = extractBacklogId(primaryTarget);
62
+ await loadFromBacklog(id, options);
63
+ return await enrichWithSupplementary(supplementaryTargets);
57
64
  }
58
65
 
59
66
  // ── Fallback: try Backlog then manual ──
60
- console.log(chalk.yellow(`Cannot detect format for: ${target}`));
67
+ console.log(chalk.yellow(`Cannot detect format for: ${primaryTarget}`));
61
68
  console.log(
62
69
  chalk.gray("Supported: PROJ-33, APP-123, version number, --manual"),
63
70
  );
@@ -1054,3 +1061,102 @@ async function suggestNextStep() {
1054
1061
  );
1055
1062
  console.log();
1056
1063
  }
1064
+
1065
+ async function loadTargetAsSupplementaryContext(target, creds, alreadyIds) {
1066
+ if (!target) return null;
1067
+
1068
+ // File path (not a ticket ID, not a backlog URL)
1069
+ if (!isTicketId(target) && !isBacklogUrl(target)) {
1070
+ try {
1071
+ const raw = await fs.readFile(target, 'utf-8');
1072
+ return {
1073
+ sourceType: 'file',
1074
+ sourcePath: target,
1075
+ title: path.basename(target),
1076
+ content: raw.substring(0, 3000),
1077
+ };
1078
+ } catch (err) {
1079
+ console.log(chalk.yellow(` ⚠ Could not read file: ${target}`));
1080
+ return null;
1081
+ }
1082
+ }
1083
+
1084
+ const issueKey = isBacklogUrl(target) ? extractBacklogId(target) : target;
1085
+ if (!issueKey || alreadyIds.includes(issueKey)) return null;
1086
+
1087
+ const adapter = await resolveTicketAdapter();
1088
+
1089
+ try {
1090
+ if (adapter === 'jira') {
1091
+ const token = creds.JIRA_API_TOKEN;
1092
+ const email = creds.JIRA_EMAIL;
1093
+ const domain = creds.JIRA_DOMAIN;
1094
+ if (!token || !email || !domain) return null;
1095
+ const issue = await fetchJiraIssue(domain, email, token, issueKey);
1096
+ const fields = issue.fields || {};
1097
+ const desc = (fields.description?.content || [])
1098
+ .map(b => (b.content || []).map(c => c.text || '').join('')).join('\n');
1099
+ return {
1100
+ sourceType: 'ticket',
1101
+ sourceUrl: `https://${domain}.atlassian.net/browse/${issueKey}`,
1102
+ ticketId: issueKey,
1103
+ title: fields.summary || '',
1104
+ description: desc.substring(0, 2000),
1105
+ };
1106
+ }
1107
+ const apiKey = creds.BACKLOG_API_KEY;
1108
+ const spaceKey = creds.BACKLOG_SPACE_KEY || creds.BACKLOG_DOMAIN;
1109
+ if (!apiKey || !spaceKey) return null;
1110
+ const domain = spaceKey.includes('.') ? spaceKey : `${spaceKey}.backlog.com`;
1111
+ const issue = await fetchBacklogIssue(domain, apiKey, issueKey);
1112
+ return {
1113
+ sourceType: 'ticket',
1114
+ sourceUrl: `https://${domain}/view/${issueKey}`,
1115
+ ticketId: issueKey,
1116
+ title: issue.summary || '',
1117
+ description: (issue.description || '').substring(0, 2000),
1118
+ };
1119
+ } catch (err) {
1120
+ console.log(chalk.yellow(` ⚠ Could not load supplementary ${issueKey}: ${err.message}`));
1121
+ return null;
1122
+ }
1123
+ }
1124
+
1125
+ async function enrichWithSupplementary(supplementaryTargets) {
1126
+ const currentFile = path.join(CONTEXT_DIR, 'current.json');
1127
+ if (!(await fs.pathExists(currentFile))) return;
1128
+
1129
+ const ctx = await fs.readJson(currentFile).catch(() => null);
1130
+ if (!ctx) return;
1131
+
1132
+ const creds = await loadCredentials();
1133
+ const supplementary = [];
1134
+ const alreadyIds = [ctx.taskId].filter(Boolean);
1135
+
1136
+ for (const t of supplementaryTargets) {
1137
+ const sc = await loadTargetAsSupplementaryContext(t, creds, alreadyIds);
1138
+ if (sc) {
1139
+ supplementary.push(sc);
1140
+ if (sc.ticketId) alreadyIds.push(sc.ticketId);
1141
+ }
1142
+ }
1143
+
1144
+ if (ctx.description) {
1145
+ const { resolveLinks } = require('./link-resolver');
1146
+ const linked = await resolveLinks(ctx.description, creds, alreadyIds);
1147
+ supplementary.push(...linked);
1148
+ }
1149
+
1150
+ if (supplementary.length > 0) {
1151
+ ctx.supplementaryContext = supplementary;
1152
+ await fs.writeJson(currentFile, ctx, { spaces: 2 });
1153
+ const histFile = path.join(CONTEXT_DIR, 'history', `${ctx.taskId || 'manual'}.json`);
1154
+ if (await fs.pathExists(path.dirname(histFile))) {
1155
+ await fs.writeJson(histFile, ctx, { spaces: 2 });
1156
+ }
1157
+ console.log(chalk.green(` ✓ ${supplementary.length} supplementary context(s) linked`));
1158
+ }
1159
+ }
1160
+
1161
+ module.exports = useCommand;
1162
+ module.exports.loadCredentials = loadCredentials;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/upstream/LICENSE CHANGED
File without changes
File without changes
File without changes