@event4u/agent-config 5.6.1 → 5.8.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/.agent-src/commands/agent-handoff.md +1 -1
- package/.agent-src/commands/agent-status.md +1 -1
- package/.agent-src/commands/agents/audit.md +1 -1
- package/.agent-src/commands/agents/init.md +1 -1
- package/.agent-src/commands/agents/user/accept.md +3 -3
- package/.agent-src/commands/agents/user/init.md +4 -4
- package/.agent-src/commands/agents/user/show.md +3 -3
- package/.agent-src/commands/agents/user/update.md +3 -3
- package/.agent-src/commands/agents/user.md +1 -1
- package/.agent-src/commands/agents.md +1 -1
- package/.agent-src/commands/analytics/prune.md +1 -1
- package/.agent-src/commands/analytics/show.md +1 -1
- package/.agent-src/commands/analytics.md +1 -1
- package/.agent-src/commands/bug-fix.md +1 -1
- package/.agent-src/commands/challenge-me.md +1 -1
- package/.agent-src/commands/chat-history/import.md +1 -1
- package/.agent-src/commands/chat-history/learn.md +1 -1
- package/.agent-src/commands/chat-history/show.md +1 -1
- package/.agent-src/commands/chat-history.md +1 -1
- package/.agent-src/commands/check-current-md.md +1 -1
- package/.agent-src/commands/condense.md +1 -1
- package/.agent-src/commands/context.md +1 -1
- package/.agent-src/commands/cost-report.md +13 -8
- package/.agent-src/commands/council.md +3 -3
- package/.agent-src/commands/create-pr/description-only.md +1 -1
- package/.agent-src/commands/create-pr.md +1 -1
- package/.agent-src/commands/e2e-heal.md +1 -1
- package/.agent-src/commands/e2e-plan.md +1 -1
- package/.agent-src/commands/feature.md +1 -1
- package/.agent-src/commands/fix/ci.md +1 -1
- package/.agent-src/commands/fix/portability.md +1 -1
- package/.agent-src/commands/fix/pr-bot-comments.md +1 -1
- package/.agent-src/commands/fix/pr-comments.md +1 -1
- package/.agent-src/commands/fix/pr-developer-comments.md +1 -1
- package/.agent-src/commands/fix/refs.md +1 -1
- package/.agent-src/commands/fix/seeder.md +1 -1
- package/.agent-src/commands/fix.md +1 -1
- package/.agent-src/commands/judge.md +1 -1
- package/.agent-src/commands/knowledge/cross-repo.md +1 -1
- package/.agent-src/commands/knowledge/forget.md +1 -1
- package/.agent-src/commands/knowledge/ingest.md +1 -1
- package/.agent-src/commands/knowledge/list.md +1 -1
- package/.agent-src/commands/knowledge.md +1 -1
- package/.agent-src/commands/memory/add.md +1 -1
- package/.agent-src/commands/memory/learn-low-impact.md +1 -1
- package/.agent-src/commands/memory/load.md +1 -1
- package/.agent-src/commands/memory/mine-session.md +1 -1
- package/.agent-src/commands/memory/promote.md +1 -1
- package/.agent-src/commands/memory/propose.md +1 -1
- package/.agent-src/commands/memory.md +1 -1
- package/.agent-src/commands/mode.md +1 -1
- package/.agent-src/commands/optimize/agents-dir.md +1 -1
- package/.agent-src/commands/optimize/augmentignore.md +1 -1
- package/.agent-src/commands/optimize/rtk.md +1 -1
- package/.agent-src/commands/optimize/skills.md +1 -1
- package/.agent-src/commands/optimize.md +1 -1
- package/.agent-src/commands/orchestrate.md +1 -1
- package/.agent-src/commands/override/create.md +1 -1
- package/.agent-src/commands/override/manage.md +1 -1
- package/.agent-src/commands/override.md +1 -1
- package/.agent-src/commands/package-reset.md +1 -1
- package/.agent-src/commands/prediction-pool.md +234 -0
- package/.agent-src/commands/profile/activate.md +81 -0
- package/.agent-src/commands/profile/deactivate.md +68 -0
- package/.agent-src/commands/profile/show.md +70 -0
- package/.agent-src/commands/profile.md +68 -0
- package/.agent-src/commands/project-health.md +1 -1
- package/.agent-src/commands/quality-fix.md +1 -1
- package/.agent-src/commands/roadmap/process-full.md +1 -1
- package/.agent-src/commands/roadmap/process-phase.md +1 -1
- package/.agent-src/commands/roadmap/process-step.md +1 -1
- package/.agent-src/commands/roadmap.md +1 -1
- package/.agent-src/commands/set-cost-profile.md +9 -9
- package/.agent-src/commands/skill/preview.md +3 -3
- package/.agent-src/commands/skill.md +1 -1
- package/.agent-src/commands/skills/discover.md +1 -1
- package/.agent-src/commands/skills.md +1 -1
- package/.agent-src/commands/sync-agent-settings.md +3 -3
- package/.agent-src/commands/sync-gitignore/fix.md +1 -1
- package/.agent-src/commands/sync-gitignore.md +1 -1
- package/.agent-src/commands/update-form-request-messages.md +1 -1
- package/.agent-src/presets/README.md +1 -1
- package/.agent-src/profiles/README.md +1 -1
- package/.agent-src/rules/non-destructive-by-default.md +2 -1
- package/.agent-src/skills/check-refs/SKILL.md +1 -1
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +1 -1
- package/.agent-src/skills/git-workflow/SKILL.md +1 -1
- package/.agent-src/skills/jira-integration/SKILL.md +1 -1
- package/.agent-src/skills/markitdown/SKILL.md +1 -1
- package/.agent-src/skills/prediction-pool-optimizer/SKILL.md +314 -0
- package/.agent-src/skills/prediction-pool-optimizer/evals/triggers.json +20 -0
- package/.agent-src/skills/prediction-pool-optimizer/reference/ev-fixtures.md +175 -0
- package/.agent-src/skills/prediction-pool-optimizer/reference/odds-and-bonus.md +109 -0
- package/.agent-src/skills/rtk-output-filtering/SKILL.md +1 -1
- package/.agent-src/skills/script-writing/SKILL.md +1 -1
- package/.agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/.agent-src/templates/agent-settings.md +7 -7
- package/.agent-src/templates/agents/agent-project-settings.example.yml +2 -2
- package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +54 -6
- package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +1 -1
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +9 -7
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +9 -10
- package/.agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +17 -4
- package/.claude-plugin/marketplace.json +370 -364
- package/CHANGELOG.md +108 -0
- package/README.md +2 -2
- package/config/agent-settings.template.yml +11 -2
- package/config/discovery/packs.yml +11 -0
- package/config/discovery/session-profiles.yml +37 -0
- package/config/discovery/workspaces.yml +1 -1
- package/config/profiles/balanced.ini +1 -1
- package/config/profiles/full.ini +1 -1
- package/config/profiles/minimal.ini +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +254 -100
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +41 -6
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +19 -6
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/server/io/substituteTemplate.js +3 -3
- package/dist/server/io/substituteTemplate.js.map +1 -1
- package/dist/server/routes/settings.js +2 -2
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/{index-DVsyUMZe.js → index-5lFqAKL0.js} +2 -2
- package/dist/ui/assets/index-5lFqAKL0.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/architecture/current-onboard-baseline.md +3 -3
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +11 -5
- package/docs/contracts/adr-level-6-productization.md +1 -1
- package/docs/contracts/command-clusters.md +2 -0
- package/docs/contracts/config-presets.md +2 -2
- package/docs/contracts/cost-profile-defaults.md +5 -5
- package/docs/contracts/discovery-manifest.schema.json +1 -1
- package/docs/contracts/explain-trace.schema.json +3 -3
- package/docs/contracts/memory-visibility-v1.md +15 -7
- package/docs/contracts/profile-system.md +2 -2
- package/docs/contracts/session-profile-overlay.md +120 -0
- package/docs/contracts/settings-api.md +3 -3
- package/docs/contracts/value-report-schema.md +14 -1
- package/docs/customization.md +47 -5
- package/docs/decisions/ADR-010-profile-pack-preset-boundary.md +47 -11
- package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +16 -2
- package/docs/decisions/ADR-034-per-skill-model-recommendation-transport.md +1 -1
- package/docs/decisions/ADR-036-global-install-browser-wizard-handoff.md +106 -0
- package/docs/decisions/ADR-037-cost-profile-untangle.md +117 -0
- package/docs/decisions/ADR-038-canonical-settings-path.md +66 -0
- package/docs/decisions/ADR-039-claude-skills-untracked.md +139 -0
- package/docs/decisions/ADR-rule-kernel-and-router.md +1 -1
- package/docs/decisions/INDEX.md +4 -0
- package/docs/development.md +12 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/layered-settings.md +10 -4
- package/docs/installation.md +3 -3
- package/docs/setup/mcp-client-config.md +1 -1
- package/docs/skills-catalog.md +5 -1
- package/docs/value.md +9 -7
- package/docs/wizard.md +1 -1
- package/llms.txt +4 -0
- package/package.json +1 -1
- package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
- package/scripts/_cli/cmd_doctor.py +3 -2
- package/scripts/_cli/cmd_explain.py +1 -1
- package/scripts/_cli/cmd_versions.py +2 -2
- package/scripts/_cli/explain_last/inputs.py +11 -8
- package/scripts/_cli/explain_last/sections/inputs.py +1 -1
- package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
- package/scripts/_lib/agent_settings.py +54 -6
- package/scripts/_lib/agent_src.py +30 -0
- package/scripts/_lib/value_ladder.py +99 -2
- package/scripts/_lib/value_report.py +30 -16
- package/scripts/ai_council/modes.py +1 -1
- package/scripts/ai_council/session.py +5 -1
- package/scripts/audit_command_surface.py +7 -1
- package/scripts/audit_initial_context.py +26 -2
- package/scripts/check_gate_paths.py +117 -0
- package/scripts/check_references.py +51 -2
- package/scripts/check_skill_requires.py +143 -0
- package/scripts/check_test_coverage_diff.py +180 -0
- package/scripts/compile_router.py +5 -1
- package/scripts/condense.py +92 -4
- package/scripts/config/session_profiles.py +492 -0
- package/scripts/council_cli.py +5 -1
- package/scripts/first-run.sh +11 -11
- package/scripts/hook_manifest.yaml +15 -7
- package/scripts/hooks/dispatch_hook.py +8 -0
- package/scripts/install +14 -1
- package/scripts/install-hooks.sh +2 -1
- package/scripts/install.py +203 -433
- package/scripts/install_anthropic_key.sh +1 -1
- package/scripts/install_openai_key.sh +1 -1
- package/scripts/inventory_abstraction_budget.py +6 -1
- package/scripts/lint_agents_md.py +11 -4
- package/scripts/lint_discovery_vocabulary.py +5 -5
- package/scripts/lint_hook_concern_budget.py +5 -1
- package/scripts/lint_marketplace.py +18 -7
- package/scripts/lint_roadmap_ci_steps.py +5 -1
- package/scripts/lint_roadmap_complexity.py +5 -1
- package/scripts/lint_value_dashboard.py +1 -1
- package/scripts/mcp_server/prompts.py +5 -1
- package/scripts/prediction-pool/adapters/_schema.md +42 -0
- package/scripts/prediction-pool/adapters/kicktipp.yml +23 -0
- package/scripts/prediction-pool/poisson_sim.py +167 -0
- package/scripts/prediction-pool/pool_winsim.py +236 -0
- package/scripts/prediction-pool/score_ev.py +188 -0
- package/scripts/profile_staleness_hook.py +69 -0
- package/scripts/render_value_md.py +1 -0
- package/scripts/roadmap_progress_hook.py +56 -6
- package/scripts/schemas/agent-settings.schema.json +77 -0
- package/scripts/schemas/skill.schema.json +7 -0
- package/scripts/smoke_quickstart.py +7 -6
- package/scripts/sync_agent_settings.py +12 -5
- package/scripts/validate_agent_settings.py +124 -0
- package/scripts/validate_decision_engine.py +5 -1
- package/templates/minimal/.agent-settings.yml +1 -1
- package/dist/ui/assets/index-DVsyUMZe.js.map +0 -1
- package/scripts/measure_roadmap_trajectory.py +0 -112
- package/scripts/verify_roadmap_closure.py +0 -327
|
@@ -104,5 +104,5 @@ echo " Remove: rm ${TARGET_FILE}"
|
|
|
104
104
|
echo
|
|
105
105
|
echo "ℹ️ Key install ≠ enable. To use this provider in /council:"
|
|
106
106
|
echo " set ai_council.enabled: true and ai_council.members.anthropic.enabled: true"
|
|
107
|
-
echo " in .agent-settings.yml. Council is a 'full'
|
|
107
|
+
echo " in .agent-settings.yml. Council is a 'full' rule_loading_tier feature; under"
|
|
108
108
|
echo " 'minimal' / 'balanced' the runtime hooks stay inactive."
|
|
@@ -104,5 +104,5 @@ echo " Remove: rm ${TARGET_FILE}"
|
|
|
104
104
|
echo
|
|
105
105
|
echo "ℹ️ Key install ≠ enable. To use this provider in /council:"
|
|
106
106
|
echo " set ai_council.enabled: true and ai_council.members.openai.enabled: true"
|
|
107
|
-
echo " in .agent-settings.yml. Council is a 'full'
|
|
107
|
+
echo " in .agent-settings.yml. Council is a 'full' rule_loading_tier feature; under"
|
|
108
108
|
echo " 'minimal' / 'balanced' the runtime hooks stay inactive."
|
|
@@ -45,7 +45,12 @@ try:
|
|
|
45
45
|
except ImportError:
|
|
46
46
|
script_output = None # graceful fallback when running outside repo
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
from _lib.agent_src import resolve_package_core_path # noqa: E402
|
|
49
|
+
|
|
50
|
+
CORE_SRC = resolve_package_core_path(".agent-src.uncondensed")
|
|
51
|
+
# Enforced packages/core targets — read by scripts/check_gate_paths.py so a
|
|
52
|
+
# future move that desyncs this path fails CI instead of silently no-opping.
|
|
53
|
+
GATE_CORE_PATHS = (CORE_SRC,)
|
|
49
54
|
DIRECTIVES_ROOT = CORE_SRC / "templates" / "scripts" / "work_engine" / "directives"
|
|
50
55
|
EVIDENCE_DIR = REPO_ROOT / "agents" / "evidence" / "analysis"
|
|
51
56
|
|
|
@@ -23,8 +23,18 @@ from dataclasses import dataclass
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
|
|
25
25
|
ROOT = Path(__file__).resolve().parent.parent
|
|
26
|
+
sys.path.insert(0, str(ROOT / "scripts"))
|
|
27
|
+
from _lib.agent_src import resolve_package_core_path # noqa: E402
|
|
28
|
+
|
|
26
29
|
QUIET = "--quiet" in sys.argv
|
|
27
30
|
|
|
31
|
+
_CONSUMER_TEMPLATE = resolve_package_core_path(".agent-src.uncondensed/templates/AGENTS.md")
|
|
32
|
+
# Enforced packages/core target — read by scripts/check_gate_paths.py so a
|
|
33
|
+
# future move that desyncs this path fails CI instead of silently no-opping.
|
|
34
|
+
# Only the consumer-template lives under packages/core; the package-root
|
|
35
|
+
# AGENTS.md sits at the repo root and is intentionally excluded.
|
|
36
|
+
GATE_CORE_PATHS = (_CONSUMER_TEMPLATE,)
|
|
37
|
+
|
|
28
38
|
LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
|
29
39
|
PATH_BACKTICK_RE = re.compile(r"`[^`]*/[^`]*`")
|
|
30
40
|
BULLET_RE = re.compile(r"^\s*[-*+]\s+")
|
|
@@ -64,10 +74,7 @@ class Target:
|
|
|
64
74
|
|
|
65
75
|
TARGETS = [
|
|
66
76
|
Target(ROOT / "AGENTS.md", "package-root", 3000, 2800, template=False),
|
|
67
|
-
Target(
|
|
68
|
-
ROOT / "packages" / "core" / ".agent-src.uncondensed" / "templates" / "AGENTS.md",
|
|
69
|
-
"consumer-template", 2500, 2300, template=True,
|
|
70
|
-
),
|
|
77
|
+
Target(_CONSUMER_TEMPLATE, "consumer-template", 2500, 2300, template=True),
|
|
71
78
|
]
|
|
72
79
|
|
|
73
80
|
|
|
@@ -10,7 +10,7 @@ every pack listed as a workspace's default/optional MUST list that
|
|
|
10
10
|
workspace in its own `workspaces:` array (and vice versa).
|
|
11
11
|
|
|
12
12
|
Non-overlap (ADR-010 alignment): no pack id may collide with a
|
|
13
|
-
`
|
|
13
|
+
`rule_loading_tier` value (minimal/balanced/full/custom) or a `profile.id`
|
|
14
14
|
value (founder/developer/content_creator/agency/finance/ops).
|
|
15
15
|
|
|
16
16
|
Cap: ≤ 150 LOC, stdlib + PyYAML. Exit 0 clean, 1 on failure.
|
|
@@ -41,11 +41,11 @@ ADR_PACKS: frozenset[str] = frozenset({
|
|
|
41
41
|
"typescript", "react", "nextjs", "python", "product-basic",
|
|
42
42
|
"product-discovery", "finance-basic", "finance-advanced",
|
|
43
43
|
"gtm-sales", "gtm-marketing", "ops-people", "founder-strategy", "small-business",
|
|
44
|
-
"construction", "ai-video", "meta",
|
|
44
|
+
"construction", "ai-video", "fun", "meta",
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
# ADR-010 non-overlap reservations.
|
|
48
|
-
|
|
48
|
+
RULE_LOADING_TIER_RESERVED: frozenset[str] = frozenset({
|
|
49
49
|
"minimal", "balanced", "full", "custom",
|
|
50
50
|
})
|
|
51
51
|
PROFILE_ID_RESERVED: frozenset[str] = frozenset({
|
|
@@ -136,9 +136,9 @@ def lint(quiet: bool) -> int:
|
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
# 5. Non-overlap (ADR-010).
|
|
139
|
-
overlap_cost = pack_ids &
|
|
139
|
+
overlap_cost = pack_ids & RULE_LOADING_TIER_RESERVED
|
|
140
140
|
if overlap_cost:
|
|
141
|
-
errors.append(f"pack ids collide with
|
|
141
|
+
errors.append(f"pack ids collide with rule_loading_tier values: {sorted(overlap_cost)}")
|
|
142
142
|
overlap_profile = pack_ids & PROFILE_ID_RESERVED
|
|
143
143
|
if overlap_profile:
|
|
144
144
|
errors.append(f"pack ids collide with profile.id values: {sorted(overlap_profile)}")
|
|
@@ -49,10 +49,14 @@ import argparse
|
|
|
49
49
|
import re
|
|
50
50
|
import sys
|
|
51
51
|
from pathlib import Path
|
|
52
|
+
try: # invocation-agnostic import (repo-root-on-path vs scripts-on-path)
|
|
53
|
+
from scripts._lib.agent_settings import project_settings_path
|
|
54
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
55
|
+
from _lib.agent_settings import project_settings_path
|
|
52
56
|
|
|
53
57
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
54
58
|
DEFAULT_MANIFEST = REPO_ROOT / "scripts" / "hook_manifest.yaml"
|
|
55
|
-
DEFAULT_SETTINGS = REPO_ROOT
|
|
59
|
+
DEFAULT_SETTINGS = project_settings_path(REPO_ROOT)
|
|
56
60
|
|
|
57
61
|
DEFAULT_MAX_PER_EVENT = 8
|
|
58
62
|
DEFAULT_TIER1: list[str] = []
|
|
@@ -25,7 +25,14 @@ from pathlib import Path
|
|
|
25
25
|
ROOT = Path(".")
|
|
26
26
|
MARKETPLACE = ROOT / ".claude-plugin" / "marketplace.json"
|
|
27
27
|
PACKAGE_JSON = ROOT / "package.json"
|
|
28
|
-
|
|
28
|
+
# Committed marketplace skill sources (git-consumed): real skills resolve to
|
|
29
|
+
# .agent-src/skills/<name>; command-as-skill entries to the committed
|
|
30
|
+
# .claude-plugin/skills/<slug> projection. .claude/skills/ is a gitignored
|
|
31
|
+
# local channel and is intentionally NOT a marketplace source.
|
|
32
|
+
SKILL_SOURCE_DIRS = (
|
|
33
|
+
ROOT / ".agent-src" / "skills",
|
|
34
|
+
ROOT / ".claude-plugin" / "skills",
|
|
35
|
+
)
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
def fail(errors: list[str]) -> int:
|
|
@@ -124,9 +131,10 @@ def main() -> int:
|
|
|
124
131
|
if not skill_md.exists():
|
|
125
132
|
errors.append(f"{entry} has no SKILL.md: `{path}`")
|
|
126
133
|
|
|
127
|
-
# Reverse-completeness: every SKILL.md on disk under
|
|
128
|
-
#
|
|
129
|
-
# skills
|
|
134
|
+
# Reverse-completeness: every SKILL.md on disk under the committed skill
|
|
135
|
+
# sources (.agent-src/skills/ + .claude-plugin/skills/) must appear in some
|
|
136
|
+
# plugin's skills[]. Catches the drift where new skills/commands are
|
|
137
|
+
# generated but never added to the marketplace manifest.
|
|
130
138
|
listed: set[str] = set()
|
|
131
139
|
for plugin in plugins:
|
|
132
140
|
if not isinstance(plugin, dict):
|
|
@@ -135,13 +143,16 @@ def main() -> int:
|
|
|
135
143
|
if isinstance(path, str):
|
|
136
144
|
listed.add(path.removeprefix("./"))
|
|
137
145
|
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
for source_dir in SKILL_SOURCE_DIRS:
|
|
147
|
+
if not source_dir.exists():
|
|
148
|
+
continue
|
|
149
|
+
prefix = source_dir.relative_to(ROOT).as_posix()
|
|
150
|
+
for skill_dir in sorted(source_dir.iterdir()):
|
|
140
151
|
if not skill_dir.is_dir():
|
|
141
152
|
continue
|
|
142
153
|
if not (skill_dir / "SKILL.md").exists():
|
|
143
154
|
continue
|
|
144
|
-
rel = f"
|
|
155
|
+
rel = f"{prefix}/{skill_dir.name}"
|
|
145
156
|
if rel not in listed:
|
|
146
157
|
errors.append(
|
|
147
158
|
f"skill exists on disk but is not listed in marketplace.json: "
|
|
@@ -22,12 +22,16 @@ from __future__ import annotations
|
|
|
22
22
|
import re
|
|
23
23
|
import sys
|
|
24
24
|
from pathlib import Path
|
|
25
|
+
try: # invocation-agnostic import (repo-root-on-path vs scripts-on-path)
|
|
26
|
+
from scripts._lib.agent_settings import project_settings_path
|
|
27
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
28
|
+
from _lib.agent_settings import project_settings_path
|
|
25
29
|
|
|
26
30
|
QUIET = "--quiet" in sys.argv
|
|
27
31
|
|
|
28
32
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
29
33
|
ROADMAP_GLOB = "agents/roadmaps/*.md"
|
|
30
|
-
SETTINGS_FILE = REPO_ROOT
|
|
34
|
+
SETTINGS_FILE = project_settings_path(REPO_ROOT)
|
|
31
35
|
LOCAL_AUTO_RUN_PAT = re.compile(
|
|
32
36
|
r"^\s*local_auto_run:\s*(true|false)\s*(?:#.*)?$", re.MULTILINE
|
|
33
37
|
)
|
|
@@ -23,6 +23,10 @@ from __future__ import annotations
|
|
|
23
23
|
import re
|
|
24
24
|
import sys
|
|
25
25
|
from pathlib import Path
|
|
26
|
+
try: # invocation-agnostic import (repo-root-on-path vs scripts-on-path)
|
|
27
|
+
from scripts._lib.agent_settings import project_settings_path
|
|
28
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
29
|
+
from _lib.agent_settings import project_settings_path
|
|
26
30
|
|
|
27
31
|
QUIET = "--quiet" in sys.argv
|
|
28
32
|
|
|
@@ -30,7 +34,7 @@ REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
|
30
34
|
ROADMAP_GLOB = "agents/roadmaps/*.md"
|
|
31
35
|
LIGHTWEIGHT_LINE_CAP = 600
|
|
32
36
|
LIGHTWEIGHT_PHASE_CAP = 6
|
|
33
|
-
SETTINGS_FILE = REPO_ROOT
|
|
37
|
+
SETTINGS_FILE = project_settings_path(REPO_ROOT)
|
|
34
38
|
HORIZON_WEEKS_PAT = re.compile(
|
|
35
39
|
r"^\s*horizon_weeks:\s*(\d+)\s*(?:#.*)?$", re.MULTILINE
|
|
36
40
|
)
|
|
@@ -48,7 +48,7 @@ REQUIRED_SECTIONS = (
|
|
|
48
48
|
"**NETTO",
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
-
CANONICAL_RUNG_IDS = ("baseline", "load", "condense", "rtk", "terse")
|
|
51
|
+
CANONICAL_RUNG_IDS = ("baseline", "load", "thin", "condense", "rtk", "terse")
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def _log(msg: str, quiet: bool, *, err: bool = False) -> None:
|
|
@@ -21,6 +21,10 @@ from __future__ import annotations
|
|
|
21
21
|
import dataclasses
|
|
22
22
|
from dataclasses import dataclass, field
|
|
23
23
|
from pathlib import Path
|
|
24
|
+
try: # invocation-agnostic import (repo-root-on-path vs scripts-on-path)
|
|
25
|
+
from scripts._lib.agent_settings import project_settings_path
|
|
26
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
27
|
+
from _lib.agent_settings import project_settings_path
|
|
24
28
|
from typing import Any, Literal
|
|
25
29
|
|
|
26
30
|
# Phase 1 hand-picked skills — kept for the Phase-1 entrypoint
|
|
@@ -128,7 +132,7 @@ def _load_active_user_type(root: Path) -> str:
|
|
|
128
132
|
the loader (consistent with `_strip_frontmatter`). Only matches
|
|
129
133
|
`user_type:` directly under the top-level `personal:` block.
|
|
130
134
|
"""
|
|
131
|
-
settings = root
|
|
135
|
+
settings = project_settings_path(root)
|
|
132
136
|
if not settings.is_file():
|
|
133
137
|
return ""
|
|
134
138
|
try:
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Tippspiel adapter contract — declarative selector maps
|
|
2
|
+
|
|
3
|
+
An adapter is **data, not code**: a YAML file mapping a prediction-pool
|
|
4
|
+
platform's tip-form fields to CSS selectors. A generic, trusted Playwright
|
|
5
|
+
driver reads it and fills the inputs. Because adapters carry no executable
|
|
6
|
+
code, contributing one via PR is safe — there is no supply-chain surface to
|
|
7
|
+
audit beyond the selectors themselves.
|
|
8
|
+
|
|
9
|
+
One file per platform: `scripts/prediction-pool/adapters/<platform>.yml`.
|
|
10
|
+
|
|
11
|
+
## Required keys
|
|
12
|
+
|
|
13
|
+
| Key | Type | Meaning |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `platform` | string | Stable id, matches the filename (`kicktipp`). |
|
|
16
|
+
| `match` | string | URL host (or substring) this adapter applies to. |
|
|
17
|
+
| `login_required` | bool | Always `true` — the user logs in; the driver never handles credentials. |
|
|
18
|
+
| `selectors.row` | string | CSS selector for one repeated **match row** on the tip page. |
|
|
19
|
+
| `selectors.home_input` | string | Within a row: the home-score input. |
|
|
20
|
+
| `selectors.away_input` | string | Within a row: the away-score input. |
|
|
21
|
+
|
|
22
|
+
## Optional keys
|
|
23
|
+
|
|
24
|
+
| Key | Type | Meaning |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `tip_page_hint` | string | URL path pattern of the tip page (e.g. `/<pool>/tippabgabe`). |
|
|
27
|
+
| `selectors.home_team` / `selectors.away_team` | string | Within a row: team-name nodes, to align the right tip to the right match. |
|
|
28
|
+
| `selectors.bonus_*` | string | Selectors for bonus-question inputs. |
|
|
29
|
+
| `selectors.submit` | string | The submit control. **The driver NEVER clicks this** unless the user authorized submit this turn. |
|
|
30
|
+
| `notes` | string | Drift warnings, quirks, last-verified date. |
|
|
31
|
+
|
|
32
|
+
## Rules for adapters
|
|
33
|
+
|
|
34
|
+
- **No code.** YAML data only — no scripts, no JS, no URLs the driver
|
|
35
|
+
fetches. Selectors and hints, nothing else.
|
|
36
|
+
- **`submit` is never auto-clicked.** It exists so the driver can *locate*
|
|
37
|
+
(and, only on explicit authorization, click) the control — never by default.
|
|
38
|
+
- **Selectors drift.** If a row/input selector no longer matches at run
|
|
39
|
+
time, the driver falls back to the **vision-assisted synthesis** path
|
|
40
|
+
(screenshot → identify fields → user confirms) rather than guessing.
|
|
41
|
+
- **PR contributions** add exactly one `<platform>.yml` plus, ideally, a
|
|
42
|
+
`notes:` line with the date the selectors were verified.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# kicktipp — declarative selector map (see _schema.md)
|
|
2
|
+
#
|
|
3
|
+
# Selectors are a STARTING POINT and may drift if kicktipp changes its DOM.
|
|
4
|
+
# They have not been re-verified against the live site in this commit — if a
|
|
5
|
+
# selector no longer matches at run time, the driver falls back to the
|
|
6
|
+
# vision-assisted synthesis path. Update `notes.verified` when you confirm.
|
|
7
|
+
platform: kicktipp
|
|
8
|
+
match: kicktipp.de
|
|
9
|
+
login_required: true
|
|
10
|
+
tip_page_hint: "/<pool>/tippabgabe"
|
|
11
|
+
selectors:
|
|
12
|
+
row: "form#tippabgabeSpiele tbody tr"
|
|
13
|
+
home_team: "td.col1 .nfooball, td.col1"
|
|
14
|
+
away_team: "td.col3 .nfooball, td.col3"
|
|
15
|
+
home_input: "input.kicktipp-heimtipp"
|
|
16
|
+
away_input: "input.kicktipp-gasttipp"
|
|
17
|
+
# submit is located, never auto-clicked unless the user authorizes it this turn
|
|
18
|
+
submit: "input[type=submit][value*='Tipps speichern'], button[type=submit]"
|
|
19
|
+
notes: |
|
|
20
|
+
verified: unverified (DOM not inspected in this commit)
|
|
21
|
+
kicktipp renders one <tr> per match inside the tip form; each row carries a
|
|
22
|
+
home and away number input. Bonus questions live on separate pages per pool.
|
|
23
|
+
If the row/input selectors miss, use the vision path — do not guess.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Poisson tournament simulator for prediction-pool-optimizer.
|
|
3
|
+
|
|
4
|
+
Honest replacement for "I simulated 10,000 runs" — this actually runs them.
|
|
5
|
+
Goals per match are drawn from a Poisson whose rate comes from each team's
|
|
6
|
+
attack / defence strength; group stages are round-robin, then a single-
|
|
7
|
+
elimination bracket runs over the qualifiers. Aggregates advancement and
|
|
8
|
+
title probabilities over N runs.
|
|
9
|
+
|
|
10
|
+
It is an APPROXIMATION, stated as such: real tournament bracket pairings
|
|
11
|
+
(winner-of-A vs runner-up-of-B …) are format-specific. Provide an explicit
|
|
12
|
+
`bracket` (list of name pairs per round, or "auto" for a random seed) to
|
|
13
|
+
control this; the default "auto" randomly seeds qualifiers and is good
|
|
14
|
+
enough for outright/advancement estimates, not for exact-pairing bonus
|
|
15
|
+
questions.
|
|
16
|
+
|
|
17
|
+
Input JSON shape:
|
|
18
|
+
{
|
|
19
|
+
"base_goals": 1.35, # league-average goals per side
|
|
20
|
+
"teams": { "Germany": {"att": 1.3, "def": 0.8}, ... }, # att/def multipliers (1.0 = average)
|
|
21
|
+
"groups": [ ["Germany","Scotland","Hungary","Switzerland"], ... ],
|
|
22
|
+
"advance_per_group": 2,
|
|
23
|
+
"bracket": "auto" # or omit; "auto" = random seed of qualifiers
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
python3 scripts/prediction-pool/poisson_sim.py teams.json --runs 20000 [--seed 1]
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import math
|
|
34
|
+
import random
|
|
35
|
+
import sys
|
|
36
|
+
from collections import defaultdict
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _poisson(rate: float, rng: random.Random) -> int:
|
|
41
|
+
"""Knuth's algorithm — stdlib only, no numpy."""
|
|
42
|
+
if rate <= 0:
|
|
43
|
+
return 0
|
|
44
|
+
L = math.exp(-rate)
|
|
45
|
+
k, p = 0, 1.0
|
|
46
|
+
while True:
|
|
47
|
+
k += 1
|
|
48
|
+
p *= rng.random()
|
|
49
|
+
if p <= L:
|
|
50
|
+
return k - 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _rates(home: str, away: str, teams: dict, base: float) -> tuple[float, float]:
|
|
54
|
+
h, a = teams.get(home, {}), teams.get(away, {})
|
|
55
|
+
lam_h = base * h.get("att", 1.0) * a.get("def", 1.0)
|
|
56
|
+
lam_a = base * a.get("att", 1.0) * h.get("def", 1.0)
|
|
57
|
+
return lam_h, lam_a
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _play(home: str, away: str, teams: dict, base: float, rng: random.Random,
|
|
61
|
+
allow_draw: bool = True) -> tuple[int, int]:
|
|
62
|
+
lam_h, lam_a = _rates(home, away, teams, base)
|
|
63
|
+
gh, ga = _poisson(lam_h, rng), _poisson(lam_a, rng)
|
|
64
|
+
if not allow_draw and gh == ga:
|
|
65
|
+
# extra-time / penalties proxy: edge to the stronger attack, else coin flip
|
|
66
|
+
if lam_h == lam_a:
|
|
67
|
+
return (gh + 1, ga) if rng.random() < 0.5 else (gh, ga + 1)
|
|
68
|
+
return (gh + 1, ga) if lam_h > lam_a else (gh, ga + 1)
|
|
69
|
+
return gh, ga
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _group_table(group: list[str], teams: dict, base: float, rng: random.Random) -> list[str]:
|
|
73
|
+
pts = defaultdict(int)
|
|
74
|
+
gd = defaultdict(int)
|
|
75
|
+
gf = defaultdict(int)
|
|
76
|
+
for i in range(len(group)):
|
|
77
|
+
for j in range(i + 1, len(group)):
|
|
78
|
+
gh, ga = _play(group[i], group[j], teams, base, rng)
|
|
79
|
+
gd[group[i]] += gh - ga
|
|
80
|
+
gd[group[j]] += ga - gh
|
|
81
|
+
gf[group[i]] += gh
|
|
82
|
+
gf[group[j]] += ga
|
|
83
|
+
if gh > ga:
|
|
84
|
+
pts[group[i]] += 3
|
|
85
|
+
elif ga > gh:
|
|
86
|
+
pts[group[j]] += 3
|
|
87
|
+
else:
|
|
88
|
+
pts[group[i]] += 1
|
|
89
|
+
pts[group[j]] += 1
|
|
90
|
+
# rank: points, then goal difference, then goals for, then random tiebreak
|
|
91
|
+
return sorted(group, key=lambda t: (pts[t], gd[t], gf[t], rng.random()), reverse=True)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _knockout(qualifiers: list[str], teams: dict, base: float, rng: random.Random) -> str:
|
|
95
|
+
field = qualifiers[:]
|
|
96
|
+
rng.shuffle(field)
|
|
97
|
+
# pad to a power of two with byes
|
|
98
|
+
while (len(field) & (len(field) - 1)) != 0:
|
|
99
|
+
field.append(None)
|
|
100
|
+
while len(field) > 1:
|
|
101
|
+
nxt = []
|
|
102
|
+
for i in range(0, len(field), 2):
|
|
103
|
+
a, b = field[i], field[i + 1]
|
|
104
|
+
if a is None:
|
|
105
|
+
nxt.append(b)
|
|
106
|
+
elif b is None:
|
|
107
|
+
nxt.append(a)
|
|
108
|
+
else:
|
|
109
|
+
gh, ga = _play(a, b, teams, base, rng, allow_draw=False)
|
|
110
|
+
nxt.append(a if gh > ga else b)
|
|
111
|
+
field = nxt
|
|
112
|
+
return field[0]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def simulate(cfg: dict, runs: int, seed: int | None) -> dict:
|
|
116
|
+
rng = random.Random(seed)
|
|
117
|
+
base = float(cfg.get("base_goals", 1.35))
|
|
118
|
+
teams = cfg["teams"]
|
|
119
|
+
groups = cfg.get("groups", [])
|
|
120
|
+
adv = int(cfg.get("advance_per_group", 2))
|
|
121
|
+
|
|
122
|
+
advanced = defaultdict(int)
|
|
123
|
+
champ = defaultdict(int)
|
|
124
|
+
for _ in range(runs):
|
|
125
|
+
qualifiers: list[str] = []
|
|
126
|
+
if groups:
|
|
127
|
+
for g in groups:
|
|
128
|
+
ranked = _group_table(g, teams, base, rng)
|
|
129
|
+
top = ranked[:adv]
|
|
130
|
+
qualifiers.extend(top)
|
|
131
|
+
for t in top:
|
|
132
|
+
advanced[t] += 1
|
|
133
|
+
else:
|
|
134
|
+
qualifiers = list(teams.keys())
|
|
135
|
+
winner = _knockout(qualifiers, teams, base, rng) if len(qualifiers) > 1 else (qualifiers or [None])[0]
|
|
136
|
+
if winner is not None:
|
|
137
|
+
champ[winner] += 1
|
|
138
|
+
|
|
139
|
+
def pct(d):
|
|
140
|
+
return {t: round(100 * c / runs, 2) for t, c in sorted(d.items(), key=lambda kv: -kv[1])}
|
|
141
|
+
|
|
142
|
+
return {"runs": runs, "seed": seed, "advance_pct": pct(advanced), "title_pct": pct(champ)}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main() -> int:
|
|
146
|
+
ap = argparse.ArgumentParser(description="Poisson tournament simulator (stdlib only).")
|
|
147
|
+
ap.add_argument("config", help="Path to teams/groups JSON.")
|
|
148
|
+
ap.add_argument("--runs", type=int, default=20000, help="Number of simulated tournaments.")
|
|
149
|
+
ap.add_argument("--seed", type=int, default=None, help="RNG seed for reproducibility.")
|
|
150
|
+
args = ap.parse_args()
|
|
151
|
+
|
|
152
|
+
cfg_path = Path(args.config)
|
|
153
|
+
if not cfg_path.is_file():
|
|
154
|
+
print(f"ERROR: config not found: {cfg_path}", file=sys.stderr)
|
|
155
|
+
return 2
|
|
156
|
+
cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
|
|
157
|
+
if "teams" not in cfg:
|
|
158
|
+
print("ERROR: config needs a 'teams' object.", file=sys.stderr)
|
|
159
|
+
return 2
|
|
160
|
+
|
|
161
|
+
result = simulate(cfg, args.runs, args.seed)
|
|
162
|
+
print(json.dumps(result, indent=2))
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
raise SystemExit(main())
|