alphacouncil-agent 0.5.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/.claude/agents/alphacouncil-bear_researcher.md +39 -0
- package/.claude/agents/alphacouncil-bull_researcher.md +40 -0
- package/.claude/agents/alphacouncil-earnings_deep_dive.md +49 -0
- package/.claude/agents/alphacouncil-forward_expectations.md +48 -0
- package/.claude/agents/alphacouncil-ib_event_analysis.md +40 -0
- package/.claude/agents/alphacouncil-insider_sec.md +39 -0
- package/.claude/agents/alphacouncil-macro_regime.md +31 -0
- package/.claude/agents/alphacouncil-market_data.md +37 -0
- package/.claude/agents/alphacouncil-market_narrative.md +45 -0
- package/.claude/agents/alphacouncil-news_industry_management.md +49 -0
- package/.claude/agents/alphacouncil-portfolio_manager.md +22 -0
- package/.claude/agents/alphacouncil-quant_factor.md +38 -0
- package/.claude/agents/alphacouncil-social_pulse.md +49 -0
- package/.claude/agents/alphacouncil-valuation_long_short.md +45 -0
- package/.claude-plugin/marketplace.json +30 -0
- package/.claude-plugin/plugin.json +28 -0
- package/.codex-plugin/plugin.json +32 -0
- package/.grok/agents/alphacouncil-bear_researcher.md +41 -0
- package/.grok/agents/alphacouncil-bull_researcher.md +42 -0
- package/.grok/agents/alphacouncil-earnings_deep_dive.md +50 -0
- package/.grok/agents/alphacouncil-forward_expectations.md +49 -0
- package/.grok/agents/alphacouncil-ib_event_analysis.md +41 -0
- package/.grok/agents/alphacouncil-insider_sec.md +40 -0
- package/.grok/agents/alphacouncil-macro_regime.md +32 -0
- package/.grok/agents/alphacouncil-market_data.md +38 -0
- package/.grok/agents/alphacouncil-market_narrative.md +46 -0
- package/.grok/agents/alphacouncil-news_industry_management.md +50 -0
- package/.grok/agents/alphacouncil-portfolio_manager.md +24 -0
- package/.grok/agents/alphacouncil-quant_factor.md +39 -0
- package/.grok/agents/alphacouncil-social_pulse.md +50 -0
- package/.grok/agents/alphacouncil-valuation_long_short.md +46 -0
- package/.grok/config.toml +4 -0
- package/.mcp.json +11 -0
- package/.opencode/agent/alphacouncil-bear_researcher.md +45 -0
- package/.opencode/agent/alphacouncil-bull_researcher.md +46 -0
- package/.opencode/agent/alphacouncil-earnings_deep_dive.md +54 -0
- package/.opencode/agent/alphacouncil-forward_expectations.md +53 -0
- package/.opencode/agent/alphacouncil-ib_event_analysis.md +45 -0
- package/.opencode/agent/alphacouncil-insider_sec.md +44 -0
- package/.opencode/agent/alphacouncil-macro_regime.md +36 -0
- package/.opencode/agent/alphacouncil-market_data.md +42 -0
- package/.opencode/agent/alphacouncil-market_narrative.md +50 -0
- package/.opencode/agent/alphacouncil-news_industry_management.md +54 -0
- package/.opencode/agent/alphacouncil-portfolio_manager.md +28 -0
- package/.opencode/agent/alphacouncil-quant_factor.md +43 -0
- package/.opencode/agent/alphacouncil-social_pulse.md +54 -0
- package/.opencode/agent/alphacouncil-valuation_long_short.md +50 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +112 -0
- package/CLAUDE.md +144 -0
- package/LICENSE +21 -0
- package/README.ja.md +282 -0
- package/README.md +361 -0
- package/README.zh-CN.md +282 -0
- package/data/industry-map.json +74 -0
- package/docs/attribution.md +99 -0
- package/docs/personas.md +59 -0
- package/docs/report-contract.md +110 -0
- package/mcp/lib/codex.mjs +152 -0
- package/mcp/lib/constants.mjs +139 -0
- package/mcp/lib/council-options.mjs +111 -0
- package/mcp/lib/errors.mjs +46 -0
- package/mcp/lib/feeds.mjs +140 -0
- package/mcp/lib/fsutil.mjs +69 -0
- package/mcp/lib/gates.mjs +208 -0
- package/mcp/lib/grounding.mjs +252 -0
- package/mcp/lib/headings.mjs +65 -0
- package/mcp/lib/industry.mjs +208 -0
- package/mcp/lib/lang.mjs +24 -0
- package/mcp/lib/macro.mjs +167 -0
- package/mcp/lib/markdown.mjs +337 -0
- package/mcp/lib/markets-kr-jp.mjs +180 -0
- package/mcp/lib/markets.mjs +261 -0
- package/mcp/lib/narrative.mjs +235 -0
- package/mcp/lib/options.mjs +216 -0
- package/mcp/lib/orchestrator.mjs +541 -0
- package/mcp/lib/output-modes.mjs +144 -0
- package/mcp/lib/packets.mjs +256 -0
- package/mcp/lib/personas/frontmatter.mjs +80 -0
- package/mcp/lib/personas/registry.mjs +215 -0
- package/mcp/lib/preflight.mjs +174 -0
- package/mcp/lib/prompts.mjs +151 -0
- package/mcp/lib/quotes.mjs +108 -0
- package/mcp/lib/rpc.mjs +737 -0
- package/mcp/lib/run-store.mjs +140 -0
- package/mcp/lib/screen.mjs +283 -0
- package/mcp/lib/sec.mjs +131 -0
- package/mcp/lib/social.mjs +261 -0
- package/mcp/lib/tables.mjs +150 -0
- package/mcp/lib/text.mjs +27 -0
- package/mcp/lib/weights.mjs +150 -0
- package/mcp/server.mjs +92 -0
- package/opencode.json +20 -0
- package/package.json +61 -0
- package/personas/_shared/_debate_base.md +46 -0
- package/personas/_shared/_evidence_base.md +56 -0
- package/personas/_shared/_master_base.md +77 -0
- package/personas/analysts/earnings_deep_dive.md +118 -0
- package/personas/analysts/forward_expectations.md +115 -0
- package/personas/analysts/ib_event_analysis.md +100 -0
- package/personas/analysts/insider_sec.md +98 -0
- package/personas/analysts/macro_regime.md +66 -0
- package/personas/analysts/market_data.md +94 -0
- package/personas/analysts/market_narrative.md +94 -0
- package/personas/analysts/news_industry_management.md +117 -0
- package/personas/analysts/quant_factor.md +97 -0
- package/personas/analysts/social_pulse.md +102 -0
- package/personas/analysts/valuation_long_short.md +109 -0
- package/personas/debate/bear_researcher.md +96 -0
- package/personas/debate/bull_researcher.md +98 -0
- package/personas/debate/portfolio_manager.md +64 -0
- package/personas/masters/masters-adversarial/burry.md +137 -0
- package/personas/masters/masters-adversarial/dalio.md +139 -0
- package/personas/masters/masters-adversarial/druckenmiller.md +144 -0
- package/personas/masters/masters-adversarial/short_seller.md +145 -0
- package/personas/masters/masters-adversarial/soros.md +137 -0
- package/personas/masters/masters-modern/aschenbrenner.md +147 -0
- package/personas/masters/masters-options/natenberg.md +168 -0
- package/personas/masters/masters-options/sinclair.md +166 -0
- package/personas/masters/masters-options/taleb.md +163 -0
- package/personas/masters/masters-quant/asness.md +142 -0
- package/personas/masters/masters-quant/simons.md +143 -0
- package/personas/masters/masters-quant/thorp.md +143 -0
- package/personas/masters/masters-value/buffett.md +123 -0
- package/personas/masters/masters-value/duan_yongping.md +142 -0
- package/personas/masters/masters-value/li_lu.md +146 -0
- package/personas/masters/masters-value/munger.md +117 -0
- package/personas/masters/masters-value-classic/fisher.md +140 -0
- package/personas/masters/masters-value-classic/graham.md +137 -0
- package/personas/masters/masters-value-classic/klarman.md +138 -0
- package/personas/masters/masters-value-classic/lynch.md +133 -0
- package/personas/masters/masters-value-classic/marks.md +145 -0
- package/personas/verifiers/rederivation.md +97 -0
- package/personas/verifiers/refuter.md +99 -0
- package/personas/verifiers/source_fidelity.md +91 -0
- package/skills/agent-skills-governance/SKILL.md +42 -0
- package/skills/alphacouncil-agent/SKILL.md +259 -0
- package/skills/investment-banking/SKILL.md +70 -0
- package/skills/public-equity-investing/SKILL.md +74 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { DEBATE_ROLES } from "./constants.mjs";
|
|
4
|
+
import { writeJson } from "./fsutil.mjs";
|
|
5
|
+
import { isChineseLanguage } from "./lang.mjs";
|
|
6
|
+
import { bullets, clip, fence } from "./text.mjs";
|
|
7
|
+
import { completenessStatus, validateFinalReport, verificationStatus, withCompletenessBanner, withDisclaimer, withVerificationBanner } from "./gates.mjs";
|
|
8
|
+
import { agentState, appendEvent, artifactPaths, runPath, taskState } from "./run-store.mjs";
|
|
9
|
+
|
|
10
|
+
export function renderPacketMarkdown(packet, index) {
|
|
11
|
+
const claims = packet.claims.length
|
|
12
|
+
? packet.claims.map((claim, claimIndex) => [
|
|
13
|
+
`${claimIndex + 1}. ${claim.claim || ""}`,
|
|
14
|
+
` - Evidence: ${claim.evidence || ""}`,
|
|
15
|
+
` - Confidence: ${claim.confidence || "low"}`,
|
|
16
|
+
` - Sources: ${(claim.source_ids || []).join(", ") || "None"}`,
|
|
17
|
+
].join("\n")).join("\n")
|
|
18
|
+
: "No claims.";
|
|
19
|
+
const sources = packet.sources.length
|
|
20
|
+
? packet.sources.map((source) => `- ${source.id || "S?"}: ${source.title || ""} (${source.published_at || "unknown"}) ${source.url || ""}`).join("\n")
|
|
21
|
+
: "- None";
|
|
22
|
+
return [
|
|
23
|
+
`## Evidence Subagent ${index + 1}: ${packet.task}`,
|
|
24
|
+
"",
|
|
25
|
+
`- Symbol: ${packet.symbol}`,
|
|
26
|
+
`- As-of: ${packet.as_of}`,
|
|
27
|
+
packet.thread_id ? `- Visible thread ID: ${packet.thread_id}` : "",
|
|
28
|
+
packet.thread_title ? `- Visible thread title: ${packet.thread_title}` : "",
|
|
29
|
+
`- Confidence: ${packet.confidence}`,
|
|
30
|
+
`- Information richness: ${packet.information_richness || "unrated"}`,
|
|
31
|
+
"",
|
|
32
|
+
"### Summary",
|
|
33
|
+
packet.summary || "",
|
|
34
|
+
"",
|
|
35
|
+
"### Claims",
|
|
36
|
+
claims,
|
|
37
|
+
"",
|
|
38
|
+
"### Metrics",
|
|
39
|
+
fence(packet.metrics || {}, "json"),
|
|
40
|
+
"",
|
|
41
|
+
"### Sources",
|
|
42
|
+
sources,
|
|
43
|
+
"",
|
|
44
|
+
"### Open Questions",
|
|
45
|
+
bullets(packet.open_questions),
|
|
46
|
+
"",
|
|
47
|
+
"### Raw Output / Prompt",
|
|
48
|
+
fence(packet.raw_text || "", "text"),
|
|
49
|
+
].join("\n");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function renderDebateRounds(rounds) {
|
|
53
|
+
if (!Array.isArray(rounds) || rounds.length === 0) return "";
|
|
54
|
+
const blocks = rounds.map((round) => [
|
|
55
|
+
`#### Round ${round.round}`,
|
|
56
|
+
"",
|
|
57
|
+
round.summary || "",
|
|
58
|
+
"",
|
|
59
|
+
"##### Long Thesis",
|
|
60
|
+
bullets(round.long_thesis),
|
|
61
|
+
"",
|
|
62
|
+
"##### Short Thesis",
|
|
63
|
+
bullets(round.short_thesis),
|
|
64
|
+
"",
|
|
65
|
+
"##### Questions",
|
|
66
|
+
bullets(round.questions),
|
|
67
|
+
"",
|
|
68
|
+
"##### Questions Answered",
|
|
69
|
+
bullets(round.questions_answered),
|
|
70
|
+
"",
|
|
71
|
+
"##### Raw Output / Prompt",
|
|
72
|
+
fence(round.raw_text || "", "text"),
|
|
73
|
+
].join("\n"));
|
|
74
|
+
return ["### Debate Rounds", "", ...blocks].join("\n\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function renderDebateMarkdown(agent) {
|
|
78
|
+
if (!agent) return "";
|
|
79
|
+
return [
|
|
80
|
+
`## ${agent.role}`,
|
|
81
|
+
"",
|
|
82
|
+
`- Rating: ${agent.rating}`,
|
|
83
|
+
`- Winner: ${agent.winner}`,
|
|
84
|
+
`- Verdict: ${agent.verdict}`,
|
|
85
|
+
`- Confidence: ${agent.confidence}`,
|
|
86
|
+
agent.thread_id ? `- Visible thread ID: ${agent.thread_id}` : "",
|
|
87
|
+
agent.thread_title ? `- Visible thread title: ${agent.thread_title}` : "",
|
|
88
|
+
"",
|
|
89
|
+
"### Summary",
|
|
90
|
+
agent.summary || "",
|
|
91
|
+
"",
|
|
92
|
+
"### Long Thesis",
|
|
93
|
+
bullets(agent.long_thesis),
|
|
94
|
+
"",
|
|
95
|
+
"### Short Thesis",
|
|
96
|
+
bullets(agent.short_thesis),
|
|
97
|
+
"",
|
|
98
|
+
"### Valuation Range",
|
|
99
|
+
agent.valuation_range || "None",
|
|
100
|
+
"",
|
|
101
|
+
"### Catalysts",
|
|
102
|
+
bullets(agent.catalysts),
|
|
103
|
+
"",
|
|
104
|
+
"### Risks",
|
|
105
|
+
bullets(agent.risks),
|
|
106
|
+
"",
|
|
107
|
+
"### Position",
|
|
108
|
+
agent.position || "None",
|
|
109
|
+
"",
|
|
110
|
+
"### Invalidation",
|
|
111
|
+
bullets(agent.invalidation),
|
|
112
|
+
"",
|
|
113
|
+
"### Source IDs",
|
|
114
|
+
bullets(agent.source_ids),
|
|
115
|
+
"",
|
|
116
|
+
"### Report Markdown",
|
|
117
|
+
agent.report_markdown || "",
|
|
118
|
+
"",
|
|
119
|
+
renderDebateRounds(agent.debate_rounds),
|
|
120
|
+
"### Raw Output / Prompt",
|
|
121
|
+
fence(agent.raw_text || "", "text"),
|
|
122
|
+
].filter(Boolean).join("\n");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function writeAllAgentsMarkdown(run, debate = {}) {
|
|
126
|
+
const dir = runPath(run.run_id);
|
|
127
|
+
const taskStatus = run.tasks.map((task) => {
|
|
128
|
+
const state = taskState(run, task);
|
|
129
|
+
return `- ${task}: ${state.status}${state.output ? ` (${state.output})` : ""}${state.error ? ` - ${state.error}` : ""}`;
|
|
130
|
+
}).join("\n");
|
|
131
|
+
const agentStatus = DEBATE_ROLES.map((role) => {
|
|
132
|
+
const state = agentState(run, role);
|
|
133
|
+
return `- ${role}: ${state.status}${state.output ? ` (${state.output})` : ""}${state.error ? ` - ${state.error}` : ""}`;
|
|
134
|
+
}).join("\n");
|
|
135
|
+
const sections = [
|
|
136
|
+
`# AlphaCouncil Agent Full Agent Trace: ${run.symbol}`,
|
|
137
|
+
"",
|
|
138
|
+
"## Run Metadata",
|
|
139
|
+
"",
|
|
140
|
+
`- Run ID: ${run.run_id}`,
|
|
141
|
+
`- Symbol: ${run.symbol}`,
|
|
142
|
+
`- As-of: ${run.as_of}`,
|
|
143
|
+
`- Language: ${run.language || "auto"}`,
|
|
144
|
+
`- Execution mode: ${run.execution_mode || "background_codex_exec"}`,
|
|
145
|
+
`- Visibility required: ${run.visibility_required || false}`,
|
|
146
|
+
`- Dry run: ${run.dry_run}`,
|
|
147
|
+
`- Status: ${run.status || "unknown"}`,
|
|
148
|
+
`- Phase: ${run.phase || "unknown"}`,
|
|
149
|
+
`- Started: ${run.started_at}`,
|
|
150
|
+
`- Updated: ${run.updated_at || ""}`,
|
|
151
|
+
`- Completed: ${run.completed_at || ""}`,
|
|
152
|
+
`- Tasks: ${run.tasks.join(", ")}`,
|
|
153
|
+
"",
|
|
154
|
+
"## Task Status",
|
|
155
|
+
"",
|
|
156
|
+
taskStatus || "- None",
|
|
157
|
+
"",
|
|
158
|
+
"## Analyst Status",
|
|
159
|
+
"",
|
|
160
|
+
agentStatus || "- None",
|
|
161
|
+
"",
|
|
162
|
+
"# Evidence Subagents",
|
|
163
|
+
"",
|
|
164
|
+
...run.packets.map(renderPacketMarkdown),
|
|
165
|
+
];
|
|
166
|
+
if (debate.bull || debate.bear || debate.manager) {
|
|
167
|
+
sections.push(
|
|
168
|
+
"",
|
|
169
|
+
"# Analyst Debate And Portfolio Manager",
|
|
170
|
+
"",
|
|
171
|
+
renderDebateMarkdown(debate.bull),
|
|
172
|
+
"",
|
|
173
|
+
renderDebateMarkdown(debate.bear),
|
|
174
|
+
"",
|
|
175
|
+
renderDebateMarkdown(debate.manager),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const path = join(dir, "all_agents.md");
|
|
179
|
+
writeFileSync(path, `${sections.filter(Boolean).join("\n\n")}\n`);
|
|
180
|
+
return path;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function writeAnalystMarkdownFiles(run, debate = {}) {
|
|
184
|
+
const dir = runPath(run.run_id);
|
|
185
|
+
for (const [index, packet] of (run.packets || []).entries()) {
|
|
186
|
+
writeFileSync(join(dir, `${packet.task}.md`), `${renderPacketMarkdown(packet, index)}\n`);
|
|
187
|
+
}
|
|
188
|
+
const debateFiles = [
|
|
189
|
+
["bull_researcher", debate.bull],
|
|
190
|
+
["bear_researcher", debate.bear],
|
|
191
|
+
["portfolio_manager", debate.manager],
|
|
192
|
+
];
|
|
193
|
+
for (const [role, packet] of debateFiles) {
|
|
194
|
+
if (packet) writeFileSync(join(dir, `${role}.md`), `${renderDebateMarkdown({ ...packet, role })}\n`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function writeReportQuality(run, markdown) {
|
|
199
|
+
const quality = validateFinalReport(markdown, run);
|
|
200
|
+
run.report_quality = quality;
|
|
201
|
+
writeJson(join(runPath(run.run_id), "report_quality.json"), quality);
|
|
202
|
+
return quality;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function finalReportMarkdown(run, manager) {
|
|
206
|
+
const gate = verificationStatus(run);
|
|
207
|
+
const completeness = completenessStatus(run);
|
|
208
|
+
return withDisclaimer(
|
|
209
|
+
withCompletenessBanner(
|
|
210
|
+
withVerificationBanner(manager.report_markdown || manager.summary, gate, run.language),
|
|
211
|
+
completeness,
|
|
212
|
+
run.language
|
|
213
|
+
),
|
|
214
|
+
run.language
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function writeArtifactIndex(run, debate = {}) {
|
|
219
|
+
const artifacts = artifactPaths(run);
|
|
220
|
+
const lines = [
|
|
221
|
+
`# ${run.symbol} AlphaCouncil Artifacts`,
|
|
222
|
+
"",
|
|
223
|
+
`- Run ID: ${run.run_id}`,
|
|
224
|
+
`- Status: ${run.status}`,
|
|
225
|
+
`- Report quality: ${run.report_quality?.status || "not_checked"}`,
|
|
226
|
+
"",
|
|
227
|
+
"## Main Files",
|
|
228
|
+
"",
|
|
229
|
+
`- Final report: ${artifacts.final_report_md}`,
|
|
230
|
+
`- Chat handoff summary: ${artifacts.user_response_md}`,
|
|
231
|
+
`- Full agent trace: ${artifacts.all_agents_md}`,
|
|
232
|
+
`- Evidence JSON: ${artifacts.evidence_json}`,
|
|
233
|
+
`- Decision JSON: ${artifacts.decision_json}`,
|
|
234
|
+
`- Source manifest: ${artifacts.source_manifest_json}`,
|
|
235
|
+
`- Status: ${artifacts.status_json}`,
|
|
236
|
+
`- Events: ${artifacts.events_jsonl}`,
|
|
237
|
+
`- Report quality: ${artifacts.report_quality_json}`,
|
|
238
|
+
"",
|
|
239
|
+
"## Analyst Markdown Files",
|
|
240
|
+
"",
|
|
241
|
+
...(run.tasks || []).map((task) => `- ${task}: ${artifacts.analyst_markdown[task]}`),
|
|
242
|
+
debate.bull ? `- bull_researcher: ${artifacts.analyst_markdown.bull_researcher}` : "",
|
|
243
|
+
debate.bear ? `- bear_researcher: ${artifacts.analyst_markdown.bear_researcher}` : "",
|
|
244
|
+
debate.manager ? `- portfolio_manager: ${artifacts.analyst_markdown.portfolio_manager}` : "",
|
|
245
|
+
].filter(Boolean);
|
|
246
|
+
writeFileSync(artifacts.artifact_index_md, `${lines.join("\n")}\n`);
|
|
247
|
+
return artifacts.artifact_index_md;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function packetSummary(run, task) {
|
|
251
|
+
return (run.packets || []).find((packet) => packet.task === task)?.summary || "";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function userResponseMarkdown(run, manager) {
|
|
255
|
+
const chinese = isChineseLanguage(run.language);
|
|
256
|
+
const artifacts = artifactPaths(run);
|
|
257
|
+
const invalidation = (manager.invalidation || []).slice(0, 3).map((item) => `- ${clip(item, 220)}`).join("\n") || "- None";
|
|
258
|
+
if (chinese) {
|
|
259
|
+
return [
|
|
260
|
+
`# ${run.symbol} AlphaCouncil 摘要`,
|
|
261
|
+
"",
|
|
262
|
+
"## 结论",
|
|
263
|
+
`- 评级: ${manager.rating || "Hold"}`,
|
|
264
|
+
`- 多空胜负: ${manager.winner || "unknown"}`,
|
|
265
|
+
`- 置信度: ${manager.confidence || "low"}`,
|
|
266
|
+
`- 判断: ${clip(manager.verdict || manager.summary, 620)}`,
|
|
267
|
+
"",
|
|
268
|
+
"## 关键内容",
|
|
269
|
+
`- 最新财报: ${clip(packetSummary(run, "earnings_deep_dive"), 420) || "未覆盖。"}`,
|
|
270
|
+
`- 前瞻门槛: ${clip(packetSummary(run, "forward_expectations"), 420) || "未覆盖。"}`,
|
|
271
|
+
`- 新闻/行业信号: ${clip([packetSummary(run, "news_industry_management"), packetSummary(run, "management_industry_voices")].filter(Boolean).join(" "), 520) || "未覆盖。"}`,
|
|
272
|
+
`- 估值/价位: ${clip(manager.valuation_range, 520) || "未覆盖。"}`,
|
|
273
|
+
`- 仓位: ${clip(manager.position, 420) || "未覆盖。"}`,
|
|
274
|
+
"",
|
|
275
|
+
"## 失效条件",
|
|
276
|
+
invalidation,
|
|
277
|
+
"",
|
|
278
|
+
"## 文件位置",
|
|
279
|
+
`- 完整报告: ${artifacts.final_report_md}`,
|
|
280
|
+
`- 分析师全文索引: ${artifacts.artifact_index_md}`,
|
|
281
|
+
`- 全部代理追踪: ${artifacts.all_agents_md}`,
|
|
282
|
+
`- 报告质量检查: ${artifacts.report_quality_json}`,
|
|
283
|
+
].join("\n");
|
|
284
|
+
}
|
|
285
|
+
return [
|
|
286
|
+
`# ${run.symbol} AlphaCouncil Summary`,
|
|
287
|
+
"",
|
|
288
|
+
"## Conclusion",
|
|
289
|
+
`- Rating: ${manager.rating || "Hold"}`,
|
|
290
|
+
`- Debate winner: ${manager.winner || "unknown"}`,
|
|
291
|
+
`- Confidence: ${manager.confidence || "low"}`,
|
|
292
|
+
`- Judgment: ${clip(manager.verdict || manager.summary, 620)}`,
|
|
293
|
+
"",
|
|
294
|
+
"## Key Content",
|
|
295
|
+
`- Latest earnings: ${clip(packetSummary(run, "earnings_deep_dive"), 420) || "Not covered."}`,
|
|
296
|
+
`- Forward thresholds: ${clip(packetSummary(run, "forward_expectations"), 420) || "Not covered."}`,
|
|
297
|
+
`- News / industry signal: ${clip([packetSummary(run, "news_industry_management"), packetSummary(run, "management_industry_voices")].filter(Boolean).join(" "), 520) || "Not covered."}`,
|
|
298
|
+
`- Valuation / price range: ${clip(manager.valuation_range, 520) || "Not covered."}`,
|
|
299
|
+
`- Position: ${clip(manager.position, 420) || "Not covered."}`,
|
|
300
|
+
"",
|
|
301
|
+
"## Invalidation",
|
|
302
|
+
invalidation,
|
|
303
|
+
"",
|
|
304
|
+
"## File Locations",
|
|
305
|
+
`- Full report: ${artifacts.final_report_md}`,
|
|
306
|
+
`- Analyst file index: ${artifacts.artifact_index_md}`,
|
|
307
|
+
`- Full agent trace: ${artifacts.all_agents_md}`,
|
|
308
|
+
`- Report quality check: ${artifacts.report_quality_json}`,
|
|
309
|
+
].join("\n");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function writeUserResponse(run, manager) {
|
|
313
|
+
const markdown = userResponseMarkdown(run, manager);
|
|
314
|
+
writeFileSync(artifactPaths(run).user_response_md, `${markdown}\n`);
|
|
315
|
+
return markdown;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function writeFinalArtifacts(run, debate = {}) {
|
|
319
|
+
const manager = debate.manager;
|
|
320
|
+
if (!manager) {
|
|
321
|
+
writeAnalystMarkdownFiles(run, debate);
|
|
322
|
+
writeArtifactIndex(run, debate);
|
|
323
|
+
return { artifacts: artifactPaths(run) };
|
|
324
|
+
}
|
|
325
|
+
const finalMarkdown = finalReportMarkdown(run, manager);
|
|
326
|
+
writeFileSync(artifactPaths(run).final_report_md, `${finalMarkdown}\n`);
|
|
327
|
+
const quality = writeReportQuality(run, finalMarkdown);
|
|
328
|
+
if (quality.status !== "passed" && completenessStatus(run).completeness === "complete" && verificationStatus(run).verification === "passed") {
|
|
329
|
+
run.status = "needs_revision";
|
|
330
|
+
run.phase = "needs_revision";
|
|
331
|
+
appendEvent(run, "needs_revision", { missing: quality.missing });
|
|
332
|
+
}
|
|
333
|
+
writeAnalystMarkdownFiles(run, debate);
|
|
334
|
+
const user_response_markdown = writeUserResponse(run, manager);
|
|
335
|
+
writeArtifactIndex(run, debate);
|
|
336
|
+
return { final_report_markdown: finalMarkdown, user_response_markdown, report_quality: quality, artifacts: artifactPaths(run) };
|
|
337
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { LIMITS } from "./constants.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Korea (DART) and Japan (EDINET) adapters.
|
|
5
|
+
*
|
|
6
|
+
* Both regulators publish full filings and both require a free key, so these activate the
|
|
7
|
+
* moment the key is present and stay inert otherwise. Written and error-handled against
|
|
8
|
+
* the real endpoints -- the failure shapes below were captured from live calls, not
|
|
9
|
+
* guessed, which is why an unregistered key produces a message naming the fix rather than
|
|
10
|
+
* a generic HTTP error.
|
|
11
|
+
*
|
|
12
|
+
* DART: {"status":"010","message":"등록되지 않은 인증키입니다."} (unregistered key)
|
|
13
|
+
* EDINET: {"StatusCode":401,"message":"Access denied due to invalid subscription key..."}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const timeout = () => LIMITS.QUOTE_FETCH_MS * 3;
|
|
17
|
+
|
|
18
|
+
async function getJson(url, headers = {}) {
|
|
19
|
+
const ctrl = new AbortController();
|
|
20
|
+
const timer = setTimeout(() => ctrl.abort(), timeout());
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(url, { signal: ctrl.signal, headers: { Accept: "application/json", ...headers } });
|
|
23
|
+
const text = await res.text();
|
|
24
|
+
if (text.trimStart().startsWith("<")) throw new Error(`expected JSON, got HTML (HTTP ${res.status})`);
|
|
25
|
+
return { status: res.status, body: JSON.parse(text) };
|
|
26
|
+
} finally {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---- Korea: DART ----------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/** DART status codes that mean "your key is the problem", not "no data". */
|
|
34
|
+
export const DART_KEY_ERRORS = {
|
|
35
|
+
"010": "the key is not registered",
|
|
36
|
+
"011": "the key is suspended",
|
|
37
|
+
"012": "access denied for this IP",
|
|
38
|
+
"020": "the daily request quota is exhausted",
|
|
39
|
+
"021": "the request rate limit was exceeded",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DART_REPORTS = { Q1: "11013", H1: "11012", Q3: "11014", FY: "11011" };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Annual or quarterly statements for one Korean issuer.
|
|
46
|
+
* @param {string} corpCode DART's 8-digit corp_code, not the exchange ticker.
|
|
47
|
+
*/
|
|
48
|
+
export async function fetchDartFinancials({ corpCode, year, report = "FY", key = process.env.ALPHACOUNCIL_DART_KEY }) {
|
|
49
|
+
if (!key) {
|
|
50
|
+
return {
|
|
51
|
+
available: false,
|
|
52
|
+
reason: "ALPHACOUNCIL_DART_KEY is not set. Register free at opendart.fss.or.kr and export the key.",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const reprt = DART_REPORTS[report] || DART_REPORTS.FY;
|
|
56
|
+
const url = `https://opendart.fss.or.kr/api/fnlttSinglAcnt.json?crtfc_key=${encodeURIComponent(key)}`
|
|
57
|
+
+ `&corp_code=${encodeURIComponent(corpCode)}&bsns_year=${encodeURIComponent(year)}&reprt_code=${reprt}`;
|
|
58
|
+
|
|
59
|
+
const { body } = await getJson(url);
|
|
60
|
+
if (body.status && body.status !== "000") {
|
|
61
|
+
const keyProblem = DART_KEY_ERRORS[body.status];
|
|
62
|
+
return {
|
|
63
|
+
available: false,
|
|
64
|
+
dart_status: body.status,
|
|
65
|
+
reason: keyProblem
|
|
66
|
+
? `DART rejected the key: ${keyProblem}. (${body.message})`
|
|
67
|
+
// 013 is "no data", which is a legitimate answer and not a configuration fault.
|
|
68
|
+
: `DART returned status ${body.status}: ${body.message}`,
|
|
69
|
+
is_key_problem: Boolean(keyProblem),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const rows = Array.isArray(body.list) ? body.list : [];
|
|
74
|
+
const num = (v) => {
|
|
75
|
+
const n = Number(String(v ?? "").replace(/,/g, ""));
|
|
76
|
+
return Number.isFinite(n) ? n : null;
|
|
77
|
+
};
|
|
78
|
+
// Consolidated statements (CFS) where present, otherwise separate (OFS).
|
|
79
|
+
const pick = (accountName, sj) => {
|
|
80
|
+
const match = rows.find((r) => r.account_nm === accountName && r.sj_div === sj && r.fs_div === "CFS")
|
|
81
|
+
|| rows.find((r) => r.account_nm === accountName && r.sj_div === sj);
|
|
82
|
+
return match ? num(match.thstrm_amount) : null;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
available: true,
|
|
87
|
+
market: "KR",
|
|
88
|
+
source: "DART fnlttSinglAcnt",
|
|
89
|
+
corp_code: corpCode,
|
|
90
|
+
period: { year, report },
|
|
91
|
+
currency: "KRW",
|
|
92
|
+
unit: "as filed",
|
|
93
|
+
revenue: pick("매출액", "IS") ?? pick("수익(매출액)", "IS"),
|
|
94
|
+
operating_income: pick("영업이익", "IS"),
|
|
95
|
+
net_income: pick("당기순이익", "IS"),
|
|
96
|
+
assets: pick("자산총계", "BS"),
|
|
97
|
+
equity: pick("자본총계", "BS"),
|
|
98
|
+
liabilities: pick("부채총계", "BS"),
|
|
99
|
+
raw_row_count: rows.length,
|
|
100
|
+
note: "DART reports Korean account names; consolidated (CFS) is preferred over separate (OFS) where both exist.",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Resolve an exchange ticker such as 000660.KS to DART's corp_code. */
|
|
105
|
+
export async function resolveDartCorpCode(ticker, key = process.env.ALPHACOUNCIL_DART_KEY) {
|
|
106
|
+
if (!key) return { available: false, reason: "ALPHACOUNCIL_DART_KEY is not set." };
|
|
107
|
+
const stockCode = String(ticker).toUpperCase().replace(/\.(KS|KQ)$/, "");
|
|
108
|
+
// corpCode.xml ships as a zip, which needs an unzip step this repo has no dependency
|
|
109
|
+
// for. Rather than pull one in, callers pass corp_code directly; this documents why.
|
|
110
|
+
return {
|
|
111
|
+
available: false,
|
|
112
|
+
reason: `DART maps tickers via corpCode.xml, distributed as a ZIP. Look up ${stockCode} at `
|
|
113
|
+
+ "opendart.fss.or.kr and pass corp_code directly. (Samsung Electronics is 00126380, SK hynix 00164779.)",
|
|
114
|
+
stock_code: stockCode,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---- Japan: EDINET --------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Recent filings for a Japanese issuer.
|
|
122
|
+
*
|
|
123
|
+
* EDINET indexes by submission date rather than by company, so this scans a window of
|
|
124
|
+
* days and filters. That is the API's shape, not a workaround.
|
|
125
|
+
*/
|
|
126
|
+
export async function fetchEdinetFilings({ secCode, days = 90, key = process.env.ALPHACOUNCIL_EDINET_KEY }) {
|
|
127
|
+
if (!key) {
|
|
128
|
+
return {
|
|
129
|
+
available: false,
|
|
130
|
+
reason: "ALPHACOUNCIL_EDINET_KEY is not set. Register free at the EDINET portal and export the subscription key.",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const code = String(secCode).toUpperCase().replace(/\.T$/, "");
|
|
134
|
+
const wanted = code.length === 4 ? `${code}0` : code; // EDINET secCode is 5 digits
|
|
135
|
+
|
|
136
|
+
const found = [];
|
|
137
|
+
const today = new Date();
|
|
138
|
+
// One request per day is the API's granularity; cap the window so a caller cannot
|
|
139
|
+
// accidentally issue hundreds of requests.
|
|
140
|
+
const window = Math.max(1, Math.min(120, days));
|
|
141
|
+
for (let i = 0; i < window; i += 1) {
|
|
142
|
+
const day = new Date(today.getTime() - i * 86400000).toISOString().slice(0, 10);
|
|
143
|
+
let result;
|
|
144
|
+
try {
|
|
145
|
+
result = await getJson(`https://api.edinet-fsa.go.jp/api/v2/documents.json?date=${day}&type=2&Subscription-Key=${encodeURIComponent(key)}`);
|
|
146
|
+
} catch {
|
|
147
|
+
continue; // one bad day must not abort the scan
|
|
148
|
+
}
|
|
149
|
+
if (result.body?.StatusCode === 401) {
|
|
150
|
+
return { available: false, reason: `EDINET rejected the key: ${result.body.message}`, is_key_problem: true };
|
|
151
|
+
}
|
|
152
|
+
for (const doc of result.body?.results || []) {
|
|
153
|
+
if (doc.secCode === wanted) {
|
|
154
|
+
found.push({
|
|
155
|
+
doc_id: doc.docID,
|
|
156
|
+
submitted: doc.submitDateTime,
|
|
157
|
+
filer: doc.filerName,
|
|
158
|
+
description: doc.docDescription,
|
|
159
|
+
period_end: doc.periodEnd,
|
|
160
|
+
// The document itself is a ZIP of XBRL; fetching it needs an unzip step this
|
|
161
|
+
// repo deliberately has no dependency for.
|
|
162
|
+
document_url: `https://api.edinet-fsa.go.jp/api/v2/documents/${doc.docID}?type=2`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (found.length >= 8) break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
available: true,
|
|
171
|
+
market: "JP",
|
|
172
|
+
source: "EDINET v2 documents index",
|
|
173
|
+
sec_code: wanted,
|
|
174
|
+
filings: found,
|
|
175
|
+
note: found.length
|
|
176
|
+
? "Filing metadata only. The documents are XBRL inside a ZIP, so extracting figures needs WebFetch on the "
|
|
177
|
+
+ "document URL or the company's own IR page -- and those figures must be cited as read from a document."
|
|
178
|
+
: `No filings found for ${wanted} in the last ${window} days. Widen the window or check the 5-digit secCode.`,
|
|
179
|
+
};
|
|
180
|
+
}
|