@relipa/ai-flow-kit 0.0.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/AIFLOW.md +432 -0
- package/CHANGELOG.md +78 -0
- package/CONTRIBUTING.md +351 -0
- package/IMPLEMENTATION_SUMMARY.md +348 -0
- package/QUICK_START.md +392 -0
- package/README.md +323 -0
- package/bin/aiflow.js +178 -0
- package/custom/mcp-presets/README.md +138 -0
- package/custom/mcp-presets/backlog.json +15 -0
- package/custom/mcp-presets/figma.json +15 -0
- package/custom/mcp-presets/google-sheets.json +14 -0
- package/custom/mcp-presets/jira.json +16 -0
- package/custom/prompts/bug-fix.md +149 -0
- package/custom/prompts/feature.md +190 -0
- package/custom/prompts/investigation.md +164 -0
- package/custom/rules/code-style.md +7 -0
- package/custom/rules/java/code-style.md +11 -0
- package/custom/rules/java/naming.md +17 -0
- package/custom/rules/java/review-checklist.md +36 -0
- package/custom/rules/javascript/code-style.md +12 -0
- package/custom/rules/javascript/naming.md +14 -0
- package/custom/rules/naming.md +9 -0
- package/custom/rules/php/code-style.md +10 -0
- package/custom/rules/php/naming.md +15 -0
- package/custom/rules/review-checklist.md +9 -0
- package/custom/skills/figma-to-component/SKILL.md +207 -0
- package/custom/skills/generate-spec/SKILL.md +97 -0
- package/custom/skills/impact-analysis/SKILL.md +94 -0
- package/custom/skills/investigate-bug/SKILL.md +93 -0
- package/custom/skills/report-customer/SKILL.md +95 -0
- package/custom/skills/review-plan/SKILL.md +158 -0
- package/custom/skills/validate-ticket/SKILL.md +240 -0
- package/custom/templates/laravel.md +90 -0
- package/custom/templates/nextjs.md +89 -0
- package/custom/templates/reactjs.md +567 -0
- package/custom/templates/spring-boot.md +598 -0
- package/custom/templates/vue-nuxt.md +89 -0
- package/docs/architecture.md +395 -0
- package/docs/cli-reference.md +438 -0
- package/docs/configuration.md +412 -0
- package/docs/getting-started.md +248 -0
- package/docs/troubleshooting.md +587 -0
- package/docs/workflows/bug-fix.md +126 -0
- package/docs/workflows/feature.md +123 -0
- package/docs/workflows/impact-analysis.md +13 -0
- package/docs/workflows/investigation.md +13 -0
- package/docs/workflows/refactor.md +13 -0
- package/index.js +27 -0
- package/package.json +70 -0
- package/scripts/config.js +317 -0
- package/scripts/context.js +117 -0
- package/scripts/detect.js +239 -0
- package/scripts/doctor.js +48 -0
- package/scripts/guide.js +282 -0
- package/scripts/hooks/session-start.js +140 -0
- package/scripts/init.js +453 -0
- package/scripts/memory.js +247 -0
- package/scripts/prompt.js +392 -0
- package/scripts/remove.js +170 -0
- package/scripts/update.js +111 -0
- package/scripts/use.js +570 -0
- package/scripts/validate.js +303 -0
- package/upstream/.claude-plugin/marketplace.json +20 -0
- package/upstream/.claude-plugin/plugin.json +20 -0
- package/upstream/.codex/INSTALL.md +67 -0
- package/upstream/.cursor-plugin/plugin.json +25 -0
- package/upstream/.gitattributes +18 -0
- package/upstream/.github/FUNDING.yml +3 -0
- package/upstream/.github/ISSUE_TEMPLATE/bug_report.md +52 -0
- package/upstream/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/upstream/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
- package/upstream/.github/ISSUE_TEMPLATE/platform_support.md +23 -0
- package/upstream/.github/PULL_REQUEST_TEMPLATE.md +87 -0
- package/upstream/.opencode/INSTALL.md +83 -0
- package/upstream/.opencode/plugins/superpowers.js +112 -0
- package/upstream/.version-bump.json +19 -0
- package/upstream/AGENTS.md +1 -0
- package/upstream/CHANGELOG.md +13 -0
- package/upstream/CLAUDE.md +85 -0
- package/upstream/CODE_OF_CONDUCT.md +128 -0
- package/upstream/GEMINI.md +2 -0
- package/upstream/LICENSE +21 -0
- package/upstream/README.md +190 -0
- package/upstream/RELEASE-NOTES.md +1096 -0
- package/upstream/agents/code-reviewer.md +48 -0
- package/upstream/commands/brainstorm.md +5 -0
- package/upstream/commands/execute-plan.md +5 -0
- package/upstream/commands/write-plan.md +5 -0
- package/upstream/docs/README.codex.md +126 -0
- package/upstream/docs/README.opencode.md +130 -0
- package/upstream/docs/plans/2025-11-22-opencode-support-design.md +294 -0
- package/upstream/docs/plans/2025-11-22-opencode-support-implementation.md +1095 -0
- package/upstream/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md +711 -0
- package/upstream/docs/plans/2026-01-17-visual-brainstorming.md +571 -0
- package/upstream/docs/superpowers/plans/2026-01-22-document-review-system.md +301 -0
- package/upstream/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md +523 -0
- package/upstream/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md +479 -0
- package/upstream/docs/superpowers/plans/2026-03-23-codex-app-compatibility.md +564 -0
- package/upstream/docs/superpowers/specs/2026-01-22-document-review-system-design.md +136 -0
- package/upstream/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md +162 -0
- package/upstream/docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md +118 -0
- package/upstream/docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md +244 -0
- package/upstream/docs/testing.md +303 -0
- package/upstream/docs/windows/polyglot-hooks.md +212 -0
- package/upstream/gemini-extension.json +6 -0
- package/upstream/hooks/hooks-cursor.json +10 -0
- package/upstream/hooks/hooks.json +16 -0
- package/upstream/hooks/run-hook.cmd +46 -0
- package/upstream/hooks/session-start +57 -0
- package/upstream/package.json +6 -0
- package/upstream/scripts/bump-version.sh +220 -0
- package/upstream/skills/brainstorming/SKILL.md +164 -0
- package/upstream/skills/brainstorming/scripts/frame-template.html +214 -0
- package/upstream/skills/brainstorming/scripts/helper.js +88 -0
- package/upstream/skills/brainstorming/scripts/server.cjs +354 -0
- package/upstream/skills/brainstorming/scripts/start-server.sh +148 -0
- package/upstream/skills/brainstorming/scripts/stop-server.sh +56 -0
- package/upstream/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/upstream/skills/brainstorming/visual-companion.md +287 -0
- package/upstream/skills/dispatching-parallel-agents/SKILL.md +182 -0
- package/upstream/skills/executing-plans/SKILL.md +70 -0
- package/upstream/skills/finishing-a-development-branch/SKILL.md +200 -0
- package/upstream/skills/receiving-code-review/SKILL.md +213 -0
- package/upstream/skills/requesting-code-review/SKILL.md +105 -0
- package/upstream/skills/requesting-code-review/code-reviewer.md +146 -0
- package/upstream/skills/subagent-driven-development/SKILL.md +277 -0
- package/upstream/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/upstream/skills/subagent-driven-development/implementer-prompt.md +113 -0
- package/upstream/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/upstream/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/upstream/skills/systematic-debugging/SKILL.md +296 -0
- package/upstream/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/upstream/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/upstream/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/upstream/skills/systematic-debugging/find-polluter.sh +63 -0
- package/upstream/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/upstream/skills/systematic-debugging/test-academic.md +14 -0
- package/upstream/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/upstream/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/upstream/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/upstream/skills/test-driven-development/SKILL.md +371 -0
- package/upstream/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/upstream/skills/using-git-worktrees/SKILL.md +218 -0
- package/upstream/skills/using-superpowers/SKILL.md +117 -0
- package/upstream/skills/using-superpowers/references/codex-tools.md +100 -0
- package/upstream/skills/using-superpowers/references/copilot-tools.md +52 -0
- package/upstream/skills/using-superpowers/references/gemini-tools.md +33 -0
- package/upstream/skills/verification-before-completion/SKILL.md +139 -0
- package/upstream/skills/writing-plans/SKILL.md +152 -0
- package/upstream/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/upstream/skills/writing-skills/SKILL.md +655 -0
- package/upstream/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/upstream/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/upstream/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/upstream/skills/writing-skills/persuasion-principles.md +187 -0
- package/upstream/skills/writing-skills/render-graphs.js +168 -0
- package/upstream/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/upstream/tests/brainstorm-server/package-lock.json +36 -0
- package/upstream/tests/brainstorm-server/package.json +10 -0
- package/upstream/tests/brainstorm-server/server.test.js +427 -0
- package/upstream/tests/brainstorm-server/windows-lifecycle.test.sh +351 -0
- package/upstream/tests/brainstorm-server/ws-protocol.test.js +392 -0
- package/upstream/tests/claude-code/README.md +158 -0
- package/upstream/tests/claude-code/analyze-token-usage.py +168 -0
- package/upstream/tests/claude-code/run-skill-tests.sh +187 -0
- package/upstream/tests/claude-code/test-document-review-system.sh +177 -0
- package/upstream/tests/claude-code/test-helpers.sh +202 -0
- package/upstream/tests/claude-code/test-subagent-driven-development-integration.sh +314 -0
- package/upstream/tests/claude-code/test-subagent-driven-development.sh +165 -0
- package/upstream/tests/explicit-skill-requests/prompts/action-oriented.txt +3 -0
- package/upstream/tests/explicit-skill-requests/prompts/after-planning-flow.txt +17 -0
- package/upstream/tests/explicit-skill-requests/prompts/claude-suggested-it.txt +11 -0
- package/upstream/tests/explicit-skill-requests/prompts/i-know-what-sdd-means.txt +8 -0
- package/upstream/tests/explicit-skill-requests/prompts/mid-conversation-execute-plan.txt +3 -0
- package/upstream/tests/explicit-skill-requests/prompts/please-use-brainstorming.txt +1 -0
- package/upstream/tests/explicit-skill-requests/prompts/skip-formalities.txt +3 -0
- package/upstream/tests/explicit-skill-requests/prompts/subagent-driven-development-please.txt +1 -0
- package/upstream/tests/explicit-skill-requests/prompts/use-systematic-debugging.txt +1 -0
- package/upstream/tests/explicit-skill-requests/run-all.sh +70 -0
- package/upstream/tests/explicit-skill-requests/run-claude-describes-sdd.sh +100 -0
- package/upstream/tests/explicit-skill-requests/run-extended-multiturn-test.sh +113 -0
- package/upstream/tests/explicit-skill-requests/run-haiku-test.sh +144 -0
- package/upstream/tests/explicit-skill-requests/run-multiturn-test.sh +143 -0
- package/upstream/tests/explicit-skill-requests/run-test.sh +136 -0
- package/upstream/tests/opencode/run-tests.sh +163 -0
- package/upstream/tests/opencode/setup.sh +88 -0
- package/upstream/tests/opencode/test-plugin-loading.sh +82 -0
- package/upstream/tests/opencode/test-priority.sh +198 -0
- package/upstream/tests/opencode/test-tools.sh +104 -0
- package/upstream/tests/skill-triggering/prompts/dispatching-parallel-agents.txt +8 -0
- package/upstream/tests/skill-triggering/prompts/executing-plans.txt +1 -0
- package/upstream/tests/skill-triggering/prompts/requesting-code-review.txt +3 -0
- package/upstream/tests/skill-triggering/prompts/systematic-debugging.txt +11 -0
- package/upstream/tests/skill-triggering/prompts/test-driven-development.txt +7 -0
- package/upstream/tests/skill-triggering/prompts/writing-plans.txt +10 -0
- package/upstream/tests/skill-triggering/run-all.sh +60 -0
- package/upstream/tests/skill-triggering/run-test.sh +88 -0
- package/upstream/tests/subagent-driven-dev/go-fractals/design.md +81 -0
- package/upstream/tests/subagent-driven-dev/go-fractals/plan.md +172 -0
- package/upstream/tests/subagent-driven-dev/go-fractals/scaffold.sh +45 -0
- package/upstream/tests/subagent-driven-dev/run-test.sh +106 -0
- package/upstream/tests/subagent-driven-dev/svelte-todo/design.md +70 -0
- package/upstream/tests/subagent-driven-dev/svelte-todo/plan.md +222 -0
- package/upstream/tests/subagent-driven-dev/svelte-todo/scaffold.sh +46 -0
package/scripts/use.js
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const { select, input } = require('@inquirer/prompts');
|
|
6
|
+
|
|
7
|
+
const PROJECT_DIR = process.cwd();
|
|
8
|
+
const AIFLOW_DIR = path.join(PROJECT_DIR, '.aiflow');
|
|
9
|
+
const STATE_FILE = path.join(AIFLOW_DIR, 'state.json');
|
|
10
|
+
const CONTEXT_DIR = path.join(PROJECT_DIR, '.claude', 'context');
|
|
11
|
+
|
|
12
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
13
|
+
// Entry point
|
|
14
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
15
|
+
|
|
16
|
+
module.exports = async function use(target, options = {}) {
|
|
17
|
+
if (!(await fs.pathExists(STATE_FILE))) {
|
|
18
|
+
console.log(chalk.red('Project is not initialized. Run `aiflow init` first.'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// โโ Manual entry โโ
|
|
23
|
+
if (options.manual) {
|
|
24
|
+
return await manualContext();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// โโ Load from local file โโ
|
|
28
|
+
if (options.file) {
|
|
29
|
+
return await loadFromFile(options.file, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// โโ No target โ version switcher โโ
|
|
33
|
+
if (!target) {
|
|
34
|
+
return await switchVersion();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// โโ Detect target type and dispatch โโ
|
|
38
|
+
if (isVersion(target)) {
|
|
39
|
+
return await switchVersion(target);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isTicketId(target)) {
|
|
43
|
+
const adapter = await resolveTicketAdapter();
|
|
44
|
+
if (adapter === 'jira') return await loadFromJira(target, options);
|
|
45
|
+
return await loadFromBacklog(target, options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (isBacklogUrl(target)) {
|
|
49
|
+
const id = extractBacklogId(target);
|
|
50
|
+
return await loadFromBacklog(id, options);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// โโ Fallback: try Backlog then manual โโ
|
|
54
|
+
console.log(chalk.yellow(`Cannot detect format for: ${target}`));
|
|
55
|
+
console.log(chalk.gray('Supported: PROJ-33, APP-123, version number, --manual'));
|
|
56
|
+
return await manualContext();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
60
|
+
// Detectors
|
|
61
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
62
|
+
|
|
63
|
+
function isVersion(s) {
|
|
64
|
+
return /^\d+\.\d+\.\d+$/.test(s);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Both Backlog and Jira use the same PROJECT-NUMBER format. */
|
|
68
|
+
function isTicketId(s) {
|
|
69
|
+
return /^[A-Z][A-Z0-9_]+-\d+$/.test(s);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Determine which adapter to use for a ticket ID based on configured adapters
|
|
74
|
+
* in state.json. Falls back to backlog if both or neither are configured.
|
|
75
|
+
*/
|
|
76
|
+
async function resolveTicketAdapter() {
|
|
77
|
+
try {
|
|
78
|
+
if (await fs.pathExists(STATE_FILE)) {
|
|
79
|
+
const state = await fs.readJson(STATE_FILE);
|
|
80
|
+
const adapters = state.adapters || [];
|
|
81
|
+
if (adapters.includes('jira') && !adapters.includes('backlog')) {
|
|
82
|
+
return 'jira';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (_) { /* ignore */ }
|
|
86
|
+
return 'backlog';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isBacklogUrl(s) {
|
|
90
|
+
return s.includes('backlog.com/view/') || s.includes('backlogtool.com/view/');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractBacklogId(url) {
|
|
94
|
+
const match = url.match(/\/view\/([A-Z][A-Z0-9_]+-\d+)/);
|
|
95
|
+
return match ? match[1] : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
99
|
+
// Version switcher (original behavior)
|
|
100
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
101
|
+
|
|
102
|
+
async function switchVersion(versionReq) {
|
|
103
|
+
const versionsDir = path.join(AIFLOW_DIR, 'versions');
|
|
104
|
+
if (!(await fs.pathExists(versionsDir))) {
|
|
105
|
+
console.log(chalk.red('No versions directory found.'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const available = await fs.readdir(versionsDir);
|
|
110
|
+
if (!available.length) {
|
|
111
|
+
console.log(chalk.red('No installed versions found.'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let targetVersion = versionReq;
|
|
116
|
+
if (!targetVersion) {
|
|
117
|
+
targetVersion = await select({
|
|
118
|
+
message: 'Select version to use:',
|
|
119
|
+
choices: available.map(v => ({ name: `v${v}`, value: v }))
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!available.includes(targetVersion)) {
|
|
124
|
+
console.log(chalk.red(`Version v${targetVersion} is not installed.`));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log(chalk.blue(`Switching to v${targetVersion}...`));
|
|
129
|
+
const versionDir = path.join(versionsDir, targetVersion);
|
|
130
|
+
|
|
131
|
+
const claudeDir = path.join(PROJECT_DIR, '.claude');
|
|
132
|
+
await fs.emptyDir(claudeDir);
|
|
133
|
+
await fs.copy(path.join(versionDir, 'skills'), path.join(claudeDir, 'skills'), { overwrite: true });
|
|
134
|
+
|
|
135
|
+
const rulesDir = path.join(PROJECT_DIR, '.rules');
|
|
136
|
+
await fs.emptyDir(rulesDir);
|
|
137
|
+
await fs.copy(path.join(versionDir, 'rules'), rulesDir, { overwrite: true });
|
|
138
|
+
|
|
139
|
+
const state = await fs.readJson(STATE_FILE);
|
|
140
|
+
state.current_version = targetVersion;
|
|
141
|
+
await fs.writeJson(STATE_FILE, state);
|
|
142
|
+
|
|
143
|
+
console.log(chalk.green(`โ Switched to v${targetVersion}.`));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
147
|
+
// Backlog context loader
|
|
148
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
149
|
+
|
|
150
|
+
async function loadFromBacklog(issueKey, options = {}) {
|
|
151
|
+
const creds = await loadCredentials();
|
|
152
|
+
const apiKey = process.env.BACKLOG_API_KEY || creds.BACKLOG_API_KEY;
|
|
153
|
+
const spaceKey = process.env.BACKLOG_SPACE_KEY || creds.BACKLOG_SPACE_KEY
|
|
154
|
+
|| process.env.BACKLOG_DOMAIN || creds.BACKLOG_DOMAIN;
|
|
155
|
+
|
|
156
|
+
if (!apiKey || !spaceKey) {
|
|
157
|
+
console.log(chalk.yellow('โ Backlog credentials not set.'));
|
|
158
|
+
console.log(chalk.gray('Run: aiflow init --adapter backlog'));
|
|
159
|
+
console.log(chalk.gray('Or set environment variables:'));
|
|
160
|
+
console.log(chalk.gray(' BACKLOG_API_KEY=your-api-key'));
|
|
161
|
+
console.log(chalk.gray(' BACKLOG_SPACE_KEY=your-space (e.g. mycompany)'));
|
|
162
|
+
console.log(chalk.gray('\nFalling back to manual entry...\n'));
|
|
163
|
+
return await manualContext(issueKey);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// spaceKey can be full domain (mycompany.backlog.com) or just the space (mycompany)
|
|
167
|
+
const domain = spaceKey.includes('.') ? spaceKey : `${spaceKey}.backlog.com`;
|
|
168
|
+
|
|
169
|
+
console.log(chalk.blue(`Fetching context from Backlog: ${issueKey}...`));
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// Default: description only. Comments loaded only when explicitly requested.
|
|
173
|
+
const loadComments = options.withComments || options.commentsLast != null || options.commentsFrom != null;
|
|
174
|
+
const [issue, rawComments] = await Promise.all([
|
|
175
|
+
fetchBacklogIssue(domain, apiKey, issueKey),
|
|
176
|
+
loadComments
|
|
177
|
+
? fetchBacklogComments(domain, apiKey, issueKey)
|
|
178
|
+
: Promise.resolve([]),
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
const comments = filterComments(rawComments, options);
|
|
182
|
+
const context = buildContextFromBacklog(issue, comments, issueKey, domain);
|
|
183
|
+
await saveContext(context, options.save);
|
|
184
|
+
printContextSummary(context);
|
|
185
|
+
suggestNextStep(context);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.log(chalk.yellow(`โ Could not fetch from Backlog: ${err.message}`));
|
|
188
|
+
console.log(chalk.gray('Falling back to manual entry...\n'));
|
|
189
|
+
await manualContext(issueKey);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Filter comments array based on CLI options:
|
|
195
|
+
* --no-comments โ empty array (already handled upstream, but safe here too)
|
|
196
|
+
* --comments-last N โ last N comments
|
|
197
|
+
* --comments-from N โ comments starting at index N (0-based)
|
|
198
|
+
* (default) โ all comments
|
|
199
|
+
*/
|
|
200
|
+
function filterComments(comments, options = {}) {
|
|
201
|
+
if (!comments || !comments.length) return [];
|
|
202
|
+
|
|
203
|
+
if (options.commentsLast != null) {
|
|
204
|
+
const n = Math.max(1, options.commentsLast);
|
|
205
|
+
return comments.slice(-n);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (options.commentsFrom != null) {
|
|
209
|
+
const from = Math.max(0, options.commentsFrom);
|
|
210
|
+
return comments.slice(from);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return comments;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Generic Backlog GET helper
|
|
218
|
+
*/
|
|
219
|
+
function backlogGet(url) {
|
|
220
|
+
return new Promise((resolve, reject) => {
|
|
221
|
+
https.get(url, (res) => {
|
|
222
|
+
let data = '';
|
|
223
|
+
res.on('data', chunk => data += chunk);
|
|
224
|
+
res.on('end', () => {
|
|
225
|
+
if (res.statusCode !== 200) {
|
|
226
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
resolve(JSON.parse(data));
|
|
231
|
+
} catch (e) {
|
|
232
|
+
reject(new Error('Invalid JSON response from Backlog'));
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}).on('error', reject);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Fetch issue detail from Backlog REST API
|
|
241
|
+
*/
|
|
242
|
+
function fetchBacklogIssue(domain, apiKey, issueKey) {
|
|
243
|
+
return backlogGet(`https://${domain}/api/v2/issues/${issueKey}?apiKey=${apiKey}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Fetch ALL comments of an issue from Backlog REST API (paginated, max 100/page).
|
|
248
|
+
*/
|
|
249
|
+
async function fetchBacklogComments(domain, apiKey, issueKey) {
|
|
250
|
+
const all = [];
|
|
251
|
+
let offset = 0;
|
|
252
|
+
try {
|
|
253
|
+
while (true) {
|
|
254
|
+
const batch = await backlogGet(
|
|
255
|
+
`https://${domain}/api/v2/issues/${issueKey}/comments?apiKey=${apiKey}&count=100&offset=${offset}&order=asc`
|
|
256
|
+
);
|
|
257
|
+
all.push(...batch);
|
|
258
|
+
if (batch.length < 100) break;
|
|
259
|
+
offset += 100;
|
|
260
|
+
}
|
|
261
|
+
} catch (_) {
|
|
262
|
+
// comments are optional โ don't fail the whole fetch
|
|
263
|
+
}
|
|
264
|
+
return all;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Transform Backlog API response + comments to internal context format
|
|
269
|
+
*/
|
|
270
|
+
function buildContextFromBacklog(issue, comments, issueKey, domain) {
|
|
271
|
+
const type = detectTaskType(issue);
|
|
272
|
+
|
|
273
|
+
// Format comments into readable text for AI context
|
|
274
|
+
const formattedComments = (comments || [])
|
|
275
|
+
.filter(c => c.content && c.content.trim())
|
|
276
|
+
.map(c => {
|
|
277
|
+
const author = c.createdUser?.name || 'Unknown';
|
|
278
|
+
const date = c.created ? new Date(c.created).toLocaleDateString('vi-VN') : '';
|
|
279
|
+
return `[${author} - ${date}]\n${c.content.trim()}`;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const allText = [issue.description || '', ...formattedComments.map(c => c)].join('\n');
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
taskId: issueKey,
|
|
286
|
+
taskType: type,
|
|
287
|
+
title: issue.summary || '',
|
|
288
|
+
description: issue.description || '',
|
|
289
|
+
status: issue.status?.name || 'Unknown',
|
|
290
|
+
priority: issue.priority?.name || 'Unknown',
|
|
291
|
+
assignee: issue.assignee?.name || 'Unassigned',
|
|
292
|
+
|
|
293
|
+
// Comments from all participants
|
|
294
|
+
comments: formattedComments,
|
|
295
|
+
commentCount: formattedComments.length,
|
|
296
|
+
|
|
297
|
+
// Parse checklist-style acceptance criteria from description
|
|
298
|
+
acceptanceCriteria: extractCriteria(issue.description || ''),
|
|
299
|
+
|
|
300
|
+
// Related files mentioned anywhere in ticket
|
|
301
|
+
context: {
|
|
302
|
+
files: extractFiles(allText),
|
|
303
|
+
mentionedServices: [],
|
|
304
|
+
relatedTickets: extractRelatedTickets(allText),
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
metadata: {
|
|
308
|
+
created: issue.created,
|
|
309
|
+
updated: issue.updated,
|
|
310
|
+
loadedFrom: 'backlog',
|
|
311
|
+
adapter: 'backlog',
|
|
312
|
+
sourceUrl: `https://${domain}/view/${issueKey}`,
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
318
|
+
// Jira context loader
|
|
319
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
320
|
+
|
|
321
|
+
async function loadFromJira(issueKey, options = {}) {
|
|
322
|
+
const creds = await loadCredentials();
|
|
323
|
+
const apiToken = process.env.JIRA_API_TOKEN || creds.JIRA_API_TOKEN;
|
|
324
|
+
const email = process.env.JIRA_EMAIL || creds.JIRA_EMAIL;
|
|
325
|
+
const domain = process.env.JIRA_DOMAIN || creds.JIRA_DOMAIN;
|
|
326
|
+
|
|
327
|
+
if (!apiToken || !email || !domain) {
|
|
328
|
+
console.log(chalk.yellow('โ Jira credentials not set.'));
|
|
329
|
+
console.log(chalk.gray('Run: aiflow init --adapter jira'));
|
|
330
|
+
console.log(chalk.gray('Or set environment variables: JIRA_API_TOKEN, JIRA_EMAIL, JIRA_DOMAIN'));
|
|
331
|
+
console.log(chalk.gray('\nFalling back to manual entry...\n'));
|
|
332
|
+
return await manualContext(issueKey);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(chalk.blue(`Fetching context from Jira: ${issueKey}...`));
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const issue = await fetchJiraIssue(domain, email, apiToken, issueKey);
|
|
339
|
+
const context = buildContextFromJira(issue, issueKey, domain);
|
|
340
|
+
await saveContext(context, options.save);
|
|
341
|
+
printContextSummary(context);
|
|
342
|
+
suggestNextStep(context);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
console.log(chalk.yellow(`โ Could not fetch from Jira: ${err.message}`));
|
|
345
|
+
console.log(chalk.gray('Falling back to manual entry...\n'));
|
|
346
|
+
await manualContext(issueKey);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function fetchJiraIssue(domain, email, apiToken, issueKey) {
|
|
351
|
+
return new Promise((resolve, reject) => {
|
|
352
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');
|
|
353
|
+
const url = `https://${domain}.atlassian.net/rest/api/3/issue/${issueKey}`;
|
|
354
|
+
const options = {
|
|
355
|
+
headers: {
|
|
356
|
+
'Authorization': `Basic ${auth}`,
|
|
357
|
+
'Accept': 'application/json'
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
https.get(url, options, (res) => {
|
|
362
|
+
let data = '';
|
|
363
|
+
res.on('data', chunk => data += chunk);
|
|
364
|
+
res.on('end', () => {
|
|
365
|
+
if (res.statusCode !== 200) {
|
|
366
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
resolve(JSON.parse(data));
|
|
371
|
+
} catch (e) {
|
|
372
|
+
reject(new Error('Invalid JSON response from Jira'));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}).on('error', reject);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildContextFromJira(issue, issueKey, domain) {
|
|
380
|
+
const fields = issue.fields || {};
|
|
381
|
+
const type = detectTaskTypeFromString(fields.issuetype?.name || '');
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
taskId: issueKey,
|
|
385
|
+
taskType: type,
|
|
386
|
+
title: fields.summary || '',
|
|
387
|
+
description: fields.description?.content?.map(block =>
|
|
388
|
+
block.content?.map(c => c.text).join('') || ''
|
|
389
|
+
).join('\n') || '',
|
|
390
|
+
status: fields.status?.name || 'Unknown',
|
|
391
|
+
priority: fields.priority?.name || 'Unknown',
|
|
392
|
+
assignee: fields.assignee?.displayName || 'Unassigned',
|
|
393
|
+
acceptanceCriteria: extractCriteria(fields.description?.toString() || ''),
|
|
394
|
+
context: {
|
|
395
|
+
files: [],
|
|
396
|
+
relatedTickets: []
|
|
397
|
+
},
|
|
398
|
+
metadata: {
|
|
399
|
+
created: fields.created,
|
|
400
|
+
updated: fields.updated,
|
|
401
|
+
loadedFrom: 'jira',
|
|
402
|
+
adapter: 'jira',
|
|
403
|
+
sourceUrl: `https://${domain}.atlassian.net/browse/${issueKey}`
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
409
|
+
// Manual context entry
|
|
410
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
411
|
+
|
|
412
|
+
async function manualContext(prefillId = '') {
|
|
413
|
+
console.log(chalk.cyan('\nManual Context Entry\n'));
|
|
414
|
+
|
|
415
|
+
const taskId = await input({
|
|
416
|
+
message: 'Ticket ID (e.g. PROJ-33):',
|
|
417
|
+
default: prefillId
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const title = await input({ message: 'Title:' });
|
|
421
|
+
const description = await input({ message: 'Description (brief):' });
|
|
422
|
+
const taskType = await select({
|
|
423
|
+
message: 'Task type:',
|
|
424
|
+
choices: [
|
|
425
|
+
{ name: '๐ Bug Fix', value: 'bug-fix' },
|
|
426
|
+
{ name: 'โจ Feature', value: 'feature' },
|
|
427
|
+
{ name: '๐ Investigation', value: 'investigation' },
|
|
428
|
+
{ name: 'โป๏ธ Refactor', value: 'refactor' },
|
|
429
|
+
{ name: '๐ Impact Analysis', value: 'impact-analysis' }
|
|
430
|
+
]
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const context = {
|
|
434
|
+
taskId,
|
|
435
|
+
taskType,
|
|
436
|
+
title,
|
|
437
|
+
description,
|
|
438
|
+
status: 'In Progress',
|
|
439
|
+
acceptanceCriteria: [],
|
|
440
|
+
context: { files: [], relatedTickets: [] },
|
|
441
|
+
metadata: {
|
|
442
|
+
created: new Date().toISOString(),
|
|
443
|
+
loadedFrom: 'manual',
|
|
444
|
+
adapter: 'manual'
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
await saveContext(context);
|
|
449
|
+
printContextSummary(context);
|
|
450
|
+
suggestNextStep(context);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
454
|
+
// Load from JSON file
|
|
455
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
456
|
+
|
|
457
|
+
async function loadFromFile(filePath, options = {}) {
|
|
458
|
+
if (!(await fs.pathExists(filePath))) {
|
|
459
|
+
console.log(chalk.red(`File not found: ${filePath}`));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const context = await fs.readJson(filePath);
|
|
463
|
+
await saveContext(context, options.save);
|
|
464
|
+
printContextSummary(context);
|
|
465
|
+
suggestNextStep(context);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
469
|
+
// Credentials loader (reads .aiflow/credentials.json as fallback)
|
|
470
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
471
|
+
|
|
472
|
+
async function loadCredentials() {
|
|
473
|
+
const credsFile = path.join(PROJECT_DIR, '.aiflow', 'credentials.json');
|
|
474
|
+
if (await fs.pathExists(credsFile)) {
|
|
475
|
+
return await fs.readJson(credsFile).catch(() => ({}));
|
|
476
|
+
}
|
|
477
|
+
return {};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
481
|
+
// Context persistence
|
|
482
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
483
|
+
|
|
484
|
+
async function saveContext(context, saveName) {
|
|
485
|
+
await fs.ensureDir(CONTEXT_DIR);
|
|
486
|
+
await fs.ensureDir(path.join(CONTEXT_DIR, 'history'));
|
|
487
|
+
|
|
488
|
+
// Save as current
|
|
489
|
+
await fs.writeJson(path.join(CONTEXT_DIR, 'current.json'), context, { spaces: 2 });
|
|
490
|
+
|
|
491
|
+
// Save to history
|
|
492
|
+
const histFile = path.join(CONTEXT_DIR, 'history', `${context.taskId || 'manual'}.json`);
|
|
493
|
+
await fs.writeJson(histFile, context, { spaces: 2 });
|
|
494
|
+
|
|
495
|
+
// Save named snapshot if requested
|
|
496
|
+
if (saveName) {
|
|
497
|
+
const namedFile = path.join(CONTEXT_DIR, 'history', `${saveName}.json`);
|
|
498
|
+
await fs.writeJson(namedFile, context, { spaces: 2 });
|
|
499
|
+
console.log(chalk.gray(` Context also saved as: ${saveName}`));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Update state
|
|
503
|
+
if (await fs.pathExists(STATE_FILE)) {
|
|
504
|
+
const state = await fs.readJson(STATE_FILE);
|
|
505
|
+
state.current_context = context.taskId;
|
|
506
|
+
state.current_context_type = context.taskType;
|
|
507
|
+
await fs.writeJson(STATE_FILE, state);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
512
|
+
// Helpers
|
|
513
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
514
|
+
|
|
515
|
+
function detectTaskType(issue) {
|
|
516
|
+
const name = issue.issueType?.name || issue.type?.name || '';
|
|
517
|
+
const summary = issue.summary || '';
|
|
518
|
+
return detectTaskTypeFromString(name || summary);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function detectTaskTypeFromString(text) {
|
|
522
|
+
const lower = text.toLowerCase();
|
|
523
|
+
if (['bug', 'defect', 'ใใฐ'].some(k => lower.includes(k))) return 'bug-fix';
|
|
524
|
+
if (['task', 'story', 'feature', 'ใฟในใฏ'].some(k => lower.includes(k))) return 'feature';
|
|
525
|
+
if (['research', 'investigation', '่ชฟๆป'].some(k => lower.includes(k))) return 'investigation';
|
|
526
|
+
return 'bug-fix'; // default for unknown
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function extractCriteria(text) {
|
|
530
|
+
const lines = text.split('\n');
|
|
531
|
+
return lines
|
|
532
|
+
.filter(l => l.trim().match(/^[-*โข]|^\d+\./))
|
|
533
|
+
.map(l => l.replace(/^[-*โข\d.]+\s*/, '').trim())
|
|
534
|
+
.filter(l => l.length > 0)
|
|
535
|
+
.slice(0, 10);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function extractFiles(text) {
|
|
539
|
+
const matches = text.match(/[\w/]+\.(php|js|ts|vue|jsx|tsx|css|html)/g) || [];
|
|
540
|
+
return [...new Set(matches)];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function extractRelatedTickets(text) {
|
|
544
|
+
const matches = text.match(/[A-Z][A-Z0-9_]+-\d+/g) || [];
|
|
545
|
+
return [...new Set(matches)];
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function printContextSummary(context) {
|
|
549
|
+
console.log(chalk.green('\nโ Context loaded\n'));
|
|
550
|
+
console.log(` ${chalk.white('Ticket:')} ${context.taskId}`);
|
|
551
|
+
console.log(` ${chalk.white('Type:')} ${context.taskType}`);
|
|
552
|
+
console.log(` ${chalk.white('Title:')} ${context.title.substring(0, 70)}`);
|
|
553
|
+
console.log(` ${chalk.white('Status:')} ${context.status}`);
|
|
554
|
+
console.log(` ${chalk.white('Assignee:')} ${context.assignee}`);
|
|
555
|
+
if (context.description) {
|
|
556
|
+
console.log(` ${chalk.white('Desc:')} ${context.description.substring(0, 80)}...`);
|
|
557
|
+
}
|
|
558
|
+
if (context.acceptanceCriteria?.length) {
|
|
559
|
+
console.log(` ${chalk.white('Criteria:')} ${context.acceptanceCriteria.length} item(s)`);
|
|
560
|
+
}
|
|
561
|
+
if (context.commentCount > 0) {
|
|
562
|
+
console.log(` ${chalk.white('Comments:')} ${context.commentCount} comment(s) loaded`);
|
|
563
|
+
}
|
|
564
|
+
console.log();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function suggestNextStep(context) {
|
|
568
|
+
console.log(chalk.cyan('Next step: open Claude โ AI will auto-start analyzing this ticket'));
|
|
569
|
+
console.log(chalk.gray(' $ claude'));
|
|
570
|
+
}
|