alphacouncil-agent 0.5.5 → 0.6.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-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +31 -0
- package/mcp/lib/screen.mjs +9 -1
- package/mcp/lib/sec.mjs +62 -8
- package/package.json +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "AlphaCouncil plugins for public-equity research.",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.6.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "alphacouncil-agent",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Multi-agent equity research: evidence packets, bull/bear debate, portfolio-manager decision.",
|
|
16
|
-
"version": "0.
|
|
16
|
+
"version": "0.6.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Zhao73"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alphacouncil-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Multi-agent public-equity research workflow for Claude Code: spawns analyst workers, gathers sourced JSON evidence packets, runs bull/bear debate, and produces a portfolio-manager Buy/Overweight/Hold/Underweight/Sell decision with a full report.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Zhao73"
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes per release. Dates are UTC.
|
|
4
4
|
|
|
5
|
+
## [0.6.0] — 2026-07-26
|
|
6
|
+
|
|
7
|
+
Minor rather than patch: **every screened number changes.** Anyone comparing a result from
|
|
8
|
+
0.5.x will see different figures, and the 0.5.x ones were wrong.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Quarterly and stub periods were counted as years.** A 10-K reports quarterly and
|
|
13
|
+
acquisition-stub periods alongside annual ones, and the reader keyed only on the end date.
|
|
14
|
+
Lumentum's fiscal 2015 became ten separate "years" — nine quarters and stubs plus the real
|
|
15
|
+
363-day period — so every multi-year rule averaged quarterly income against annual equity.
|
|
16
|
+
LITE's ten-year ROE read 2.26% and is **-1.64%**; MU's read 10.03% and is **12.83%**.
|
|
17
|
+
Periods must now span 300–400 days, wide enough for a 53-week fiscal year, with instant
|
|
18
|
+
balance-sheet facts exempt because they have no duration.
|
|
19
|
+
- **Tag aliases stopped at the first match instead of merging.** Revenue moved to the ASC 606
|
|
20
|
+
tag in 2022, so a company filing since 2013 appeared to have four years of history — which
|
|
21
|
+
then fired a "listed under ten years" exemption on a decade-old filer. Aliases now merge,
|
|
22
|
+
the preferred one winning a contested year and a later filing winning within one alias.
|
|
23
|
+
- **The dilution rule had never once been computed.** Share counts live under the XBRL
|
|
24
|
+
`shares` unit and the reader asked for `USD`, so it reported `skipped` for every company
|
|
25
|
+
ever screened — seven rules advertised, six ever computed, and a skip is indistinguishable
|
|
26
|
+
from a genuine data gap. AAPL now computes 7 of 7.
|
|
27
|
+
|
|
28
|
+
### Testing
|
|
29
|
+
|
|
30
|
+
- The existing dilution test passed throughout because its fixture also put share counts
|
|
31
|
+
under `USD` — the same mistake as the reader, so the two agreed with each other. The
|
|
32
|
+
fixture now uses the unit XBRL actually uses.
|
|
33
|
+
- A regression test pins that one company's filings cannot leak into another's result.
|
|
34
|
+
Verified live as well: LITE, then AAPL, then LITE again, identical to the digit.
|
|
35
|
+
|
|
5
36
|
## [0.5.5] — 2026-07-26
|
|
6
37
|
|
|
7
38
|
### Fixed
|
package/mcp/lib/screen.mjs
CHANGED
|
@@ -17,8 +17,16 @@ const pct = (x) => (x === null ? null : Number((x * 100).toFixed(2)));
|
|
|
17
17
|
const last = (series, n) => series.slice(-n);
|
|
18
18
|
const sum = (xs) => xs.reduce((a, b) => a + b, 0);
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* XBRL reports share counts under the `shares` unit, not `USD`. Requesting the default unit
|
|
22
|
+
* for a share concept returns nothing, so the dilution rule reported `skipped` for every
|
|
23
|
+
* company that has ever been screened -- seven rules advertised, six ever computed, and the
|
|
24
|
+
* skip looked exactly like a genuine data gap.
|
|
25
|
+
*/
|
|
26
|
+
const UNIT_BY_CONCEPT = { sharesOutstanding: "shares" };
|
|
27
|
+
|
|
20
28
|
function values(facts, key, asOf) {
|
|
21
|
-
const found = annualSeries(facts, CONCEPTS[key], { asOf });
|
|
29
|
+
const found = annualSeries(facts, CONCEPTS[key], { asOf, unit: UNIT_BY_CONCEPT[key] || "USD" });
|
|
22
30
|
return found ? found.series.map((e) => ({ end: e.end, filed: e.filed, val: e.val })) : [];
|
|
23
31
|
}
|
|
24
32
|
|
package/mcp/lib/sec.mjs
CHANGED
|
@@ -70,26 +70,80 @@ export async function fetchCompanyFacts(cik) {
|
|
|
70
70
|
* Tries several tags because the same economic quantity has different names depending on
|
|
71
71
|
* when and under which taxonomy a company filed.
|
|
72
72
|
*/
|
|
73
|
+
/**
|
|
74
|
+
* How long a reported period is, in days. Instant facts (balance-sheet items) have no start.
|
|
75
|
+
*/
|
|
76
|
+
const spanDays = (entry) =>
|
|
77
|
+
entry.start ? Math.round((Date.parse(entry.end) - Date.parse(entry.start)) / 86400000) : null;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Is this entry an annual figure?
|
|
81
|
+
*
|
|
82
|
+
* A 10-K carries quarterly and stub periods alongside the annual ones. Keying only on the
|
|
83
|
+
* end date treated each as its own year: Lumentum's 2015 produced ten "years" from one
|
|
84
|
+
* fiscal year -- nine quarters and stubs plus the real 363-day period. Every multi-year rule
|
|
85
|
+
* then averaged quarterly income against annual equity, silently.
|
|
86
|
+
*
|
|
87
|
+
* Fiscal years run 52 or 53 weeks, so the window has to be wider than 365 exactly.
|
|
88
|
+
*/
|
|
89
|
+
const ANNUAL_MIN_DAYS = 300;
|
|
90
|
+
const ANNUAL_MAX_DAYS = 400;
|
|
91
|
+
const isAnnual = (entry) => {
|
|
92
|
+
const days = spanDays(entry);
|
|
93
|
+
// Instant facts have no duration: shares outstanding, equity, total assets.
|
|
94
|
+
if (days === null) return true;
|
|
95
|
+
return days >= ANNUAL_MIN_DAYS && days <= ANNUAL_MAX_DAYS;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** The fiscal year a period belongs to, taken from its end date. */
|
|
99
|
+
const fiscalYear = (entry) => Number(String(entry.end).slice(0, 4));
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Annual history for a concept, merged across every alias.
|
|
103
|
+
*
|
|
104
|
+
* Merging matters as much as the annual filter. The function used to return at the first
|
|
105
|
+
* alias with any data, and revenue moved to RevenueFromContractWithCustomerExcludingAssessedTax
|
|
106
|
+
* when ASC 606 was adopted -- so a company reporting under the new tag since 2022 looked like
|
|
107
|
+
* it had four years of history, which then fired a "listed under ten years" exemption on a
|
|
108
|
+
* company that had been public for a decade.
|
|
109
|
+
*
|
|
110
|
+
* Aliases are ordered by preference, so an earlier alias wins where both cover a year.
|
|
111
|
+
*/
|
|
73
112
|
export function annualSeries(facts, tags, { asOf = null, unit = "USD" } = {}) {
|
|
74
113
|
const cutoff = asOf ? new Date(asOf).getTime() : null;
|
|
114
|
+
const byYear = new Map();
|
|
115
|
+
let usedTags = [];
|
|
116
|
+
|
|
75
117
|
for (const tag of tags) {
|
|
76
118
|
const entries = facts?.facts?.["us-gaap"]?.[tag]?.units?.[unit];
|
|
77
119
|
if (!Array.isArray(entries) || entries.length === 0) continue;
|
|
120
|
+
let contributed = false;
|
|
78
121
|
|
|
79
|
-
const byPeriod = new Map();
|
|
80
122
|
for (const entry of entries) {
|
|
81
123
|
if (entry.form !== "10-K" || !entry.end || !Number.isFinite(entry.val)) continue;
|
|
124
|
+
if (!isAnnual(entry)) continue;
|
|
82
125
|
// Look-ahead guard: a filing is only usable once it was actually filed.
|
|
83
126
|
if (cutoff && new Date(entry.filed).getTime() > cutoff) continue;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
127
|
+
|
|
128
|
+
const year = fiscalYear(entry);
|
|
129
|
+
const prior = byYear.get(year);
|
|
130
|
+
if (!prior) {
|
|
131
|
+
byYear.set(year, { ...entry, tag });
|
|
132
|
+
contributed = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// An earlier alias always wins the year; within one alias, the latest filing wins,
|
|
136
|
+
// because a restatement supersedes what it restates.
|
|
137
|
+
if (prior.tag === tag && new Date(entry.filed) > new Date(prior.filed)) {
|
|
138
|
+
byYear.set(year, { ...entry, tag });
|
|
139
|
+
}
|
|
87
140
|
}
|
|
88
|
-
if (
|
|
89
|
-
const series = [...byPeriod.values()].sort((a, b) => new Date(a.end) - new Date(b.end));
|
|
90
|
-
return { tag, unit, series };
|
|
141
|
+
if (contributed) usedTags.push(tag);
|
|
91
142
|
}
|
|
92
|
-
|
|
143
|
+
|
|
144
|
+
if (byYear.size === 0) return null;
|
|
145
|
+
const series = [...byYear.values()].sort((a, b) => new Date(a.end) - new Date(b.end));
|
|
146
|
+
return { tag: usedTags[0], tags: usedTags, unit, series };
|
|
93
147
|
}
|
|
94
148
|
|
|
95
149
|
/** Concept aliases, ordered by preference. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alphacouncil-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Multi-agent public-equity research workflow plugin for Codex & Claude Code: sourced evidence packets, bull/bear debate, and a portfolio-manager Buy/Overweight/Hold/Underweight/Sell decision.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|