@event4u/agent-config 5.6.1 → 5.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.
Files changed (102) hide show
  1. package/.agent-src/commands/cost-report.md +12 -7
  2. package/.agent-src/commands/prediction-pool.md +215 -0
  3. package/.agent-src/commands/set-cost-profile.md +8 -8
  4. package/.agent-src/commands/sync-agent-settings.md +2 -2
  5. package/.agent-src/presets/README.md +1 -1
  6. package/.agent-src/profiles/README.md +1 -1
  7. package/.agent-src/rules/non-destructive-by-default.md +2 -1
  8. package/.agent-src/skills/prediction-pool-optimizer/SKILL.md +196 -0
  9. package/.agent-src/skills/prediction-pool-optimizer/evals/triggers.json +18 -0
  10. package/.agent-src/skills/prediction-pool-optimizer/reference/ev-fixtures.md +80 -0
  11. package/.agent-src/templates/agent-settings.md +7 -7
  12. package/.agent-src/templates/agents/agent-project-settings.example.yml +2 -2
  13. package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +2 -1
  14. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +1 -1
  15. package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +9 -7
  16. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +9 -10
  17. package/.agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +17 -4
  18. package/.claude-plugin/marketplace.json +3 -1
  19. package/CHANGELOG.md +48 -0
  20. package/README.md +2 -2
  21. package/config/agent-settings.template.yml +11 -2
  22. package/config/discovery/packs.yml +11 -0
  23. package/config/discovery/workspaces.yml +1 -1
  24. package/config/profiles/balanced.ini +1 -1
  25. package/config/profiles/full.ini +1 -1
  26. package/config/profiles/minimal.ini +1 -1
  27. package/dist/discovery/deprecation-report.md +1 -1
  28. package/dist/discovery/discovery-manifest.json +80 -14
  29. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  30. package/dist/discovery/discovery-manifest.summary.md +3 -2
  31. package/dist/discovery/orphan-report.md +1 -1
  32. package/dist/discovery/packs.json +34 -3
  33. package/dist/discovery/trust-report.md +2 -2
  34. package/dist/discovery/workspaces.json +13 -4
  35. package/dist/mcp/registry-manifest.json +2 -2
  36. package/dist/server/io/substituteTemplate.js +3 -3
  37. package/dist/server/io/substituteTemplate.js.map +1 -1
  38. package/dist/server/routes/settings.js +2 -2
  39. package/dist/server/routes/settings.js.map +1 -1
  40. package/dist/server/schemas/settings.js +4 -2
  41. package/dist/server/schemas/settings.js.map +1 -1
  42. package/dist/ui/assets/{index-DVsyUMZe.js → index-5lFqAKL0.js} +2 -2
  43. package/dist/ui/assets/index-5lFqAKL0.js.map +1 -0
  44. package/dist/ui/index.html +1 -1
  45. package/docs/architecture/current-onboard-baseline.md +3 -3
  46. package/docs/architecture.md +2 -2
  47. package/docs/catalog.md +7 -5
  48. package/docs/contracts/adr-level-6-productization.md +1 -1
  49. package/docs/contracts/config-presets.md +2 -2
  50. package/docs/contracts/cost-profile-defaults.md +5 -5
  51. package/docs/contracts/discovery-manifest.schema.json +1 -1
  52. package/docs/contracts/explain-trace.schema.json +3 -3
  53. package/docs/contracts/memory-visibility-v1.md +15 -7
  54. package/docs/contracts/profile-system.md +2 -2
  55. package/docs/contracts/settings-api.md +3 -3
  56. package/docs/contracts/value-report-schema.md +14 -1
  57. package/docs/customization.md +21 -5
  58. package/docs/decisions/ADR-010-profile-pack-preset-boundary.md +11 -11
  59. package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +16 -2
  60. package/docs/decisions/ADR-034-per-skill-model-recommendation-transport.md +1 -1
  61. package/docs/decisions/ADR-036-global-install-browser-wizard-handoff.md +106 -0
  62. package/docs/decisions/ADR-037-cost-profile-untangle.md +117 -0
  63. package/docs/decisions/ADR-rule-kernel-and-router.md +1 -1
  64. package/docs/decisions/INDEX.md +2 -0
  65. package/docs/getting-started.md +2 -2
  66. package/docs/guidelines/agent-infra/layered-settings.md +2 -2
  67. package/docs/installation.md +3 -3
  68. package/docs/setup/mcp-client-config.md +1 -1
  69. package/docs/value.md +9 -7
  70. package/docs/wizard.md +1 -1
  71. package/package.json +1 -1
  72. package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
  73. package/scripts/_cli/cmd_explain.py +1 -1
  74. package/scripts/_cli/explain_last/inputs.py +11 -8
  75. package/scripts/_cli/explain_last/sections/inputs.py +1 -1
  76. package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
  77. package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
  78. package/scripts/_lib/agent_settings.py +2 -1
  79. package/scripts/_lib/value_ladder.py +99 -2
  80. package/scripts/_lib/value_report.py +30 -16
  81. package/scripts/ai_council/modes.py +1 -1
  82. package/scripts/audit_initial_context.py +16 -0
  83. package/scripts/check_skill_requires.py +143 -0
  84. package/scripts/condense.py +13 -2
  85. package/scripts/first-run.sh +11 -11
  86. package/scripts/install +14 -1
  87. package/scripts/install.py +127 -428
  88. package/scripts/install_anthropic_key.sh +1 -1
  89. package/scripts/install_openai_key.sh +1 -1
  90. package/scripts/lint_discovery_vocabulary.py +5 -5
  91. package/scripts/lint_value_dashboard.py +1 -1
  92. package/scripts/prediction-pool/adapters/_schema.md +42 -0
  93. package/scripts/prediction-pool/adapters/kicktipp.yml +23 -0
  94. package/scripts/prediction-pool/poisson_sim.py +167 -0
  95. package/scripts/render_value_md.py +1 -0
  96. package/scripts/schemas/agent-settings.schema.json +77 -0
  97. package/scripts/schemas/skill.schema.json +7 -0
  98. package/scripts/smoke_quickstart.py +4 -4
  99. package/scripts/sync_agent_settings.py +4 -2
  100. package/scripts/validate_agent_settings.py +120 -0
  101. package/templates/minimal/.agent-settings.yml +1 -1
  102. package/dist/ui/assets/index-DVsyUMZe.js.map +0 -1
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env python3
2
+ """Skill-composition graph gate (roadmap 3.4).
3
+
4
+ Validates the `requires_skills:` frontmatter field (the skill→skill
5
+ composition graph) against two invariants:
6
+
7
+ 1. **Referential integrity** — every `requires_skills` target names a real
8
+ skill in the suite.
9
+ 2. **Co-availability** — whenever a parent skill ships, every sub-skill its
10
+ body assumes must ship too. A sub-skill is co-available under a parent's
11
+ pack `P` iff one of the sub-skill's packs is in `{P}` ∪ the transitive
12
+ `requires_hint` closure of `P` (from `config/discovery/packs.yml`), or
13
+ the sub-skill is always-on (no pack). A parent with no pack (always-on)
14
+ may only require always-on sub-skills.
15
+
16
+ This is distinct from the ADR-015 artefact→pack `requires` field; this gate
17
+ operates on `requires_skills` (skill→skill) only.
18
+
19
+ Exit 0 = clean · 1 = at least one violation.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
27
+ sys.path.insert(0, str(Path(__file__).resolve().parent / "_lib"))
28
+
29
+ import yaml # noqa: E402
30
+
31
+ from _lib.agent_src import ROOT, iter_artefacts # noqa: E402
32
+ from validate_frontmatter import parse_frontmatter # noqa: E402
33
+
34
+ PACKS_YML = ROOT / "config" / "discovery" / "packs.yml"
35
+
36
+
37
+ def _load_pack_closure() -> dict[str, set[str]]:
38
+ """pack_id → transitive set of {self} ∪ requires_hint closure."""
39
+ raw = yaml.safe_load(PACKS_YML.read_text(encoding="utf-8")) or []
40
+ direct: dict[str, set[str]] = {}
41
+ for entry in raw:
42
+ pid = entry["id"]
43
+ direct[pid] = set(entry.get("requires_hint") or [])
44
+
45
+ closure: dict[str, set[str]] = {}
46
+
47
+ def resolve(pid: str, seen: set[str]) -> set[str]:
48
+ if pid in closure:
49
+ return closure[pid]
50
+ acc = {pid}
51
+ for dep in direct.get(pid, set()):
52
+ if dep in seen:
53
+ continue
54
+ acc |= resolve(dep, seen | {pid})
55
+ closure[pid] = acc
56
+ return acc
57
+
58
+ for pid in direct:
59
+ resolve(pid, set())
60
+ return closure
61
+
62
+
63
+ def _collect_skills() -> dict[str, dict]:
64
+ """skill_id (directory name) → {packs: set[str], requires_skills: list[str], path}."""
65
+ skills: dict[str, dict] = {}
66
+ for path in iter_artefacts("SKILL.md"):
67
+ # logical id = the skill's directory name
68
+ skill_id = path.parent.name
69
+ fm, _ = parse_frontmatter(path.read_text(encoding="utf-8"))
70
+ if fm is None:
71
+ continue
72
+ skills[skill_id] = {
73
+ "packs": set(fm.get("packs") or []),
74
+ "requires_skills": list(fm.get("requires_skills") or []),
75
+ "path": path.relative_to(ROOT).as_posix(),
76
+ }
77
+ return skills
78
+
79
+
80
+ def main() -> int:
81
+ closure = _load_pack_closure()
82
+ skills = _collect_skills()
83
+ errors: list[str] = []
84
+
85
+ for skill_id, info in sorted(skills.items()):
86
+ reqs = info["requires_skills"]
87
+ if not reqs:
88
+ continue
89
+ parent_packs: set[str] = info["packs"]
90
+ for req in reqs:
91
+ target = skills.get(req)
92
+ # (1) referential integrity
93
+ if target is None:
94
+ errors.append(
95
+ f"{info['path']}: requires_skills → unknown skill '{req}' "
96
+ f"(no skills/{req}/SKILL.md in the suite)."
97
+ )
98
+ continue
99
+ # (2) co-availability
100
+ req_packs: set[str] = target["packs"]
101
+ if not req_packs:
102
+ # always-on sub-skill is reachable from anywhere
103
+ continue
104
+ if not parent_packs:
105
+ # always-on parent may only require an always-on sub-skill
106
+ errors.append(
107
+ f"{info['path']}: always-on skill '{skill_id}' requires "
108
+ f"'{req}' which is pack-gated ({sorted(req_packs)}); a base "
109
+ f"install would ship '{skill_id}' without '{req}'."
110
+ )
111
+ continue
112
+ for p in sorted(parent_packs):
113
+ reachable = closure.get(p, {p})
114
+ if req_packs & reachable:
115
+ continue
116
+ hint = sorted(req_packs - reachable)
117
+ errors.append(
118
+ f"{info['path']}: skill '{skill_id}' (pack '{p}') requires "
119
+ f"'{req}' (pack {sorted(req_packs)}), but '{p}' does not reach "
120
+ f"it. Add requires_hint: {hint} to pack '{p}' in "
121
+ f"config/discovery/packs.yml, or move '{req}' into a reachable pack."
122
+ )
123
+
124
+ if errors:
125
+ print("❌ check_skill_requires: skill-composition graph has unmet edges:")
126
+ for e in errors:
127
+ print(f" 🔴 {e}")
128
+ print(
129
+ "\nEvery sub-skill a parent's body invokes must ship wherever the "
130
+ "parent ships. Declare the missing pack dependency or co-locate the skill."
131
+ )
132
+ return 1
133
+
134
+ n_edges = sum(len(i["requires_skills"]) for i in skills.values())
135
+ print(
136
+ f"✅ check_skill_requires: {n_edges} composition edge(s) across "
137
+ f"{sum(1 for i in skills.values() if i['requires_skills'])} skill(s) — all sub-skills co-available."
138
+ )
139
+ return 0
140
+
141
+
142
+ if __name__ == "__main__":
143
+ raise SystemExit(main())
@@ -770,6 +770,17 @@ def _parse_frontmatter(content: str) -> tuple[dict, str]:
770
770
  return meta if isinstance(meta, dict) else {}, body
771
771
 
772
772
 
773
+ def _yaml_scalar(value: str) -> str:
774
+ """Return a YAML-safe single-line scalar for a frontmatter value.
775
+
776
+ Descriptions can contain ``:``, ``#``, or quotes — characters that break
777
+ an unquoted YAML scalar (e.g. ``description: Hard Floor: ...`` is invalid
778
+ YAML). A JSON string is itself a valid YAML double-quoted scalar, so
779
+ ``json.dumps`` gives correct escaping for free.
780
+ """
781
+ return json.dumps(value, ensure_ascii=False)
782
+
783
+
773
784
  def _emit_cursor_mdc(source: Path, target: Path) -> None:
774
785
  """Write a Cursor `.mdc` file with Cursor-shaped frontmatter."""
775
786
  meta, body = _parse_frontmatter(source.read_text())
@@ -777,7 +788,7 @@ def _emit_cursor_mdc(source: Path, target: Path) -> None:
777
788
  always_apply = bool(meta.get("alwaysApply") or meta.get("type") == "always")
778
789
  lines = [
779
790
  "---",
780
- f"description: {description}",
791
+ f"description: {_yaml_scalar(description)}",
781
792
  "globs: ",
782
793
  f"alwaysApply: {'true' if always_apply else 'false'}",
783
794
  "---",
@@ -797,7 +808,7 @@ def _emit_windsurf_rule(source: Path, target: Path) -> None:
797
808
  lines = [
798
809
  "---",
799
810
  f"trigger: {trigger}",
800
- f"description: {description}",
811
+ f"description: {_yaml_scalar(description)}",
801
812
  "globs: ",
802
813
  "---",
803
814
  "",
@@ -9,14 +9,14 @@ echo "Agent Config — First Run"
9
9
  echo ""
10
10
 
11
11
  # --- Profile detection ---
12
- # The YAML format stores `cost_profile` as a top-level scalar:
13
- # cost_profile: minimal
12
+ # The YAML format stores `rule_loading_tier` as a top-level scalar:
13
+ # rule_loading_tier: minimal
14
14
  # It may be unquoted or double-quoted after migration. We strip both.
15
- read_cost_profile() {
15
+ read_rule_loading_tier() {
16
16
  local file="$1"
17
- grep -E '^cost_profile:' "$file" 2>/dev/null \
17
+ grep -E '^rule_loading_tier:' "$file" 2>/dev/null \
18
18
  | head -n1 \
19
- | sed -E 's/^cost_profile:[[:space:]]*//' \
19
+ | sed -E 's/^rule_loading_tier:[[:space:]]*//' \
20
20
  | sed -E 's/^"(.*)"$/\1/' \
21
21
  | sed -E "s/^'(.*)'\$/\\1/" \
22
22
  | tr -d '[:space:]'
@@ -25,16 +25,16 @@ read_cost_profile() {
25
25
  if [ -f "$SETTINGS_FILE" ]; then
26
26
  echo "✅ Found $SETTINGS_FILE"
27
27
  echo ""
28
- CURRENT_PROFILE=$(read_cost_profile "$SETTINGS_FILE" || true)
28
+ CURRENT_PROFILE=$(read_rule_loading_tier "$SETTINGS_FILE" || true)
29
29
 
30
30
  if [ -n "${CURRENT_PROFILE:-}" ]; then
31
- echo "Active cost_profile: $CURRENT_PROFILE"
31
+ echo "Active rule_loading_tier: $CURRENT_PROFILE"
32
32
  else
33
- echo "No cost_profile configured yet."
33
+ echo "No rule_loading_tier configured yet."
34
34
  echo ""
35
35
  echo "Recommended: add this to $SETTINGS_FILE:"
36
36
  echo ""
37
- echo " cost_profile: minimal"
37
+ echo " rule_loading_tier: minimal"
38
38
  fi
39
39
  elif [ -f "$LEGACY_SETTINGS_FILE" ]; then
40
40
  echo "⚠️ Found legacy $LEGACY_SETTINGS_FILE (key=value format)."
@@ -47,7 +47,7 @@ else
47
47
  echo ""
48
48
  echo "Create one with:"
49
49
  echo ""
50
- echo " cost_profile: minimal"
50
+ echo " rule_loading_tier: minimal"
51
51
  echo ""
52
52
  fi
53
53
 
@@ -97,7 +97,7 @@ echo " minimal rules, skills, commands only"
97
97
  echo " balanced + runtime dispatcher"
98
98
  echo " full + experimental read-only tool adapters"
99
99
  echo ""
100
- echo "Change profile: edit cost_profile: <name> in $SETTINGS_FILE"
100
+ echo "Change profile: edit rule_loading_tier: <name> in $SETTINGS_FILE"
101
101
  echo "Profile details: docs/customization.md"
102
102
  echo "Getting started: docs/getting-started.md"
103
103
  echo ""
package/scripts/install CHANGED
@@ -232,7 +232,20 @@ prompt_tools() {
232
232
  echo " ✅ Selected: $TOOLS"
233
233
  }
234
234
 
235
- if ! $MINIMAL && ! $TOOLS_EXPLICIT && ! $YES && ! $QUIET && ! $LIST_TOOLS && [[ -t 0 && -t 1 ]]; then
235
+ # When an interactive global install will hand off to the browser wizard,
236
+ # the wizard is the single tool-selection surface — skip the terminal
237
+ # picker so it does not pre-empt (and, via TOOLS_EXPLICIT, suppress) the
238
+ # GUI. Mirrors install.py::_wizard_should_launch minus the --no-ui flag,
239
+ # which the bash orchestrator never receives (it errors on unknown args).
240
+ # Headless paths (no TTY / CI / AGENT_CONFIG_NO_UI) still get the picker.
241
+ wizard_will_handle_tools=false
242
+ if $GLOBAL && [[ -t 0 && -t 1 && -z "${CI:-}" ]] \
243
+ && { [[ -z "${AGENT_CONFIG_NO_UI:-}" ]] || [[ "${AGENT_CONFIG_NO_UI:-}" == "0" ]]; }; then
244
+ wizard_will_handle_tools=true
245
+ fi
246
+
247
+ if ! $MINIMAL && ! $TOOLS_EXPLICIT && ! $YES && ! $QUIET && ! $LIST_TOOLS \
248
+ && [[ -t 0 && -t 1 ]] && ! $wizard_will_handle_tools; then
236
249
  prompt_tools
237
250
  TOOLS_EXPLICIT=true
238
251
  fi