alphacouncil-agent 1.0.3 → 1.0.13
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-insider_sec.md +19 -0
- package/.claude/commands/alpha.md +48 -19
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.grok/agents/alphacouncil-insider_sec.md +19 -0
- package/.grok/commands/alpha.md +48 -19
- package/.opencode/agent/alphacouncil-insider_sec.md +19 -0
- package/.opencode/command/alpha.md +48 -19
- package/CHANGELOG.md +307 -0
- package/CLAUDE.md +55 -13
- package/README.ja.md +42 -16
- package/README.md +70 -27
- package/README.zh-CN.md +53 -20
- package/SECURITY.md +11 -1
- package/commands/alpha.md +48 -19
- package/data/build-profile.v1.json +1 -1
- package/docs/INSTALL.md +9 -6
- package/docs/examples/final_report.SOX.zh.md +442 -0
- package/docs/report-contract.md +27 -3
- package/knowledge/ai-assisted-solo/experiments/runs/a.json +4 -4
- package/knowledge/ai-assisted-solo/experiments/runs/b.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/c.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/d13.json +16 -16
- package/knowledge/ai-assisted-solo/experiments/runs/d26.json +30 -30
- package/knowledge/ai-assisted-solo/experiments/runs/e-d13.json +19 -19
- package/knowledge/ai-assisted-solo/experiments/runs/e-d26.json +33 -33
- package/knowledge/ai-assisted-solo/experiments/runs/h_ai_reference.json +34 -34
- package/knowledge/ai-assisted-solo/experiments/simulation-input.json +84 -84
- package/knowledge/ai-assisted-solo/experiments/simulation-manifest.json +19 -19
- package/knowledge/solo-test/masters/master_ackman/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_aschenbrenner/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_asness/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_bogle/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_buffett/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_burry/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_cathie_wood/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_dalio/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_damodaran/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_druckenmiller/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_duan_yongping/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_fisher/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_forensic_short/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_graham/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_jhunjhunwala/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_klarman/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_li_lu/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_lynch/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_marks/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_munger/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_natenberg/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_pabrai/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_simons/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_sinclair/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_soros/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_taleb/manifest.json +8 -8
- package/knowledge/solo-test/masters/master_thorp/manifest.json +8 -8
- package/mcp/lib/constants.mjs +82 -4
- package/mcp/lib/council-options.mjs +8 -4
- package/mcp/lib/council-selection.mjs +79 -1
- package/mcp/lib/fundamentals.mjs +50 -10
- package/mcp/lib/gates.mjs +42 -0
- package/mcp/lib/grounding.mjs +27 -1
- package/mcp/lib/industry.mjs +9 -1
- package/mcp/lib/markdown.mjs +25 -1
- package/mcp/lib/orchestrator.mjs +187 -24
- package/mcp/lib/personas/engine.mjs +57 -12
- package/mcp/lib/personas-v3/runtime.mjs +28 -0
- package/mcp/lib/prompts.mjs +72 -3
- package/mcp/lib/rpc.mjs +93 -7
- package/mcp/lib/run-store.mjs +3 -0
- package/mcp/lib/screen.mjs +46 -46
- package/mcp/lib/sec.mjs +25 -10
- package/mcp/lib/voice-from-decision.mjs +13 -5
- package/mcp/lib/weights.mjs +9 -2
- package/mcp/lib/xbrl-series.mjs +31 -5
- package/package.json +2 -1
- package/personas/analysts/insider_sec.md +38 -0
- package/scripts/lib/persona-v3-solo-test-packs.mjs +14 -8
- package/scripts/review-persona-v3-solo-formulas.mjs +5 -3
- package/skills/alphacouncil-agent/SKILL.md +80 -19
package/mcp/lib/prompts.mjs
CHANGED
|
@@ -91,7 +91,67 @@ function fundOrIndexTaskInstruction(task, instrument, language) {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Output shaping for a depth tier.
|
|
96
|
+
*
|
|
97
|
+
* A tier is a timeout, and a timeout is not a plan. Sending the identical prompt with a shorter
|
|
98
|
+
* fuse does not buy a faster good packet -- it buys a packet the worker could not finish, which
|
|
99
|
+
* arrives as `degraded` or not at all. For an LLM call the wall clock is dominated by the tokens
|
|
100
|
+
* it generates, so the way to run faster without losing information is to ask for the same
|
|
101
|
+
* information in less prose.
|
|
102
|
+
*
|
|
103
|
+
* The line every tier holds: claims, numbers, scoped source IDs, required report sections and
|
|
104
|
+
* the decision itself are never what gets cut. What `fast` removes is restatement -- re-quoting
|
|
105
|
+
* evidence the packet can cite by ID, recapping an opponent before answering, methodology
|
|
106
|
+
* preambles. What `slow` buys back is room to write a derivation out in full.
|
|
107
|
+
*/
|
|
108
|
+
export function paceShapingInstruction(pace, role, chinese) {
|
|
109
|
+
const tier = String(pace || "normal").toLowerCase();
|
|
110
|
+
if (tier !== "fast" && tier !== "slow") return "";
|
|
111
|
+
const isReport = role === "portfolio_manager";
|
|
112
|
+
if (tier === "fast") {
|
|
113
|
+
if (chinese) {
|
|
114
|
+
return [
|
|
115
|
+
"本轮按 fast 档运行。要压缩的是叙述,不是内容:",
|
|
116
|
+
"- 每一条仍必须带具体数字和作用域来源 ID(`<task>:S<n>`)。少写一条论点比少写一个来源 ID 更可接受,凭记忆补数字则永远不可接受。",
|
|
117
|
+
"- 不要复述证据原文——引用来源 ID 即可。不要在回答前复述对手立场。不要写方法论开场或结尾总结。",
|
|
118
|
+
"- 每条论点压到一到两句,只保留「主张 + 数字 + 来源 + 它错在哪」。",
|
|
119
|
+
isReport
|
|
120
|
+
? "- 报告的每一个必需章节仍必须齐备且非空:简洁只能来自散文,不能来自删章节。价位阶梯与失效条件不得压缩成一句话,它们是这份报告唯一可执行的部分。"
|
|
121
|
+
: "- 论点条数上限 6 条,取信息量最高的 6 条,其余舍弃而不是缩写成半句。",
|
|
122
|
+
"- 如果时间不足以完成,宁可交一份明确标注缺口的短包,也不要交一份看起来完整但数字来自记忆的包。",
|
|
123
|
+
].join("\n");
|
|
124
|
+
}
|
|
125
|
+
return [
|
|
126
|
+
"This round runs at the fast tier. Compress the prose, not the content:",
|
|
127
|
+
"- Every item still carries its figure and its scoped source ID (`<task>:S<n>`). Dropping one argument is acceptable; dropping a source ID is not, and filling a number from memory never is.",
|
|
128
|
+
"- Do not re-quote evidence -- cite the source ID. Do not recap the opponent before answering. No methodology preamble and no closing summary.",
|
|
129
|
+
"- One or two sentences per item: claim, figure, source, and what would break it.",
|
|
130
|
+
isReport
|
|
131
|
+
? "- Every required report section must still be present and non-empty: terseness comes out of prose, never out of sections. Price levels and invalidation conditions may not be compressed to one line -- they are the only actionable part of the report."
|
|
132
|
+
: "- At most 6 arguments. Keep the six highest-information ones and drop the rest rather than shortening every one into a fragment.",
|
|
133
|
+
"- If there is not enough time to finish, hand in a short packet that names the gap rather than a complete-looking one built from memory.",
|
|
134
|
+
].join("\n");
|
|
135
|
+
}
|
|
136
|
+
if (chinese) {
|
|
137
|
+
return [
|
|
138
|
+
"本轮按 slow 档运行。这一档买到的是把推导写完整的空间:",
|
|
139
|
+
"- 多步推算要逐步写出,把每一步的口径、假设与它对结论的敏感度都写明,而不是只给结果。",
|
|
140
|
+
"- 逐条处置对手的论点:哪一条你接受、哪一条你反驳、反驳依据的是哪个来源 ID 与哪个数字。",
|
|
141
|
+
"- 明确写出你自己的证伪条件:什么读数会让你放弃这条论点。",
|
|
142
|
+
"- 更长不等于更好:每一段要么带来新的数字,要么带来新的反驳。重复已说过的内容仍然要删。",
|
|
143
|
+
].join("\n");
|
|
144
|
+
}
|
|
145
|
+
return [
|
|
146
|
+
"This round runs at the slow tier. What that budget buys is room to write the derivation out:",
|
|
147
|
+
"- Show multi-step arithmetic step by step, with the basis, the assumption and the sensitivity of the conclusion to each step, not just the result.",
|
|
148
|
+
"- Handle the opponent's arguments one by one: which you accept, which you refute, and on which source ID and figure the refutation rests.",
|
|
149
|
+
"- State your own falsification conditions explicitly: which reading would make you drop the argument.",
|
|
150
|
+
"- Longer is not better. Every paragraph must add either a new figure or a new refutation; repetition of what you already said still gets cut.",
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function taskPrompt(task, symbol, asOfDate, userPrompt = "", language = "auto", grounding = null, pace = null) {
|
|
95
155
|
const resolvedLanguage = resolveLanguage({ language, prompt: userPrompt });
|
|
96
156
|
const chinese = isChineseLanguage(resolvedLanguage);
|
|
97
157
|
const reg = registry();
|
|
@@ -112,7 +172,13 @@ export function taskPrompt(task, symbol, asOfDate, userPrompt = "", language = "
|
|
|
112
172
|
// which facts are already settled, or it reads them as the whole assignment.
|
|
113
173
|
const instrumentOverride = fundOrIndexTaskInstruction(task, grounding?.instrument, resolvedLanguage);
|
|
114
174
|
const grounded = groundingBlock(grounding, resolvedLanguage);
|
|
115
|
-
return [
|
|
175
|
+
return [
|
|
176
|
+
`${base}\n\n${chinese ? "任务:" : "Task: "}${task}\n${body}`,
|
|
177
|
+
instrumentOverride,
|
|
178
|
+
grounded,
|
|
179
|
+
// Last, so it is the final word on form after the role brief and the settled facts.
|
|
180
|
+
paceShapingInstruction(pace, task, chinese),
|
|
181
|
+
].filter(Boolean).join("\n\n");
|
|
116
182
|
}
|
|
117
183
|
|
|
118
184
|
export function debatePrompt(role, run, context = {}) {
|
|
@@ -190,6 +256,8 @@ export function debatePrompt(role, run, context = {}) {
|
|
|
190
256
|
].filter(Boolean).join("\n\n")
|
|
191
257
|
: "",
|
|
192
258
|
role === "portfolio_manager" && !quick ? outputModeInstruction(context.outputMode || "chat", language) : "",
|
|
259
|
+
// The tier's shaping is the final word on form. Quick has its own shaping already.
|
|
260
|
+
quick ? "" : paceShapingInstruction(run.council_pace, role, chinese),
|
|
193
261
|
`Evidence JSON: ${evidenceJson}`,
|
|
194
262
|
].filter(Boolean).join("\n\n");
|
|
195
263
|
}
|
|
@@ -269,8 +337,9 @@ export function masterVoicePrompt(masterId, run, frozenOpinion) {
|
|
|
269
337
|
].join(" "),
|
|
270
338
|
`\`position_intent\` MUST be one of: ${intentsForStance(frozenOpinion?.stance).join(" | ")}. Those are the only intents the frozen stance admits; anything else is rejected without changing run state.`,
|
|
271
339
|
"Return ONLY one valid JSON object, no Markdown fence. Schema: {\"master\":\"stable id\",\"acknowledged_stance\":\"constructive|cautious|opposed|out_of_scope\",\"position_intent\":\"one of the allowed intents above\",\"voice\":{\"what_i_see\":\"string\",\"how_my_method_reads_it\":\"string\",\"would_i_act\":\"string\",\"what_changes_my_mind\":\"string\",\"where_i_disagree\":\"string\"},\"key_findings\":[\"string\"],\"disagreements\":[\"string\"],\"what_would_change_my_mind\":[\"string\"],\"source_ids\":[\"task:S1\"],\"confidence\":\"high|medium|low\"}.",
|
|
340
|
+
paceShapingInstruction(run.council_pace, masterId, isChineseLanguage(language)),
|
|
272
341
|
`Bounded shared evidence JSON: ${evidence}`,
|
|
273
|
-
].join("\n\n");
|
|
342
|
+
].filter(Boolean).join("\n\n");
|
|
274
343
|
}
|
|
275
344
|
|
|
276
345
|
/** The master ids a run has selected, from an explicit list or a roster name. */
|
package/mcp/lib/rpc.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import readline from "node:readline";
|
|
4
|
-
import { COUNCIL_MODES, LIMITS, MASTER_STANCES, OUTPUT_MODES, QUICK_TASKS, SERVER_NAME, VERSION } from "./constants.mjs";
|
|
4
|
+
import { COUNCIL_MODES, COUNCIL_PACE_NAMES, DEFAULT_COUNCIL_PACE, LIMITS, MASTER_STANCES, OUTPUT_MODES, QUICK_TASKS, SERVER_NAME, VERSION, councilPaceProfile } from "./constants.mjs";
|
|
5
5
|
import { RpcCode, methodNotFound, invalidParams, toRpcError } from "./errors.mjs";
|
|
6
6
|
import { readJson, readJsonl, writeJson } from "./fsutil.mjs";
|
|
7
7
|
import { localized, resolveLanguage } from "./lang.mjs";
|
|
@@ -147,6 +147,36 @@ export function jsonContent(text, structuredContent = {}) {
|
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// Series history and per-fact lineage are the two unbounded parts of a grounding object: on one
|
|
151
|
+
// real run they were 2.33 MB of a 2.54 MB plan payload, which exceeded the host's tool-result
|
|
152
|
+
// limit and pushed the entire plan to a scratch file. They are already saved in evidence.json and
|
|
153
|
+
// baked into every seat prompt, so the plan response replaces them with a pointer and a count.
|
|
154
|
+
const BULK_GROUNDING_FIELDS = ["macro_series", "typed_fact_sources"];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The grounding a plan response carries: the same object minus its two unbounded fields.
|
|
158
|
+
* Shape, flags and gap lists are preserved so a host can still read what was established.
|
|
159
|
+
*/
|
|
160
|
+
export function compactGrounding(run) {
|
|
161
|
+
const g = run?.grounding;
|
|
162
|
+
if (!g || typeof g !== "object") return g;
|
|
163
|
+
const compact = { ...g };
|
|
164
|
+
const omitted = [];
|
|
165
|
+
for (const field of BULK_GROUNDING_FIELDS) {
|
|
166
|
+
if (compact[field] === undefined) continue;
|
|
167
|
+
const count = Array.isArray(compact[field]) ? compact[field].length : Object.keys(compact[field] || {}).length;
|
|
168
|
+
omitted.push(`${field} (${count} entries)`);
|
|
169
|
+
delete compact[field];
|
|
170
|
+
}
|
|
171
|
+
if (!omitted.length) return compact;
|
|
172
|
+
compact.omitted_from_this_response = {
|
|
173
|
+
fields: omitted,
|
|
174
|
+
reason: "kept out of the plan payload for size; every seat prompt already carries them",
|
|
175
|
+
saved_to: join(runPath(run.run_id), "evidence.json"),
|
|
176
|
+
};
|
|
177
|
+
return compact;
|
|
178
|
+
}
|
|
179
|
+
|
|
150
180
|
export function tool(name, description, inputSchema, annotations = {}) {
|
|
151
181
|
return { name, description, inputSchema, annotations };
|
|
152
182
|
}
|
|
@@ -197,12 +227,34 @@ function startValidation(args, entryTool) {
|
|
|
197
227
|
maximum_ms: LIMITS.QUICK_HARD_MAX_MS,
|
|
198
228
|
});
|
|
199
229
|
}
|
|
200
|
-
if (
|
|
201
|
-
throw invalidParams(`
|
|
202
|
-
reason: "
|
|
203
|
-
|
|
230
|
+
if (args.council_pace !== undefined && !COUNCIL_PACE_NAMES.includes(String(args.council_pace))) {
|
|
231
|
+
throw invalidParams(`council_pace must be one of ${COUNCIL_PACE_NAMES.join(", ")}.`, {
|
|
232
|
+
reason: "INVALID_COUNCIL_PACE",
|
|
233
|
+
allowed: COUNCIL_PACE_NAMES,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (mode === "quick" && args.council_pace !== undefined) {
|
|
237
|
+
throw invalidParams("council_pace applies to the full council only. Quick is a smaller contract, not a slower one.", {
|
|
238
|
+
reason: "QUICK_PACE_FORBIDDEN",
|
|
204
239
|
});
|
|
205
240
|
}
|
|
241
|
+
// A call is held to its own pace's budget, not to the outer bound of the schema: asking for
|
|
242
|
+
// an hour at the fast pace would buy idle time, because what bounds each worker is its
|
|
243
|
+
// per-stage cap and those are set by the pace.
|
|
244
|
+
if (mode === "full" && args.total_timeout_ms !== undefined) {
|
|
245
|
+
const profile = councilPaceProfile(args.council_pace);
|
|
246
|
+
if (args.total_timeout_ms > profile.total_ms) {
|
|
247
|
+
throw invalidParams(
|
|
248
|
+
`Full council total_timeout_ms cannot exceed ${profile.total_ms} at the ${profile.pace} pace. Raise council_pace to buy more time.`,
|
|
249
|
+
{
|
|
250
|
+
reason: "FULL_TOTAL_TIMEOUT_EXCEEDS_MAX",
|
|
251
|
+
council_pace: profile.pace,
|
|
252
|
+
maximum_ms: profile.total_ms,
|
|
253
|
+
paces: Object.fromEntries(COUNCIL_PACE_NAMES.map((name) => [name, councilPaceProfile(name).total_ms])),
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
206
258
|
if (mode === "quick" && args.synthesis === false) {
|
|
207
259
|
throw invalidParams("Quick council requires its one-round bull/bear and portfolio-manager synthesis.", {
|
|
208
260
|
reason: "QUICK_SYNTHESIS_REQUIRED",
|
|
@@ -263,12 +315,30 @@ function selectedRunArgs(args = {}, entryTool) {
|
|
|
263
315
|
prompt: typeof args.prompt === "string" ? args.prompt : "",
|
|
264
316
|
council_mode: args.council_mode || "full",
|
|
265
317
|
});
|
|
318
|
+
// The pace was approved at the gate, so the receipt is authoritative: an execution call may
|
|
319
|
+
// repeat it but not change it. Otherwise a user could approve fifteen minutes and get an
|
|
320
|
+
// hour, or the reverse, with nothing in the record showing the switch.
|
|
321
|
+
if (args.council_pace !== undefined
|
|
322
|
+
&& selection.council_pace
|
|
323
|
+
&& String(args.council_pace) !== String(selection.council_pace)) {
|
|
324
|
+
throw invalidParams(
|
|
325
|
+
`council_pace ${args.council_pace} does not match the pace confirmed at the selection gate (${selection.council_pace}).`,
|
|
326
|
+
{
|
|
327
|
+
reason: "COUNCIL_PACE_RECEIPT_MISMATCH",
|
|
328
|
+
confirmed_council_pace: selection.council_pace,
|
|
329
|
+
submitted_council_pace: String(args.council_pace),
|
|
330
|
+
remedy: "Send the confirmed pace or start a new selection to change it.",
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
}
|
|
266
334
|
return {
|
|
267
335
|
...args,
|
|
268
336
|
run_id: id,
|
|
269
337
|
entry_tool: entryTool,
|
|
270
338
|
masters: selection.selected_master_ids,
|
|
271
339
|
master_selection: selection,
|
|
340
|
+
// The gate's decision wins when the caller omitted it.
|
|
341
|
+
council_pace: args.council_pace ?? selection.council_pace ?? undefined,
|
|
272
342
|
existing_run: existing,
|
|
273
343
|
release_run_lock: releaseRunLock,
|
|
274
344
|
};
|
|
@@ -400,10 +470,16 @@ export function tools() {
|
|
|
400
470
|
timeout_ms: { type: "number", default: LIMITS.CODEX_TIMEOUT_MS },
|
|
401
471
|
synthesis: { type: "boolean", default: true, description: "Run bull, bear, and portfolio-manager synthesis after evidence collection." },
|
|
402
472
|
synthesis_timeout_ms: { type: "number", default: LIMITS.CODEX_TIMEOUT_MS },
|
|
473
|
+
council_pace: {
|
|
474
|
+
type: "string",
|
|
475
|
+
enum: COUNCIL_PACE_NAMES,
|
|
476
|
+
default: DEFAULT_COUNCIL_PACE,
|
|
477
|
+
description: "Full-council depth/time tier. fast = 15 minutes, normal = 30, slow = 60. The tier sets the total budget AND every per-stage cap together: slow gives each evidence seat 12 minutes instead of 6 and each debate round 6 minutes per side instead of 150 seconds, which is where the extra depth comes from. Raising total_timeout_ms alone buys idle time, not depth. Quick rejects this field.",
|
|
478
|
+
},
|
|
403
479
|
total_timeout_ms: {
|
|
404
480
|
type: "number",
|
|
405
481
|
maximum: LIMITS.FULL_HARD_MAX_MS,
|
|
406
|
-
description: "Hard queue-to-persistence wall-clock budget.
|
|
482
|
+
description: "Hard queue-to-persistence wall-clock budget. Cannot exceed the selected council_pace's total (fast 900000, normal 1800000, slow 3600000) or ten minutes for quick. Callers may lower the applicable ceiling, never raise it.",
|
|
407
483
|
},
|
|
408
484
|
output_mode: { type: "string", enum: OUTPUT_MODES, default: "public_equity", description: "Final synthesis target shape." },
|
|
409
485
|
selection_receipt: { type: "string", description: "One-run receipt returned by confirm_master_selection. Required for every council run and consumed exactly once." },
|
|
@@ -426,6 +502,11 @@ export function tools() {
|
|
|
426
502
|
items: { type: "string", enum: masterIds },
|
|
427
503
|
description: "Optional stable IDs inferred from masters explicitly named in the request. They are highlighted only; the user must still submit this run's selection.",
|
|
428
504
|
},
|
|
505
|
+
council_pace: {
|
|
506
|
+
type: "string",
|
|
507
|
+
enum: COUNCIL_PACE_NAMES,
|
|
508
|
+
description: "Prefill only, when the request already named a speed such as fast or slow. It highlights that tier in the returned pace_options and never confirms one. Full only.",
|
|
509
|
+
},
|
|
429
510
|
},
|
|
430
511
|
required: ["symbol"],
|
|
431
512
|
}, { readOnlyHint: false, destructiveHint: false, openWorldHint: false }),
|
|
@@ -438,6 +519,11 @@ export function tools() {
|
|
|
438
519
|
selected_master_ids: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string", enum: masterIds } },
|
|
439
520
|
select_all: { type: "boolean", const: true },
|
|
440
521
|
selection: { type: "string", minLength: 1, description: "Text fallback: stable numbers/ranges/IDs, or all." },
|
|
522
|
+
council_pace: {
|
|
523
|
+
type: "string",
|
|
524
|
+
enum: COUNCIL_PACE_NAMES,
|
|
525
|
+
description: "The depth tier the user picked from the pace_options this selection returned. Full only; omit to accept the default (normal, ~20 min expected, 30 min ceiling). Binds into the receipt, so an execution call may repeat it but never change it.",
|
|
526
|
+
},
|
|
441
527
|
},
|
|
442
528
|
required: ["selection_id", "catalog_hash", "display_ack"],
|
|
443
529
|
oneOf: [
|
|
@@ -793,7 +879,7 @@ export async function handleToolCall(id, params) {
|
|
|
793
879
|
+ `Network preflight: ${preflight.status}. `
|
|
794
880
|
+ `Established facts: ${run.grounding && !run.grounding.facts_unavailable ? "attached to every seat" : "UNAVAILABLE -- seats will run without filings or quotes, say so in the report"}.`,
|
|
795
881
|
{
|
|
796
|
-
run,
|
|
882
|
+
run: { ...run, grounding: compactGrounding(run) },
|
|
797
883
|
idempotent_replay: Boolean(runArgs.existing_run),
|
|
798
884
|
preflight,
|
|
799
885
|
...specs,
|
package/mcp/lib/run-store.mjs
CHANGED
|
@@ -70,6 +70,9 @@ export function statusSnapshot(run) {
|
|
|
70
70
|
language: run.language,
|
|
71
71
|
execution_mode: run.execution_mode,
|
|
72
72
|
council_mode: run.council_mode || "full",
|
|
73
|
+
// Which depth/time tier produced this run. Two runs of the same symbol at different paces
|
|
74
|
+
// are not the same analysis, so the pace belongs in the audit record beside the budget.
|
|
75
|
+
council_pace: run.council_pace || null,
|
|
73
76
|
debate_format: run.debate_format || "three_round_cross_exam",
|
|
74
77
|
visible_debate_contract: visibleDebate?.contract || null,
|
|
75
78
|
visible_debate_rounds_expected: visibleDebate?.rounds_expected || null,
|
package/mcp/lib/screen.mjs
CHANGED
|
@@ -17,6 +17,21 @@ 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
|
+
* Pair two annual series by fiscal period end, newest last, capped at n pairs.
|
|
22
|
+
*
|
|
23
|
+
* Pairing by array position instead put every year after a gap onto the wrong
|
|
24
|
+
* counterpart -- a filer that stopped tagging InterestExpense had this year's EBIT
|
|
25
|
+
* divided by a two-year-old interest figure, and an equity series missing one middle
|
|
26
|
+
* year shifted a decade of ROE by one year -- while the provenance block still showed
|
|
27
|
+
* a clean range. A year present on only one side is dropped, never matched to a
|
|
28
|
+
* neighbour; the pair count is what the rule reports as its coverage.
|
|
29
|
+
*/
|
|
30
|
+
const pairByEnd = (a, b, n) => {
|
|
31
|
+
const byEnd = new Map(b.map((row) => [row.end, row]));
|
|
32
|
+
return a.filter((row) => byEnd.has(row.end)).slice(-n).map((row) => [row, byEnd.get(row.end)]);
|
|
33
|
+
};
|
|
34
|
+
|
|
20
35
|
/**
|
|
21
36
|
* XBRL reports share counts under the `shares` unit, not `USD`. Requesting the default unit
|
|
22
37
|
* for a share concept returns nothing, so the dilution rule reported `skipped` for every
|
|
@@ -117,41 +132,36 @@ export function evaluateRules(facts, { asOf = null } = {}) {
|
|
|
117
132
|
};
|
|
118
133
|
|
|
119
134
|
add("roe_10y", { en: "10-year average ROE", zh: "10年平均ROE" }, () => {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const n = Math.min(ni.length, eq.length);
|
|
124
|
-
const roes = [];
|
|
125
|
-
for (let i = 0; i < n; i += 1) {
|
|
126
|
-
const e = eq[eq.length - n + i].val;
|
|
127
|
-
if (e > 0) roes.push(ni[ni.length - n + i].val / e);
|
|
128
|
-
}
|
|
135
|
+
const pairs = pairByEnd(netIncome, equity, 10);
|
|
136
|
+
if (pairs.length < 5) return null;
|
|
137
|
+
const roes = pairs.filter(([, e]) => e.val > 0).map(([n, e]) => n.val / e.val);
|
|
129
138
|
if (roes.length < 5) return null;
|
|
130
139
|
const avg = sum(roes) / roes.length;
|
|
131
140
|
return {
|
|
132
141
|
passed: avg >= 0.08, value: pct(avg), unit: "%", threshold: 8, direction: "min", years: roes.length,
|
|
133
|
-
...ruleProvenance([
|
|
142
|
+
...ruleProvenance([pairs.map(([n]) => n), pairs.map(([, e]) => e)]),
|
|
134
143
|
};
|
|
135
144
|
});
|
|
136
145
|
|
|
137
146
|
add("fcf_5y", { en: "5-year cumulative free cash flow", zh: "5年累计自由现金流" }, () => {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const n = Math.min(o.length, c.length);
|
|
142
|
-
const total = sum(o.slice(-n).map((x) => x.val)) - sum(c.slice(-n).map((x) => x.val));
|
|
147
|
+
const pairs = pairByEnd(ocf, capex, 5);
|
|
148
|
+
if (pairs.length < 3) return null;
|
|
149
|
+
const total = sum(pairs.map(([o, c]) => o.val - c.val));
|
|
143
150
|
// Raw dollars, not billions rounded to two places: rounding erased the entire figure
|
|
144
151
|
// for anything below ~$5m, which is most of the small-cap universe.
|
|
145
152
|
return {
|
|
146
|
-
passed: total >= 0, value: Math.round(total), unit: "USD", threshold: 0, direction: "min", years:
|
|
147
|
-
...ruleProvenance([
|
|
153
|
+
passed: total >= 0, value: Math.round(total), unit: "USD", threshold: 0, direction: "min", years: pairs.length,
|
|
154
|
+
...ruleProvenance([pairs.map(([o]) => o), pairs.map(([, c]) => c)]),
|
|
148
155
|
};
|
|
149
156
|
});
|
|
150
157
|
|
|
151
158
|
add("interest_cover", { en: "EBIT / interest cover", zh: "利息保障倍数" }, () => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
// The interest figure must belong to the same period as the latest EBIT. A filer that
|
|
160
|
+
// stops tagging InterestExpense gets a skip here, not a ratio built on old debt.
|
|
161
|
+
const pairs = pairByEnd(last(operatingIncome, 1), interest, 1);
|
|
162
|
+
if (!pairs.length) return null;
|
|
163
|
+
const [ebit, int] = pairs[0];
|
|
164
|
+
if (int.val === 0) return null;
|
|
155
165
|
const cover = ebit.val / Math.abs(int.val);
|
|
156
166
|
return {
|
|
157
167
|
passed: cover >= 2, value: Number(cover.toFixed(2)), unit: "x", threshold: 2, direction: "min",
|
|
@@ -160,48 +170,38 @@ export function evaluateRules(facts, { asOf = null } = {}) {
|
|
|
160
170
|
});
|
|
161
171
|
|
|
162
172
|
add("gross_margin", { en: "long-run gross margin", zh: "长期毛利率" }, () => {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const n = Math.min(gp.length, rev.length);
|
|
167
|
-
const margins = [];
|
|
168
|
-
for (let i = 0; i < n; i += 1) {
|
|
169
|
-
const r = rev[rev.length - n + i].val;
|
|
170
|
-
if (r > 0) margins.push(gp[gp.length - n + i].val / r);
|
|
171
|
-
}
|
|
173
|
+
const pairs = pairByEnd(grossProfit, revenue, 5);
|
|
174
|
+
if (pairs.length < 3) return null;
|
|
175
|
+
const margins = pairs.filter(([, r]) => r.val > 0).map(([g, r]) => g.val / r.val);
|
|
172
176
|
if (!margins.length) return null;
|
|
173
177
|
const avg = sum(margins) / margins.length;
|
|
174
178
|
return {
|
|
175
179
|
passed: avg >= 0.15, value: pct(avg), unit: "%", threshold: 15, direction: "min", years: margins.length,
|
|
176
|
-
...ruleProvenance([
|
|
180
|
+
...ruleProvenance([pairs.map(([g]) => g), pairs.map(([, r]) => r)]),
|
|
177
181
|
};
|
|
178
182
|
});
|
|
179
183
|
|
|
180
184
|
add("ocf_over_ni", { en: "5-year OCF / net income", zh: "5年经营现金流/净利" }, () => {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const n = Math.min(o.length, ni.length);
|
|
185
|
-
const totalNi = sum(ni.slice(-n).map((x) => x.val));
|
|
185
|
+
const pairs = pairByEnd(ocf, netIncome, 5);
|
|
186
|
+
if (pairs.length < 3) return null;
|
|
187
|
+
const totalNi = sum(pairs.map(([, n]) => n.val));
|
|
186
188
|
if (totalNi <= 0) return null;
|
|
187
|
-
const ratio = sum(
|
|
189
|
+
const ratio = sum(pairs.map(([o]) => o.val)) / totalNi;
|
|
188
190
|
return {
|
|
189
|
-
passed: ratio >= 0.7, value: Number(ratio.toFixed(2)), unit: "x", threshold: 0.7, direction: "min", years:
|
|
190
|
-
...ruleProvenance([
|
|
191
|
+
passed: ratio >= 0.7, value: Number(ratio.toFixed(2)), unit: "x", threshold: 0.7, direction: "min", years: pairs.length,
|
|
192
|
+
...ruleProvenance([pairs.map(([o]) => o), pairs.map(([, n]) => n)]),
|
|
191
193
|
};
|
|
192
194
|
});
|
|
193
195
|
|
|
194
196
|
add("net_margin", { en: "long-run net margin", zh: "长期净利率" }, () => {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const n = Math.min(ni.length, rev.length);
|
|
199
|
-
const totalRev = sum(rev.slice(-n).map((x) => x.val));
|
|
197
|
+
const pairs = pairByEnd(netIncome, revenue, 5);
|
|
198
|
+
if (pairs.length < 3) return null;
|
|
199
|
+
const totalRev = sum(pairs.map(([, r]) => r.val));
|
|
200
200
|
if (totalRev <= 0) return null;
|
|
201
|
-
const margin = sum(
|
|
201
|
+
const margin = sum(pairs.map(([n]) => n.val)) / totalRev;
|
|
202
202
|
return {
|
|
203
|
-
passed: margin >= 0.05, value: pct(margin), unit: "%", threshold: 5, direction: "min", years:
|
|
204
|
-
...ruleProvenance([
|
|
203
|
+
passed: margin >= 0.05, value: pct(margin), unit: "%", threshold: 5, direction: "min", years: pairs.length,
|
|
204
|
+
...ruleProvenance([pairs.map(([n]) => n), pairs.map(([, r]) => r)]),
|
|
205
205
|
};
|
|
206
206
|
});
|
|
207
207
|
|
package/mcp/lib/sec.mjs
CHANGED
|
@@ -97,20 +97,35 @@ export async function fetchFilingIndex(cik, { signal } = {}) {
|
|
|
97
97
|
return { cik: padded, name: data?.name || null, filings: rows };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* One filing document, fetched as text. Same throttle, User-Agent and 429 backoff as the JSON
|
|
102
|
+
* calls.
|
|
103
|
+
*
|
|
104
|
+
* The backoff was the part missing here. Every JSON endpoint retried a rate-limited response
|
|
105
|
+
* while this one threw on the first 429, so the documents that carry the actual disclosure --
|
|
106
|
+
* a Form 4's transaction table, an 8-K's item text -- were the easiest evidence in the run to
|
|
107
|
+
* lose. `www.sec.gov/Archives` is also throttled harder than `data.sec.gov`, which makes this
|
|
108
|
+
* the path most likely to be limited and was the least protected against it.
|
|
109
|
+
*/
|
|
101
110
|
export async function fetchFilingDocument(cik, accession, document, { signal } = {}) {
|
|
102
|
-
await throttle();
|
|
103
111
|
const stripped = String(cik).replace(/\D/gu, "").replace(/^0+/u, "");
|
|
104
112
|
const folder = String(accession).replace(/-/gu, "");
|
|
105
113
|
const url = `https://www.sec.gov/Archives/edgar/data/${stripped}/${folder}/${document}`;
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
const outcome = await withRateLimitRetry(async () => {
|
|
115
|
+
await throttle();
|
|
116
|
+
const abort = linkedAbort(LIMITS.QUOTE_FETCH_MS * 2, signal);
|
|
117
|
+
try {
|
|
118
|
+
const res = await fetch(url, { signal: abort.signal, headers: { "User-Agent": UA } });
|
|
119
|
+
if (isRateLimited(res.status)) {
|
|
120
|
+
return { rateLimited: true, error: new Error(`HTTP ${res.status} for ${url}`) };
|
|
121
|
+
}
|
|
122
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
|
|
123
|
+
return { rateLimited: false, value: { url, text: await res.text() } };
|
|
124
|
+
} finally {
|
|
125
|
+
abort.cleanup();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return outcome.value;
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
/** The full US listed universe: ~10k entries of {cik, ticker, title}. */
|
|
@@ -119,8 +119,11 @@ function copyFor(language) {
|
|
|
119
119
|
*
|
|
120
120
|
* `result` is the frozen `structured_decision.result`; `policy` is the seat's decision policy,
|
|
121
121
|
* which supplies the human-readable condition ids the result refers to by index.
|
|
122
|
+
* `readableIds` maps each anonymised id back to the declared one exactly; prefer it over the
|
|
123
|
+
* positional fallback, which assumes the executor's hits/misses/uncomputable split happens to
|
|
124
|
+
* come back in declaration order and silently swaps two condition names when it does not.
|
|
122
125
|
*/
|
|
123
|
-
export function voiceFromDecision({ result, policy, factPack, language = "English" } = {}) {
|
|
126
|
+
export function voiceFromDecision({ result, policy, factPack, readableIds, language = "English" } = {}) {
|
|
124
127
|
const copy = copyFor(language);
|
|
125
128
|
const facts = new Map((factPack?.facts || []).map((fact) => [fact.fact_id, fact]));
|
|
126
129
|
const stance = result?.common_projection?.stance || "out_of_scope";
|
|
@@ -136,7 +139,7 @@ export function voiceFromDecision({ result, policy, factPack, language = "Englis
|
|
|
136
139
|
const computed = (result?.computations?.trace || [])
|
|
137
140
|
.filter((step) => step.status === "computed" && finite(step.value))
|
|
138
141
|
.slice(0, MAX_ITEMS)
|
|
139
|
-
.map((step) => `${step.output_id} = ${Number(step.value.toFixed(4))}`)
|
|
142
|
+
.map((step) => `${readableIds?.get?.(step.output_id) || step.output_id} = ${Number(step.value.toFixed(4))}`)
|
|
140
143
|
.join("; ");
|
|
141
144
|
|
|
142
145
|
// The executor publishes the rules it evaluated already split into hits, misses and the ones
|
|
@@ -157,13 +160,18 @@ export function voiceFromDecision({ result, policy, factPack, language = "Englis
|
|
|
157
160
|
.map((rule) => rule.rule_id)
|
|
158
161
|
.filter(Boolean)
|
|
159
162
|
.map((id, index) => [id, index]));
|
|
163
|
+
const declaredName = (anon) => (anon && readableIds?.get?.(anon)) || null;
|
|
160
164
|
const ruleName = (rule) => {
|
|
161
165
|
const anon = rule.rule_id || rule.id;
|
|
162
|
-
|
|
163
|
-
return readable || anon || "rule";
|
|
166
|
+
return declaredName(anon) || readableRuleIds.get(anonOrder.get(anon)) || anon || "rule";
|
|
164
167
|
};
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
// Vetoes went unmapped, so a hashed veto reached the reader while the scoring conditions
|
|
170
|
+
// beside it were readable. That asymmetry is what made two seats speculate about their own
|
|
171
|
+
// hard veto instead of naming it.
|
|
172
|
+
const vetoes = (result?.vetoes_triggered || [])
|
|
173
|
+
.map((veto) => declaredName(veto.veto_id) || veto.veto_id)
|
|
174
|
+
.filter(Boolean);
|
|
167
175
|
|
|
168
176
|
const whatISee = [
|
|
169
177
|
readList ? copy.readFacts(readList) : copy.noFacts,
|
package/mcp/lib/weights.mjs
CHANGED
|
@@ -76,8 +76,15 @@ export function resolveSeatWeights(run, overrides = {}) {
|
|
|
76
76
|
|
|
77
77
|
const debateIds = reg.ids("debate").filter((id) => id !== "portfolio_manager");
|
|
78
78
|
const masterIds = (run.master_opinions || []).map((o) => o.master);
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
// Evidence seats belong here too. The contract says a failed verification down-weights the
|
|
80
|
+
// seat that made the claim, but the enumeration stopped at debate and master seats, so a
|
|
81
|
+
// `contradicted` verdict against an analyst was recorded and then silently ignored.
|
|
82
|
+
const evidenceIds = [
|
|
83
|
+
...(run.tasks || []),
|
|
84
|
+
...(run.verifier_verdicts || []).map((v) => v?.seat).filter(Boolean),
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const id of new Set([...debateIds, ...masterIds, ...evidenceIds])) {
|
|
81
88
|
const persona = reg.get(id);
|
|
82
89
|
if (!persona) continue;
|
|
83
90
|
const declared = baseWeight(persona);
|
package/mcp/lib/xbrl-series.mjs
CHANGED
|
@@ -29,9 +29,14 @@ export const MAX_ALIGNMENT_DRIFT_DAYS = 45;
|
|
|
29
29
|
/**
|
|
30
30
|
* Concept aliases, ordered by preference, on top of the ones sec.mjs already publishes.
|
|
31
31
|
*
|
|
32
|
-
* Aliases inside one entry are the same economic quantity under different taxonomies. They
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Aliases inside one entry are the same economic quantity under different taxonomies. They are
|
|
33
|
+
* never a fallback to a different concept: a missing depreciation tag stays missing.
|
|
34
|
+
*
|
|
35
|
+
* Total liabilities has one sanctioned reconstruction and it is guarded. Subtracting equity
|
|
36
|
+
* from the balance-sheet total is wrong when the two tags disagree about non-controlling
|
|
37
|
+
* interests -- the difference then lands inside "liabilities", silently. Done against the
|
|
38
|
+
* INCLUDING-NCI equity tag the identity is exact, so that tag, and only that tag, may stand in
|
|
39
|
+
* where a filer never tags `Liabilities` directly. See `totalLiabilitiesFor`.
|
|
35
40
|
*/
|
|
36
41
|
export const FUNDAMENTAL_CONCEPTS = Object.freeze({
|
|
37
42
|
revenue: { tags: CONCEPTS.revenue, unit: "USD" },
|
|
@@ -50,6 +55,8 @@ export const FUNDAMENTAL_CONCEPTS = Object.freeze({
|
|
|
50
55
|
},
|
|
51
56
|
current_assets: { tags: ["AssetsCurrent"], unit: "USD" },
|
|
52
57
|
total_liabilities: { tags: ["Liabilities"], unit: "USD" },
|
|
58
|
+
liabilities_and_equity: { tags: ["LiabilitiesAndStockholdersEquity"], unit: "USD" },
|
|
59
|
+
equity_including_nci: { tags: ["StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest"], unit: "USD" },
|
|
53
60
|
goodwill: { tags: ["Goodwill"], unit: "USD" },
|
|
54
61
|
intangibles: { tags: ["IntangibleAssetsNetExcludingGoodwill", "FiniteLivedIntangibleAssetsNet"], unit: "USD" },
|
|
55
62
|
cash: {
|
|
@@ -61,9 +68,28 @@ export const FUNDAMENTAL_CONCEPTS = Object.freeze({
|
|
|
61
68
|
},
|
|
62
69
|
// Debt buckets are non-overlapping by construction so they can be summed. `debt_combined`
|
|
63
70
|
// is the whole of it in one tag and wins outright when a filer publishes it.
|
|
71
|
+
//
|
|
72
|
+
// Within a bucket the tags are ALIASES, not addends: an earlier alias wins a year outright.
|
|
73
|
+
// The convertible aliases are therefore appended last, which makes them strictly additive --
|
|
74
|
+
// a filer that already resolves a bucket resolves it to exactly the same number as before,
|
|
75
|
+
// and only a filer whose sole balance-sheet debt line is a convertible note changes at all.
|
|
76
|
+
//
|
|
77
|
+
// That filer is not rare. ServiceNow's FY2020 and FY2025 balance sheets carry no straight
|
|
78
|
+
// debt tag whatsoever: the only debt instant either year is ConvertibleLongTermNotesPayable
|
|
79
|
+
// ($1.64bn and $1.491bn). Total debt therefore read as unknown, and because unknown debt is
|
|
80
|
+
// correctly refused rather than treated as zero, three separate facts died from one absent
|
|
81
|
+
// alias -- leverage, downside asset value, and incremental return on capital -- which in turn
|
|
82
|
+
// is why a seat requiring incremental return on capital abstained on a company whose debt is
|
|
83
|
+
// plainly stated on the face of its balance sheet.
|
|
64
84
|
debt_combined: { tags: ["DebtLongtermAndShorttermCombinedAmount"], unit: "USD" },
|
|
65
|
-
debt_long_term_noncurrent: {
|
|
66
|
-
|
|
85
|
+
debt_long_term_noncurrent: {
|
|
86
|
+
tags: ["LongTermDebtNoncurrent", "LongTermDebtAndCapitalLeaseObligations", "ConvertibleLongTermNotesPayable"],
|
|
87
|
+
unit: "USD",
|
|
88
|
+
},
|
|
89
|
+
debt_long_term_current: {
|
|
90
|
+
tags: ["LongTermDebtCurrent", "LongTermDebtAndCapitalLeaseObligationsCurrent", "ConvertibleNotesPayableCurrent"],
|
|
91
|
+
unit: "USD",
|
|
92
|
+
},
|
|
67
93
|
debt_short_term: { tags: ["ShortTermBorrowings", "OtherShortTermBorrowings", "CommercialPaper"], unit: "USD" },
|
|
68
94
|
tax_expense: { tags: ["IncomeTaxExpenseBenefit"], unit: "USD" },
|
|
69
95
|
pretax_income: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alphacouncil-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Multi-agent company, ETF and market-index research workflow for Codex & Claude Code: sourced evidence, method seats, bull/bear debate and portfolio-manager synthesis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
".opencode/command/",
|
|
128
128
|
".grok/commands/",
|
|
129
129
|
"docs/INSTALL.md",
|
|
130
|
+
"docs/examples/",
|
|
130
131
|
"docs/plans/",
|
|
131
132
|
"docs/releases/",
|
|
132
133
|
"docs/evaluation/",
|