@event4u/agent-config 2.9.0 → 2.11.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/agents.md +1 -0
- package/.agent-src/commands/challenge-me.md +1 -0
- package/.agent-src/commands/chat-history.md +1 -0
- package/.agent-src/commands/context.md +1 -0
- package/.agent-src/commands/council.md +1 -0
- package/.agent-src/commands/feature.md +1 -0
- package/.agent-src/commands/fix.md +1 -0
- package/.agent-src/commands/grill-me.md +1 -0
- package/.agent-src/commands/judge.md +1 -0
- package/.agent-src/commands/memory.md +1 -0
- package/.agent-src/commands/module.md +1 -0
- package/.agent-src/commands/onboard.md +32 -4
- package/.agent-src/commands/optimize.md +1 -0
- package/.agent-src/commands/override.md +1 -0
- package/.agent-src/commands/roadmap.md +1 -0
- package/.agent-src/commands/tests.md +1 -0
- package/.agent-src/rules/no-roadmap-references.md +19 -0
- package/.agent-src/skills/nextjs-patterns/SKILL.md +203 -0
- package/.agent-src/skills/symfony-workflow/SKILL.md +173 -0
- package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +4 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +3 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.py +162 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +32 -3
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +24 -6
- package/.agent-src/templates/scripts/work_engine/scoring/decision_engine.py +351 -0
- package/.agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +147 -1
- package/.claude-plugin/marketplace.json +3 -1
- package/CHANGELOG.md +65 -0
- package/README.md +66 -17
- package/config/agent-settings.template.yml +85 -0
- package/docs/architecture.md +1 -1
- package/docs/contracts/STABILITY.md +16 -0
- package/docs/contracts/adr-chat-history-split.md +1 -0
- package/docs/contracts/adr-forecast-construction-shape.md +1 -0
- package/docs/contracts/adr-gtm-context-spine.md +1 -0
- package/docs/contracts/adr-level-6-productization.md +147 -0
- package/docs/contracts/adr-settings-sync-engine.md +1 -0
- package/docs/contracts/adr-wing4-context-spine.md +1 -0
- package/docs/contracts/agent-memory-contract.md +1 -0
- package/docs/contracts/agents-md-tech-stack.md +1 -0
- package/docs/contracts/audit-log-v1.md +1 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/command-surface-tiers.md +1 -0
- package/docs/contracts/context-paths.md +1 -0
- package/docs/contracts/cost-profile-defaults.md +105 -0
- package/docs/contracts/cross-wing-handoff.md +1 -0
- package/docs/contracts/decision-engine-gates.md +115 -0
- package/docs/contracts/decision-trace-v1.md +31 -0
- package/docs/contracts/file-ownership-matrix.md +1 -0
- package/docs/contracts/hook-architecture-v1.md +47 -0
- package/docs/contracts/implement-ticket-flow.md +1 -0
- package/docs/contracts/installed-tools-lockfile.md +1 -0
- package/docs/contracts/kernel-membership.md +1 -0
- package/docs/contracts/linear-ai-rules-inclusion.md +1 -0
- package/docs/contracts/linear-ai-three-layers.md +1 -0
- package/docs/contracts/linter-structural-model.md +1 -0
- package/docs/contracts/load-context-budget-model.md +1 -0
- package/docs/contracts/load-context-schema.md +1 -0
- package/docs/contracts/memory-visibility-v1.md +34 -0
- package/docs/contracts/one-off-script-lifecycle.md +1 -0
- package/docs/contracts/orchestration-dsl-v1.md +1 -0
- package/docs/contracts/package-self-orientation.md +1 -0
- package/docs/contracts/persona-schema.md +1 -0
- package/docs/contracts/release-trunk-sync.md +104 -0
- package/docs/contracts/roadmap-complexity-standard.md +1 -0
- package/docs/contracts/rule-classification.md +1 -0
- package/docs/contracts/rule-interactions.md +26 -0
- package/docs/contracts/rule-priority-hierarchy.md +1 -0
- package/docs/contracts/rule-router.md +1 -0
- package/docs/contracts/settings-sync-yaml-subset.md +139 -0
- package/docs/contracts/skill-domains.md +1 -0
- package/docs/contracts/tier-3-contrib-plugin.md +1 -0
- package/docs/contracts/ui-stack-extension.md +1 -0
- package/docs/contracts/ui-track-flow.md +1 -0
- package/docs/customization.md +1 -1
- package/docs/getting-started.md +3 -1
- package/docs/installation.md +8 -6
- package/docs/readme-split-plan.md +102 -0
- package/package.json +1 -1
- package/scripts/_cli/cmd_settings_check.py +171 -0
- package/scripts/agent-config +40 -0
- package/scripts/chat_history.py +19 -0
- package/scripts/check_beta_review_markers.py +127 -0
- package/scripts/check_council_references.py +46 -5
- package/scripts/check_release_trunk_sync.py +152 -0
- package/scripts/hooks/dispatch_hook.py +5 -1
- package/scripts/hooks/replay_hook.py +144 -0
- package/scripts/hooks/state_io.py +24 -1
- package/scripts/hooks_doctor.py +184 -0
- package/scripts/install.py +3 -3
- package/scripts/lint_hook_concern_budget.py +203 -0
- package/scripts/roadmap_progress_hook.py +11 -0
- package/scripts/schemas/command.schema.json +5 -0
- package/scripts/skill_linter.py +11 -2
- package/scripts/smoke_quickstart.py +134 -0
- package/scripts/validate_decision_engine.py +124 -0
package/scripts/skill_linter.py
CHANGED
|
@@ -2233,17 +2233,26 @@ def lint_type_boundaries(path: Path, text: str, artifact_type: str) -> List[Issu
|
|
|
2233
2233
|
# Check frontmatter skills field
|
|
2234
2234
|
frontmatter = extract_frontmatter(text)
|
|
2235
2235
|
has_skills_field = False
|
|
2236
|
+
# Commands tagged `type: orchestrator` aggregate other commands /
|
|
2237
|
+
# routers — they intentionally do not declare a `skills:` list and
|
|
2238
|
+
# are exempt from the no-skill-reference check. The tag is the
|
|
2239
|
+
# contract; no hard-coded path list.
|
|
2240
|
+
is_orchestrator = False
|
|
2236
2241
|
if frontmatter:
|
|
2237
2242
|
skills_match = re.search(r'skills:\s*\[(.+)\]', frontmatter)
|
|
2238
2243
|
has_skills_field = bool(skills_match and skills_match.group(1).strip())
|
|
2244
|
+
type_match = re.search(r'^type:\s*[\'"]?orchestrator[\'"]?\s*$',
|
|
2245
|
+
frontmatter, re.MULTILINE)
|
|
2246
|
+
is_orchestrator = bool(type_match)
|
|
2239
2247
|
|
|
2240
2248
|
# Also check body for skill references
|
|
2241
2249
|
has_skill_ref = bool(re.search(r'skill|SKILL\.md', text))
|
|
2242
2250
|
|
|
2243
|
-
if not has_skills_field and not has_skill_ref:
|
|
2251
|
+
if not has_skills_field and not has_skill_ref and not is_orchestrator:
|
|
2244
2252
|
issues.append(Issue("warning", "command_missing_skill_references",
|
|
2245
2253
|
"Command does not reference any skills — "
|
|
2246
|
-
"commands should orchestrate skills, not contain domain logic"
|
|
2254
|
+
"commands should orchestrate skills, not contain domain logic "
|
|
2255
|
+
"(use `type: orchestrator` in frontmatter to exempt routers)"))
|
|
2247
2256
|
|
|
2248
2257
|
# --- Skill: validation should be concrete, not vague ---
|
|
2249
2258
|
if artifact_type == "skill":
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Structural smoke-test for the README Quickstart path.
|
|
3
|
+
|
|
4
|
+
Verifies the 3-step Quickstart from a fresh-project perspective:
|
|
5
|
+
|
|
6
|
+
1. `scripts/install.py --project <tmpdir>` produces a usable
|
|
7
|
+
`.agent-settings.yml` with the documented default `cost_profile`.
|
|
8
|
+
2. The decision_engine block (P2.x of road-to-productization) parses
|
|
9
|
+
cleanly through the same engine parser the runtime uses.
|
|
10
|
+
3. The work-engine state-file format (`agents/state/<id>.json`) is
|
|
11
|
+
emit-ready — schema for `decision_result` matches the contract.
|
|
12
|
+
|
|
13
|
+
What it does NOT do:
|
|
14
|
+
- Invoke a real LLM agent (CI doesn't run a model). The end-to-end
|
|
15
|
+
`/onboard → /work → decision_result` chain still requires the host
|
|
16
|
+
agent. This smoke test asserts the *mechanics* the agent depends
|
|
17
|
+
on, so a Quickstart break is caught before the agent ever runs.
|
|
18
|
+
|
|
19
|
+
Exit codes: 0 = green; 1 = one or more checks failed; 2 = setup error.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
import tempfile
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
30
|
+
INSTALLER = ROOT / "scripts" / "install.py"
|
|
31
|
+
TEMPLATE = ROOT / "config" / "agent-settings.template.yml"
|
|
32
|
+
|
|
33
|
+
EXPECTED_DEFAULT_PROFILE = "balanced"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _fail(msg: str) -> int:
|
|
37
|
+
print(f"::error::{msg}", file=sys.stderr)
|
|
38
|
+
return 1
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _check_installer_runs(tmpdir: Path) -> tuple[int, Path | None]:
|
|
42
|
+
"""Step 1 — run installer against a fresh tmpdir."""
|
|
43
|
+
cmd = [
|
|
44
|
+
sys.executable,
|
|
45
|
+
str(INSTALLER),
|
|
46
|
+
"--project",
|
|
47
|
+
str(tmpdir),
|
|
48
|
+
"--package",
|
|
49
|
+
str(ROOT),
|
|
50
|
+
"--skip-bridges",
|
|
51
|
+
]
|
|
52
|
+
try:
|
|
53
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
54
|
+
except subprocess.TimeoutExpired:
|
|
55
|
+
return _fail("installer timed out after 60s"), None
|
|
56
|
+
if result.returncode != 0:
|
|
57
|
+
return (
|
|
58
|
+
_fail(f"installer exited {result.returncode}\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}"),
|
|
59
|
+
None,
|
|
60
|
+
)
|
|
61
|
+
settings = tmpdir / ".agent-settings.yml"
|
|
62
|
+
if not settings.exists():
|
|
63
|
+
return _fail(".agent-settings.yml not written by installer"), None
|
|
64
|
+
return 0, settings
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _check_default_profile(settings: Path) -> int:
|
|
68
|
+
"""Step 2 — assert default cost_profile matches the contract."""
|
|
69
|
+
import yaml
|
|
70
|
+
|
|
71
|
+
parsed = yaml.safe_load(settings.read_text(encoding="utf-8"))
|
|
72
|
+
if not isinstance(parsed, dict):
|
|
73
|
+
return _fail(f"{settings.name}: top-level is not a YAML mapping")
|
|
74
|
+
profile = parsed.get("cost_profile")
|
|
75
|
+
if profile != EXPECTED_DEFAULT_PROFILE:
|
|
76
|
+
return _fail(
|
|
77
|
+
f"cost_profile drift: docs/contracts/cost-profile-defaults.md "
|
|
78
|
+
f"declares '{EXPECTED_DEFAULT_PROFILE}', settings has '{profile!r}'"
|
|
79
|
+
)
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _check_decision_engine_block(settings: Path) -> int:
|
|
84
|
+
"""Step 3 — decision_engine block parses through the engine parser."""
|
|
85
|
+
sys.path.insert(0, str(ROOT / ".agent-src.uncompressed" / "templates" / "scripts"))
|
|
86
|
+
try:
|
|
87
|
+
from work_engine.scoring.decision_engine import ( # type: ignore[import-not-found]
|
|
88
|
+
DecisionEngineSettings,
|
|
89
|
+
parse as parse_decision_engine,
|
|
90
|
+
)
|
|
91
|
+
except ImportError as exc:
|
|
92
|
+
return _fail(f"decision_engine module not importable: {exc}")
|
|
93
|
+
|
|
94
|
+
import yaml
|
|
95
|
+
|
|
96
|
+
parsed = yaml.safe_load(settings.read_text(encoding="utf-8"))
|
|
97
|
+
block = parsed.get("decision_engine") if isinstance(parsed, dict) else None
|
|
98
|
+
try:
|
|
99
|
+
settings_obj = parse_decision_engine(block)
|
|
100
|
+
except Exception as exc: # noqa: BLE001 — surface the schema error
|
|
101
|
+
return _fail(f"decision_engine block rejected by parser: {exc}")
|
|
102
|
+
if not isinstance(settings_obj, DecisionEngineSettings):
|
|
103
|
+
return _fail("parser returned non-DecisionEngineSettings instance")
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> int:
|
|
108
|
+
if not INSTALLER.exists():
|
|
109
|
+
print(f"::error::installer not found at {INSTALLER}", file=sys.stderr)
|
|
110
|
+
return 2
|
|
111
|
+
if not TEMPLATE.exists():
|
|
112
|
+
print(f"::error::template not found at {TEMPLATE}", file=sys.stderr)
|
|
113
|
+
return 2
|
|
114
|
+
|
|
115
|
+
failures = 0
|
|
116
|
+
tmpdir = Path(tempfile.mkdtemp(prefix="agent-config-quickstart-"))
|
|
117
|
+
try:
|
|
118
|
+
rc, settings = _check_installer_runs(tmpdir)
|
|
119
|
+
failures += rc
|
|
120
|
+
if settings is not None:
|
|
121
|
+
failures += _check_default_profile(settings)
|
|
122
|
+
failures += _check_decision_engine_block(settings)
|
|
123
|
+
finally:
|
|
124
|
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
125
|
+
|
|
126
|
+
if failures:
|
|
127
|
+
print(f"\n❌ smoke-quickstart: {failures} check(s) failed", file=sys.stderr)
|
|
128
|
+
return 1
|
|
129
|
+
print("✅ smoke-quickstart: install → settings → decision_engine green")
|
|
130
|
+
return 0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
sys.exit(main())
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Decision-engine settings validator (road-to-productization P2).
|
|
4
|
+
|
|
5
|
+
Walks every ``agent-settings.yml`` / ``agent-settings.template.yml``
|
|
6
|
+
under the repo, parses any ``decision_engine`` block via the canonical
|
|
7
|
+
``work_engine.scoring.decision_engine.parse`` schema, and surfaces:
|
|
8
|
+
|
|
9
|
+
- hard errors → exit 1 (unknown keys, invalid enum values, bad types).
|
|
10
|
+
- warnings → exit 0 with a ``::warning::`` line per finding
|
|
11
|
+
(gates active but ``hooks.enabled`` is false → gates won't fire).
|
|
12
|
+
|
|
13
|
+
Contract: ``docs/contracts/decision-engine-gates.md``. Wired into
|
|
14
|
+
``task ci`` via ``taskfiles/ci-fast.yml`` so configuration drift is
|
|
15
|
+
caught before a Decision Engine surprise lands in main.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import yaml
|
|
25
|
+
except ImportError: # pragma: no cover — bootstrap guard
|
|
26
|
+
print("::error::PyYAML not installed; cannot validate decision_engine block")
|
|
27
|
+
sys.exit(3)
|
|
28
|
+
|
|
29
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
30
|
+
TEMPLATE_SCRIPTS = REPO_ROOT / ".agent-src.uncompressed" / "templates" / "scripts"
|
|
31
|
+
if str(TEMPLATE_SCRIPTS) not in sys.path:
|
|
32
|
+
sys.path.insert(0, str(TEMPLATE_SCRIPTS))
|
|
33
|
+
|
|
34
|
+
from work_engine.scoring.decision_engine import ( # noqa: E402
|
|
35
|
+
DecisionEngineConfigError,
|
|
36
|
+
parse,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Files we always validate, even if they don't exist (template is
|
|
40
|
+
# canonical — its absence is itself a regression).
|
|
41
|
+
TEMPLATE_PATH = REPO_ROOT / "config" / "agent-settings.template.yml"
|
|
42
|
+
# Project-level overrides developers may have on disk locally.
|
|
43
|
+
LOCAL_PATHS = [REPO_ROOT / ".agent-settings.yml"]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _load_yaml(path: Path) -> dict | None:
|
|
47
|
+
if not path.is_file():
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
51
|
+
except yaml.YAMLError as exc:
|
|
52
|
+
print(f"::error file={path}::malformed YAML: {exc}")
|
|
53
|
+
return {}
|
|
54
|
+
if raw is None:
|
|
55
|
+
return {}
|
|
56
|
+
if not isinstance(raw, dict):
|
|
57
|
+
print(f"::error file={path}::top-level must be a mapping")
|
|
58
|
+
return {}
|
|
59
|
+
return raw
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _validate(path: Path, doc: dict) -> tuple[int, int]:
|
|
63
|
+
"""Return ``(errors, warnings)`` counts for ``doc``."""
|
|
64
|
+
errors = 0
|
|
65
|
+
warnings = 0
|
|
66
|
+
block = doc.get("decision_engine")
|
|
67
|
+
if block is None:
|
|
68
|
+
return 0, 0
|
|
69
|
+
try:
|
|
70
|
+
settings = parse(block)
|
|
71
|
+
except DecisionEngineConfigError as exc:
|
|
72
|
+
rel = path.relative_to(REPO_ROOT)
|
|
73
|
+
print(f"::error file={rel}::decision_engine: {exc}")
|
|
74
|
+
return 1, 0
|
|
75
|
+
if settings.any_gate_active:
|
|
76
|
+
hooks_block = doc.get("hooks") or {}
|
|
77
|
+
if isinstance(hooks_block, dict) and hooks_block.get("enabled") is False:
|
|
78
|
+
rel = path.relative_to(REPO_ROOT)
|
|
79
|
+
print(
|
|
80
|
+
f"::warning file={rel}::decision_engine gates configured "
|
|
81
|
+
"(min_confidence/block_on_risk/require_memory_hits) but "
|
|
82
|
+
"hooks.enabled=false — gates will not fire. Either enable "
|
|
83
|
+
"hooks or remove the gate keys."
|
|
84
|
+
)
|
|
85
|
+
warnings += 1
|
|
86
|
+
return errors, warnings
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> int:
|
|
90
|
+
total_errors = 0
|
|
91
|
+
total_warnings = 0
|
|
92
|
+
paths: list[Path] = []
|
|
93
|
+
if TEMPLATE_PATH.is_file():
|
|
94
|
+
paths.append(TEMPLATE_PATH)
|
|
95
|
+
else:
|
|
96
|
+
print(f"::error file={TEMPLATE_PATH}::template missing")
|
|
97
|
+
return 1
|
|
98
|
+
for candidate in LOCAL_PATHS:
|
|
99
|
+
if candidate.is_file():
|
|
100
|
+
paths.append(candidate)
|
|
101
|
+
for path in paths:
|
|
102
|
+
doc = _load_yaml(path)
|
|
103
|
+
if doc is None:
|
|
104
|
+
continue
|
|
105
|
+
errors, warnings = _validate(path, doc)
|
|
106
|
+
total_errors += errors
|
|
107
|
+
total_warnings += warnings
|
|
108
|
+
if total_errors:
|
|
109
|
+
return 1
|
|
110
|
+
if total_warnings:
|
|
111
|
+
# Warnings already printed; CI treats exit 0 + ::warning:: as
|
|
112
|
+
# green-with-note. Surface a summary for human readers.
|
|
113
|
+
print(
|
|
114
|
+
f"decision_engine: {total_warnings} warning(s); see ::warning:: lines above"
|
|
115
|
+
)
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
try:
|
|
121
|
+
sys.exit(main())
|
|
122
|
+
except Exception as exc: # noqa: BLE001
|
|
123
|
+
print(f"::error::validate_decision_engine internal error: {exc}")
|
|
124
|
+
sys.exit(3)
|