@v587d/capital-generation 2.6.4 → 2.7.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/CHANGELOG.md +89 -0
- package/README.md +82 -27
- package/THIRD_PARTY_NOTICES.md +43 -0
- package/capital-config/client.js +18 -2
- package/capital-config/client.src.cjs +161 -20
- package/capital-config/index.js +10 -0
- package/capital-config/package.json +1 -1
- package/capital-watchlist/client.js +48 -7
- package/capital-watchlist/client.src.cjs +383 -66
- package/capital-watchlist/holding-rules.js +64 -0
- package/capital-watchlist/index.js +117 -2
- package/capital-watchlist/package.json +2 -1
- package/chart-ui/package.json +1 -1
- package/lib/agents/selected-skills-provider.d.ts +26 -0
- package/lib/agents/selected-skills-provider.js +99 -0
- package/lib/index.d.ts +37 -0
- package/lib/index.js +45 -11
- package/lib/time/tools.d.ts +6 -0
- package/lib/time/tools.js +6 -1
- package/lib/watchlist/tools.d.ts +52 -0
- package/lib/watchlist/tools.js +146 -0
- package/lib/web-retriever/local-fetch.d.ts +1 -1
- package/lib/web-retriever/local-fetch.js +1 -1
- package/lib/web-retriever/wind-client.d.ts +1 -1
- package/lib/web-retriever/wind-client.js +1 -1
- package/package.json +13 -3
- package/preset/capital-generation/agent.patch.yml +7 -0
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +45 -0
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +29 -0
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +29 -0
- package/selected-skills/README.md +44 -0
- package/selected-skills/catalog.js +181 -0
- package/selected-skills/china-stock-research-skills/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/selected-skills/china-stock-research-skills/.github/ISSUE_TEMPLATE/skill_request.md +28 -0
- package/selected-skills/china-stock-research-skills/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- package/selected-skills/china-stock-research-skills/CHANGELOG.md +59 -0
- package/selected-skills/china-stock-research-skills/CONTRIBUTING.md +121 -0
- package/selected-skills/china-stock-research-skills/GITHUB_PUBLISHING.md +259 -0
- package/selected-skills/china-stock-research-skills/INSTALL.md +151 -0
- package/selected-skills/china-stock-research-skills/LICENSE +21 -0
- package/selected-skills/china-stock-research-skills/PUBLISH_CHECKLIST.md +165 -0
- package/selected-skills/china-stock-research-skills/README.en.md +209 -0
- package/selected-skills/china-stock-research-skills/README.md +209 -0
- package/selected-skills/china-stock-research-skills/RELEASE_ASSETS.md +222 -0
- package/selected-skills/china-stock-research-skills/ROADMAP.md +45 -0
- package/selected-skills/china-stock-research-skills/SKILLS.md +85 -0
- package/selected-skills/china-stock-research-skills/UPSTREAM.md +11 -0
- package/selected-skills/china-stock-research-skills/examples/full-report-flow.md +20 -0
- package/selected-skills/china-stock-research-skills/examples/module-select-cases.md +26 -0
- package/selected-skills/china-stock-research-skills/examples/quickstart-prompts.md +25 -0
- package/selected-skills/china-stock-research-skills/skills/business-decomposition-order-quality/SKILL.md +55 -0
- package/selected-skills/china-stock-research-skills/skills/business-decomposition-order-quality/references/idc-order-ledger-guide.md +34 -0
- package/selected-skills/china-stock-research-skills/skills/business-decomposition-order-quality/references/order-quality-checklist.md +16 -0
- package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/SKILL.md +75 -0
- package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/references/final-output-contract.md +62 -0
- package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/references/routing-matrix.md +34 -0
- package/selected-skills/china-stock-research-skills/skills/china-stock-research-orchestrator/references/sector-overlay-escalation-rules.md +38 -0
- package/selected-skills/china-stock-research-skills/skills/financial-health/SKILL.md +54 -0
- package/selected-skills/china-stock-research-skills/skills/financial-health/references/idc-balance-sheet-stress-guide.md +31 -0
- package/selected-skills/china-stock-research-skills/skills/financial-health/references/red-flag-checklist.md +14 -0
- package/selected-skills/china-stock-research-skills/skills/industry-competition-moat/SKILL.md +54 -0
- package/selected-skills/china-stock-research-skills/skills/industry-competition-moat/references/idc-value-chain-and-peers.md +42 -0
- package/selected-skills/china-stock-research-skills/skills/industry-competition-moat/references/moat-grid.md +13 -0
- package/selected-skills/china-stock-research-skills/skills/risk-warning-catalysts/SKILL.md +54 -0
- package/selected-skills/china-stock-research-skills/skills/risk-warning-catalysts/references/idc-monitoring-indicators.md +33 -0
- package/selected-skills/china-stock-research-skills/skills/risk-warning-catalysts/references/monitoring-dashboard.md +25 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/analysis-pattern-selector.md +24 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/asset-heavy-ramp-overlay.md +28 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/business-model-transition-overlay.md +28 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/citation-standard.md +48 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/company-disclosure-map.md +37 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/consumer-channel-brand-overlay.md +29 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/cyclical-price-driven-overlay.md +29 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/export-manufacturing-supply-chain-overlay.md +29 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/financial-kpi-dictionary.md +43 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/idc-heavy-asset-transition-overlay.md +73 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/language-output-standard.md +36 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/project-order-business-overlay.md +28 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/regulated-healthcare-policy-overlay.md +29 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/research-output-template.md +50 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/sector-adaptation-checklist.md +42 -0
- package/selected-skills/china-stock-research-skills/skills/shared/references/source-registry.md +49 -0
- package/selected-skills/china-stock-research-skills/skills/shared/templates/catalyst-calendar.md +5 -0
- package/selected-skills/china-stock-research-skills/skills/shared/templates/company-fact-sheet.md +25 -0
- package/selected-skills/china-stock-research-skills/skills/shared/templates/evidence-table.md +5 -0
- package/selected-skills/china-stock-research-skills/skills/shared/templates/idc-project-ledger.md +5 -0
- package/selected-skills/china-stock-research-skills/skills/shared/templates/valuation-scenario-table.md +7 -0
- package/selected-skills/china-stock-research-skills/skills/strategy-business-transition/SKILL.md +55 -0
- package/selected-skills/china-stock-research-skills/skills/strategy-business-transition/references/idc-transition-markers.md +27 -0
- package/selected-skills/china-stock-research-skills/skills/strategy-business-transition/references/transition-scorecard.md +13 -0
- package/selected-skills/china-stock-research-skills/skills/valuation-investment-strategy/SKILL.md +55 -0
- package/selected-skills/china-stock-research-skills/skills/valuation-investment-strategy/references/idc-valuation-overlays.md +31 -0
- package/selected-skills/china-stock-research-skills/skills/valuation-investment-strategy/references/method-selection.md +14 -0
- package/selected-skills/investment-skills/.github/workflows/validate.yml +25 -0
- package/selected-skills/investment-skills/.tbd/config.yml +107 -0
- package/selected-skills/investment-skills/LICENSE +21 -0
- package/selected-skills/investment-skills/Makefile +29 -0
- package/selected-skills/investment-skills/README.md +253 -0
- package/selected-skills/investment-skills/UPSTREAM.md +12 -0
- package/selected-skills/investment-skills/docs/development.md +37 -0
- package/selected-skills/investment-skills/docs/roadmap.md +25 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/SKILL.md +192 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/agents/openai.yaml +4 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/00-source-key.md +82 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/01-decision-posture.md +242 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/02-business-economics.md +188 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/03-management-governance.md +213 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/04-financial-reality.md +192 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/05-valuation.md +166 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/06-capital-allocation.md +238 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/07-risk-monitoring.md +189 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/references/08-specialized-overlays.md +309 -0
- package/selected-skills/investment-skills/skills/buffett-investment-framework/scripts/framework.py +470 -0
package/selected-skills/investment-skills/skills/buffett-investment-framework/scripts/framework.py
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Route, inspect, and validate the standalone Buffett framework skill."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
SKILL_ROOT = Path(__file__).resolve().parent.parent
|
|
14
|
+
SKILL_PATH = SKILL_ROOT / "SKILL.md"
|
|
15
|
+
MINIMUM_CARD_LOAD = 5
|
|
16
|
+
MAXIMUM_CARD_LOAD = 12
|
|
17
|
+
EXPECTED_COUNTS = {
|
|
18
|
+
"D": 9,
|
|
19
|
+
"B": 7,
|
|
20
|
+
"M": 8,
|
|
21
|
+
"F": 7,
|
|
22
|
+
"V": 6,
|
|
23
|
+
"C": 9,
|
|
24
|
+
"R": 7,
|
|
25
|
+
"S": 12,
|
|
26
|
+
}
|
|
27
|
+
REFERENCE_FILES = {
|
|
28
|
+
"D": "references/01-decision-posture.md",
|
|
29
|
+
"B": "references/02-business-economics.md",
|
|
30
|
+
"M": "references/03-management-governance.md",
|
|
31
|
+
"F": "references/04-financial-reality.md",
|
|
32
|
+
"V": "references/05-valuation.md",
|
|
33
|
+
"C": "references/06-capital-allocation.md",
|
|
34
|
+
"R": "references/07-risk-monitoring.md",
|
|
35
|
+
"S": "references/08-specialized-overlays.md",
|
|
36
|
+
}
|
|
37
|
+
WORKFLOW_CARDS = {
|
|
38
|
+
"financial-analysis": ("F01", "F02", "F05", "F06", "F07"),
|
|
39
|
+
"memo-review": ("D01", "D03", "B02", "V01", "V05", "V06", "R01", "R07"),
|
|
40
|
+
"thesis-evaluation": (
|
|
41
|
+
"D02",
|
|
42
|
+
"D03",
|
|
43
|
+
"B01",
|
|
44
|
+
"B06",
|
|
45
|
+
"V01",
|
|
46
|
+
"V04",
|
|
47
|
+
"R01",
|
|
48
|
+
"R07",
|
|
49
|
+
),
|
|
50
|
+
}
|
|
51
|
+
TOPIC_CARDS = {
|
|
52
|
+
"business-model": ("B01",),
|
|
53
|
+
"moat": ("B02",),
|
|
54
|
+
"management": ("M01", "M02", "M03"),
|
|
55
|
+
"governance": ("M04", "M05"),
|
|
56
|
+
"integration-controls": ("M05", "M06"),
|
|
57
|
+
"succession": ("M07", "M08"),
|
|
58
|
+
"valuation": ("V01", "V03", "V05", "V06"),
|
|
59
|
+
"capital-allocation": ("C01", "C03", "C04"),
|
|
60
|
+
"dilution": ("C01", "C07"),
|
|
61
|
+
"acquisition": ("C08", "C09"),
|
|
62
|
+
"leverage": ("F07", "R01", "R02", "R03"),
|
|
63
|
+
"insurance": ("S01", "S02", "S03"),
|
|
64
|
+
"insurance-tail-risk": ("S04", "S05"),
|
|
65
|
+
"insurance-moat": ("S06",),
|
|
66
|
+
"banking": ("S07",),
|
|
67
|
+
"consumer": ("S08",),
|
|
68
|
+
"infrastructure": ("S09",),
|
|
69
|
+
"technology": ("S10",),
|
|
70
|
+
"energy": ("S11",),
|
|
71
|
+
"special-instruments": ("S12",),
|
|
72
|
+
}
|
|
73
|
+
REQUIRED_CARD_FIELDS = (
|
|
74
|
+
"Decision question",
|
|
75
|
+
"Guidance",
|
|
76
|
+
"Use when",
|
|
77
|
+
"Analytical actions",
|
|
78
|
+
"Output",
|
|
79
|
+
"Limits",
|
|
80
|
+
"Source basis",
|
|
81
|
+
"Corroboration",
|
|
82
|
+
)
|
|
83
|
+
SOURCE_KEY_FILE = "references/00-source-key.md"
|
|
84
|
+
RETRIEVAL_SECTION_HEADING = "## Where to Retrieve Each Source"
|
|
85
|
+
RETRIEVAL_KEY_FORMS = (
|
|
86
|
+
"BH<year>",
|
|
87
|
+
"BPL<year>",
|
|
88
|
+
"OM1996",
|
|
89
|
+
"SI1984",
|
|
90
|
+
"WB50-2015",
|
|
91
|
+
"CM50-2015",
|
|
92
|
+
"FL2025",
|
|
93
|
+
"GC<year>",
|
|
94
|
+
"SC<year>",
|
|
95
|
+
"ACL2022",
|
|
96
|
+
"ABM2010",
|
|
97
|
+
"IL2001",
|
|
98
|
+
"EWB",
|
|
99
|
+
"LOW",
|
|
100
|
+
)
|
|
101
|
+
MINIMUM_ANALYTICAL_ACTIONS = 1
|
|
102
|
+
MAXIMUM_ANALYTICAL_ACTIONS = 3
|
|
103
|
+
MINIMUM_SOURCE_CITATIONS = 1
|
|
104
|
+
MAXIMUM_SOURCE_CITATIONS = 3
|
|
105
|
+
MINIMUM_CORROBORATION = 2
|
|
106
|
+
MAXIMUM_CORROBORATION = 4
|
|
107
|
+
MAXIMUM_DESCRIPTION_LENGTH = 1024
|
|
108
|
+
CORROBORATION_KEY: re.Pattern[str] = re.compile(
|
|
109
|
+
r"^(?:BH\d{4}|BPL\d{4}|OM1996|SI1984|WB50-2015|CM50-2015|FL2025|GC\d{4}"
|
|
110
|
+
r"|SC\d{4}|ACL2022|ABM2010|IL2001"
|
|
111
|
+
r"|EWB §(?:Prologue|Epilogue|Afterword|[IVX]+\.[A-J])"
|
|
112
|
+
r"|LOW (?:ch\.\d{1,2}|Intro|Afterword))"
|
|
113
|
+
r"(?: \([^();]+\))?$"
|
|
114
|
+
)
|
|
115
|
+
CARD_HEADING: re.Pattern[str] = re.compile(
|
|
116
|
+
r"^### ([DBMFVCRS]\d{2}) — (.+)$", re.MULTILINE
|
|
117
|
+
)
|
|
118
|
+
MARKDOWN_LINK: re.Pattern[str] = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
|
|
119
|
+
SOURCE_ENTRY: re.Pattern[str] = re.compile(r"^ - \*\*", re.MULTILINE)
|
|
120
|
+
SOURCE_ROLE: re.Pattern[str] = re.compile(
|
|
121
|
+
r"\*(defines|supports|implements|illustrates|qualifies|specializes)\*:"
|
|
122
|
+
)
|
|
123
|
+
CARD_FIELD: re.Pattern[str] = re.compile(
|
|
124
|
+
r"^- \*\*(?P<label>[^:]+):\*\*\s*(?P<value>.*?)(?=^- \*\*|^<a id=|^<!--|\Z)",
|
|
125
|
+
re.MULTILINE | re.DOTALL,
|
|
126
|
+
)
|
|
127
|
+
UNPUBLISHABLE_TEXT = {
|
|
128
|
+
"draft or preview label": re.compile(r"\b(?:draft|preview)\b", re.IGNORECASE),
|
|
129
|
+
"opaque record identifier": re.compile(
|
|
130
|
+
"".join((r"\brec", r"ord:[a-z0-9][a-z0-9:-]+"))
|
|
131
|
+
),
|
|
132
|
+
"absolute workspace path": re.compile(
|
|
133
|
+
r"(?:^|[\s(`'\"])/(?:Users|home|private|workspace)/", re.MULTILINE
|
|
134
|
+
),
|
|
135
|
+
"parent-directory traversal": re.compile(r"(?:^|[(`'\"])(?:\.\./)+"),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def ordered_unique(values: Iterable[str]) -> list[str]:
|
|
140
|
+
"""Return values in first-seen order without duplicates."""
|
|
141
|
+
return list(dict.fromkeys(values))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def card_locations() -> dict[str, str]:
|
|
145
|
+
"""Map every card ID to its stable reference location."""
|
|
146
|
+
locations: dict[str, str] = {}
|
|
147
|
+
for prefix, relative_path in REFERENCE_FILES.items():
|
|
148
|
+
path = SKILL_ROOT / relative_path
|
|
149
|
+
if not path.is_file():
|
|
150
|
+
raise FileNotFoundError(f"missing reference file: {relative_path}")
|
|
151
|
+
body = path.read_text(encoding="utf-8")
|
|
152
|
+
for match in CARD_HEADING.finditer(body):
|
|
153
|
+
card_id = match.group(1)
|
|
154
|
+
if not card_id.startswith(prefix):
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"{card_id} is in the wrong module file: {relative_path}"
|
|
157
|
+
)
|
|
158
|
+
if card_id in locations:
|
|
159
|
+
raise ValueError(f"duplicate card ID: {card_id}")
|
|
160
|
+
locations[card_id] = f"{relative_path}#{card_id.lower()}"
|
|
161
|
+
return locations
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def validate_local_links(path: Path, errors: list[str]) -> None:
|
|
165
|
+
"""Append failures for local Markdown links that do not resolve."""
|
|
166
|
+
body = path.read_text(encoding="utf-8")
|
|
167
|
+
for match in MARKDOWN_LINK.finditer(body):
|
|
168
|
+
target = match.group(1)
|
|
169
|
+
if target.startswith(("http://", "https://", "mailto:")):
|
|
170
|
+
continue
|
|
171
|
+
local_target, _separator, fragment = target.partition("#")
|
|
172
|
+
resolved = path if not local_target else path.parent / local_target
|
|
173
|
+
if not resolved.exists():
|
|
174
|
+
errors.append(
|
|
175
|
+
f"broken local link in {path.relative_to(SKILL_ROOT)}: {target}"
|
|
176
|
+
)
|
|
177
|
+
continue
|
|
178
|
+
if fragment:
|
|
179
|
+
target_body = resolved.read_text(encoding="utf-8")
|
|
180
|
+
if f'id="{fragment}"' not in target_body:
|
|
181
|
+
errors.append(
|
|
182
|
+
f"broken local anchor in {path.relative_to(SKILL_ROOT)}: {target}"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def validate() -> int:
|
|
187
|
+
"""Validate package structure, public card contracts, and routing."""
|
|
188
|
+
errors: list[str] = []
|
|
189
|
+
locations = card_locations()
|
|
190
|
+
expected_ids = {
|
|
191
|
+
f"{prefix}{number:02d}"
|
|
192
|
+
for prefix, count in EXPECTED_COUNTS.items()
|
|
193
|
+
for number in range(1, count + 1)
|
|
194
|
+
}
|
|
195
|
+
actual_ids = set(locations)
|
|
196
|
+
if actual_ids != expected_ids:
|
|
197
|
+
errors.append(
|
|
198
|
+
f"card namespace mismatch: missing={sorted(expected_ids - actual_ids)}, extra={sorted(actual_ids - expected_ids)}"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
for prefix, expected_count in EXPECTED_COUNTS.items():
|
|
202
|
+
actual_count = sum(card_id.startswith(prefix) for card_id in actual_ids)
|
|
203
|
+
if actual_count != expected_count:
|
|
204
|
+
errors.append(
|
|
205
|
+
f"{prefix} module has {actual_count} cards; expected {expected_count}"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
for relative_path in REFERENCE_FILES.values():
|
|
209
|
+
path = SKILL_ROOT / relative_path
|
|
210
|
+
body = path.read_text(encoding="utf-8")
|
|
211
|
+
second_level_headings = re.findall(r"^## (.+)$", body, re.MULTILINE)
|
|
212
|
+
if second_level_headings != ["Card Index", "Complete Cards"]:
|
|
213
|
+
errors.append(
|
|
214
|
+
f"unexpected section structure in {relative_path}: {second_level_headings}"
|
|
215
|
+
)
|
|
216
|
+
headings = list(CARD_HEADING.finditer(body))
|
|
217
|
+
for index, match in enumerate(headings):
|
|
218
|
+
end = (
|
|
219
|
+
headings[index + 1].start() if index + 1 < len(headings) else len(body)
|
|
220
|
+
)
|
|
221
|
+
card_body = body[match.end() : end]
|
|
222
|
+
field_matches = list(CARD_FIELD.finditer(card_body))
|
|
223
|
+
field_labels = tuple(item.group("label") for item in field_matches)
|
|
224
|
+
if field_labels != REQUIRED_CARD_FIELDS:
|
|
225
|
+
errors.append(
|
|
226
|
+
f"{match.group(1)} has invalid field contract: {field_labels}"
|
|
227
|
+
)
|
|
228
|
+
field_values = {
|
|
229
|
+
item.group("label"): item.group("value").strip()
|
|
230
|
+
for item in field_matches
|
|
231
|
+
}
|
|
232
|
+
action_count = len(
|
|
233
|
+
[
|
|
234
|
+
action
|
|
235
|
+
for action in field_values.get("Analytical actions", "").split(";")
|
|
236
|
+
if action.strip()
|
|
237
|
+
]
|
|
238
|
+
)
|
|
239
|
+
if (
|
|
240
|
+
not MINIMUM_ANALYTICAL_ACTIONS
|
|
241
|
+
<= action_count
|
|
242
|
+
<= MAXIMUM_ANALYTICAL_ACTIONS
|
|
243
|
+
):
|
|
244
|
+
errors.append(
|
|
245
|
+
f"{match.group(1)} has {action_count} analytical actions; expected {MINIMUM_ANALYTICAL_ACTIONS} to {MAXIMUM_ANALYTICAL_ACTIONS}"
|
|
246
|
+
)
|
|
247
|
+
source_count = len(SOURCE_ENTRY.findall(card_body))
|
|
248
|
+
if not MINIMUM_SOURCE_CITATIONS <= source_count <= MAXIMUM_SOURCE_CITATIONS:
|
|
249
|
+
errors.append(
|
|
250
|
+
f"{match.group(1)} has {source_count} source citations; expected {MINIMUM_SOURCE_CITATIONS} to {MAXIMUM_SOURCE_CITATIONS}"
|
|
251
|
+
)
|
|
252
|
+
role_count = len(SOURCE_ROLE.findall(card_body))
|
|
253
|
+
if role_count != source_count:
|
|
254
|
+
errors.append(
|
|
255
|
+
f"{match.group(1)} has {source_count} sources but {role_count} typed roles"
|
|
256
|
+
)
|
|
257
|
+
corroboration = " ".join(field_values.get("Corroboration", "").split())
|
|
258
|
+
entries = [
|
|
259
|
+
entry.strip()
|
|
260
|
+
for entry in corroboration.rstrip(".").split(";")
|
|
261
|
+
if entry.strip()
|
|
262
|
+
]
|
|
263
|
+
if not MINIMUM_CORROBORATION <= len(entries) <= MAXIMUM_CORROBORATION:
|
|
264
|
+
errors.append(
|
|
265
|
+
f"{match.group(1)} has {len(entries)} corroboration citations; expected {MINIMUM_CORROBORATION} to {MAXIMUM_CORROBORATION}"
|
|
266
|
+
)
|
|
267
|
+
for entry in entries:
|
|
268
|
+
if not CORROBORATION_KEY.match(entry):
|
|
269
|
+
errors.append(
|
|
270
|
+
f"{match.group(1)} has an unrecognized corroboration citation: {entry}"
|
|
271
|
+
)
|
|
272
|
+
if f"({SOURCE_KEY_FILE.rpartition('/')[2]})" not in body:
|
|
273
|
+
errors.append(f"{relative_path} does not link the corroboration source key")
|
|
274
|
+
validate_local_links(path, errors)
|
|
275
|
+
|
|
276
|
+
source_key_path = SKILL_ROOT / SOURCE_KEY_FILE
|
|
277
|
+
source_key_body = source_key_path.read_text(encoding="utf-8")
|
|
278
|
+
heading_index = source_key_body.find(RETRIEVAL_SECTION_HEADING)
|
|
279
|
+
if heading_index == -1:
|
|
280
|
+
errors.append(f"{SOURCE_KEY_FILE} is missing the retrieval-location section")
|
|
281
|
+
else:
|
|
282
|
+
retrieval_body = source_key_body[heading_index:]
|
|
283
|
+
for key_form in RETRIEVAL_KEY_FORMS:
|
|
284
|
+
if f"`{key_form}`" not in retrieval_body:
|
|
285
|
+
errors.append(
|
|
286
|
+
f"{SOURCE_KEY_FILE} retrieval section is missing an entry for `{key_form}`"
|
|
287
|
+
)
|
|
288
|
+
validate_local_links(source_key_path, errors)
|
|
289
|
+
|
|
290
|
+
for intent, cards in WORKFLOW_CARDS.items():
|
|
291
|
+
if not MINIMUM_CARD_LOAD <= len(cards) <= MAXIMUM_CARD_LOAD:
|
|
292
|
+
errors.append(f"{intent} starts with {len(cards)} cards")
|
|
293
|
+
unknown = set(cards) - actual_ids
|
|
294
|
+
if unknown:
|
|
295
|
+
errors.append(f"{intent} contains unknown cards: {sorted(unknown)}")
|
|
296
|
+
|
|
297
|
+
for topic, cards in TOPIC_CARDS.items():
|
|
298
|
+
unknown = set(cards) - actual_ids
|
|
299
|
+
if unknown:
|
|
300
|
+
errors.append(f"{topic} contains unknown cards: {sorted(unknown)}")
|
|
301
|
+
for intent, workflow_cards in WORKFLOW_CARDS.items():
|
|
302
|
+
routed_count = len(ordered_unique((*workflow_cards, *cards)))
|
|
303
|
+
if routed_count > MAXIMUM_CARD_LOAD:
|
|
304
|
+
errors.append(
|
|
305
|
+
f"{intent} plus {topic} routes {routed_count} cards; expected at most {MAXIMUM_CARD_LOAD}"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
skill_body = SKILL_PATH.read_text(encoding="utf-8")
|
|
309
|
+
if not skill_body.startswith("---\nname: buffett-investment-framework\n"):
|
|
310
|
+
errors.append("SKILL.md has an invalid public skill name")
|
|
311
|
+
for authority_boundary in (
|
|
312
|
+
"not a buy, sell, hold",
|
|
313
|
+
"position size",
|
|
314
|
+
"execute a trade",
|
|
315
|
+
):
|
|
316
|
+
if authority_boundary not in skill_body:
|
|
317
|
+
errors.append(
|
|
318
|
+
f"SKILL.md is missing authority boundary: {authority_boundary}"
|
|
319
|
+
)
|
|
320
|
+
if "[TODO" in skill_body:
|
|
321
|
+
errors.append("SKILL.md contains an unresolved scaffold TODO")
|
|
322
|
+
frontmatter_end = skill_body.find("\n---\n", 4)
|
|
323
|
+
if frontmatter_end == -1:
|
|
324
|
+
errors.append("SKILL.md is missing a closing frontmatter delimiter")
|
|
325
|
+
else:
|
|
326
|
+
frontmatter_block = skill_body[4 : frontmatter_end + 1]
|
|
327
|
+
desc_match = re.search(r"^description: (.*)$", frontmatter_block, re.MULTILINE)
|
|
328
|
+
if desc_match is None:
|
|
329
|
+
errors.append("SKILL.md frontmatter is missing a description field")
|
|
330
|
+
elif not desc_match.group(1).strip():
|
|
331
|
+
errors.append("SKILL.md frontmatter has an empty description value")
|
|
332
|
+
elif len(desc_match.group(1).strip()) > MAXIMUM_DESCRIPTION_LENGTH:
|
|
333
|
+
errors.append(
|
|
334
|
+
f"SKILL.md frontmatter description exceeds {MAXIMUM_DESCRIPTION_LENGTH} characters"
|
|
335
|
+
)
|
|
336
|
+
validate_local_links(SKILL_PATH, errors)
|
|
337
|
+
|
|
338
|
+
public_files = [
|
|
339
|
+
SKILL_PATH,
|
|
340
|
+
SKILL_ROOT / "agents" / "openai.yaml",
|
|
341
|
+
SKILL_ROOT / "scripts" / "framework.py",
|
|
342
|
+
SKILL_ROOT / SOURCE_KEY_FILE,
|
|
343
|
+
*(SKILL_ROOT / path for path in REFERENCE_FILES.values()),
|
|
344
|
+
]
|
|
345
|
+
prose_files = [
|
|
346
|
+
SKILL_PATH,
|
|
347
|
+
SKILL_ROOT / "agents" / "openai.yaml",
|
|
348
|
+
SKILL_ROOT / SOURCE_KEY_FILE,
|
|
349
|
+
*(SKILL_ROOT / path for path in REFERENCE_FILES.values()),
|
|
350
|
+
]
|
|
351
|
+
for path in prose_files:
|
|
352
|
+
body = path.read_text(encoding="utf-8")
|
|
353
|
+
for label, pattern in UNPUBLISHABLE_TEXT.items():
|
|
354
|
+
if pattern.search(body):
|
|
355
|
+
errors.append(f"{label} in public file: {path.relative_to(SKILL_ROOT)}")
|
|
356
|
+
|
|
357
|
+
expected_paths = {path.relative_to(SKILL_ROOT) for path in public_files}
|
|
358
|
+
actual_paths = {
|
|
359
|
+
path.relative_to(SKILL_ROOT)
|
|
360
|
+
for path in SKILL_ROOT.rglob("*")
|
|
361
|
+
if path.is_file() and "__pycache__" not in path.parts and path.suffix != ".pyc"
|
|
362
|
+
}
|
|
363
|
+
if actual_paths != expected_paths:
|
|
364
|
+
errors.append(
|
|
365
|
+
f"package file mismatch: missing={sorted(expected_paths - actual_paths)}, extra={sorted(actual_paths - expected_paths)}"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if errors:
|
|
369
|
+
for error in errors:
|
|
370
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
371
|
+
return 1
|
|
372
|
+
|
|
373
|
+
counts = "/".join(str(count) for count in EXPECTED_COUNTS.values())
|
|
374
|
+
print(
|
|
375
|
+
f"PASS: {len(actual_ids)} cards; module counts {counts}; public fields, source citations, corroboration keys, workflows, links, authority boundaries, description, and isolation checks resolve"
|
|
376
|
+
)
|
|
377
|
+
return 0
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def route(intent: str, topics: list[str]) -> int:
|
|
381
|
+
"""Print a deterministic bounded card load and exact reference locations."""
|
|
382
|
+
requested = list(WORKFLOW_CARDS[intent])
|
|
383
|
+
for topic in topics:
|
|
384
|
+
requested.extend(TOPIC_CARDS[topic])
|
|
385
|
+
cards = ordered_unique(requested)
|
|
386
|
+
if len(cards) > MAXIMUM_CARD_LOAD:
|
|
387
|
+
print(
|
|
388
|
+
f"ERROR: route requires {len(cards)} cards, above the {MAXIMUM_CARD_LOAD}-card cap; split the task or remove a topic",
|
|
389
|
+
file=sys.stderr,
|
|
390
|
+
)
|
|
391
|
+
return 2
|
|
392
|
+
|
|
393
|
+
locations = card_locations()
|
|
394
|
+
print("status: ready")
|
|
395
|
+
print(f"intent: {intent}")
|
|
396
|
+
print(f"topics: {', '.join(topics) if topics else 'none'}")
|
|
397
|
+
print(f"cards ({len(cards)}): {', '.join(cards)}")
|
|
398
|
+
print("read:")
|
|
399
|
+
for location in ordered_unique(locations[card_id] for card_id in cards):
|
|
400
|
+
print(f" - {location}")
|
|
401
|
+
return 0
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def show(card_id: str) -> int:
|
|
405
|
+
"""Print one complete card from its reference module."""
|
|
406
|
+
normalized = card_id.upper()
|
|
407
|
+
locations = card_locations()
|
|
408
|
+
if normalized not in locations:
|
|
409
|
+
print(f"ERROR: unknown card ID: {card_id}", file=sys.stderr)
|
|
410
|
+
return 2
|
|
411
|
+
relative_path = locations[normalized].split("#", maxsplit=1)[0]
|
|
412
|
+
body = (SKILL_ROOT / relative_path).read_text(encoding="utf-8")
|
|
413
|
+
matches = list(CARD_HEADING.finditer(body))
|
|
414
|
+
for index, match in enumerate(matches):
|
|
415
|
+
if match.group(1) != normalized:
|
|
416
|
+
continue
|
|
417
|
+
end = matches[index + 1].start() if index + 1 < len(matches) else len(body)
|
|
418
|
+
footer_start = body.find("\n<!--", match.end(), end)
|
|
419
|
+
if footer_start != -1:
|
|
420
|
+
end = footer_start
|
|
421
|
+
if index + 1 < len(matches):
|
|
422
|
+
next_anchor = body.rfind("\n<a id=", match.end(), end)
|
|
423
|
+
if next_anchor != -1:
|
|
424
|
+
end = next_anchor
|
|
425
|
+
print(f"source: {locations[normalized]}")
|
|
426
|
+
print(body[match.start() : end].rstrip())
|
|
427
|
+
return 0
|
|
428
|
+
raise AssertionError(f"indexed card is not readable: {normalized}")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def parser() -> argparse.ArgumentParser:
|
|
432
|
+
"""Build the command-line interface."""
|
|
433
|
+
root = argparse.ArgumentParser(description=__doc__)
|
|
434
|
+
commands = root.add_subparsers(dest="command", required=True)
|
|
435
|
+
_ = commands.add_parser("validate", help="validate the complete standalone package")
|
|
436
|
+
|
|
437
|
+
route_parser = commands.add_parser("route", help="select a bounded card load")
|
|
438
|
+
_ = route_parser.add_argument("--intent", required=True, choices=WORKFLOW_CARDS)
|
|
439
|
+
_ = route_parser.add_argument(
|
|
440
|
+
"--topic",
|
|
441
|
+
action="append",
|
|
442
|
+
default=[],
|
|
443
|
+
choices=TOPIC_CARDS,
|
|
444
|
+
help="add a material topic; repeat as needed",
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
show_parser = commands.add_parser("show", help="print one complete card")
|
|
448
|
+
_ = show_parser.add_argument("--id", required=True, dest="card_id")
|
|
449
|
+
return root
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def main() -> int:
|
|
453
|
+
"""Run the requested framework command."""
|
|
454
|
+
arguments = parser().parse_args()
|
|
455
|
+
command = cast(str, arguments.command)
|
|
456
|
+
try:
|
|
457
|
+
if command == "validate":
|
|
458
|
+
return validate()
|
|
459
|
+
if command == "route":
|
|
460
|
+
return route(cast(str, arguments.intent), cast(list[str], arguments.topic))
|
|
461
|
+
if command == "show":
|
|
462
|
+
return show(cast(str, arguments.card_id))
|
|
463
|
+
except (OSError, ValueError) as error:
|
|
464
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
465
|
+
return 1
|
|
466
|
+
raise AssertionError(f"unhandled command: {command}")
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
if __name__ == "__main__":
|
|
470
|
+
raise SystemExit(main())
|