@event4u/agent-config 2.10.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/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/settings.py +24 -6
- package/.agent-src/templates/scripts/work_engine/scoring/decision_engine.py +351 -0
- package/.claude-plugin/marketplace.json +3 -1
- package/CHANGELOG.md +37 -0
- package/README.md +37 -8
- package/config/agent-settings.template.yml +57 -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 +1 -0
- package/docs/contracts/file-ownership-matrix.md +1 -0
- package/docs/contracts/hook-architecture-v1.md +1 -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 +1 -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 +1 -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/package.json +1 -1
- package/scripts/check_beta_review_markers.py +127 -0
- package/scripts/check_release_trunk_sync.py +152 -0
- package/scripts/install.py +3 -3
- 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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
stability: beta
|
|
3
|
+
keep-beta-until: 2026-08-12
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR — Release-trunk sync: main fast-forwards on every tag
|
|
7
|
+
|
|
8
|
+
> **Status:** Decided · 2026-05-14
|
|
9
|
+
> **Context:** PR #43 feedback (Level-5/6 product rating) and PR #143
|
|
10
|
+
> revealed `main` lagging the latest tag by N skills + rules at multiple
|
|
11
|
+
> points across the 2.x cycle. External readers landing on `main`
|
|
12
|
+
> consistently saw stale README counts and missing skill catalogues
|
|
13
|
+
> relative to the npm/Packagist artefact.
|
|
14
|
+
> **Closes:** [Road to Productization](../../agents/roadmaps/road-to-productization.md) § P1.2.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
Every tagged release (`X.Y.Z`) **fast-forwards `main` to the tag's
|
|
19
|
+
commit as the final step of the release pipeline**. No exceptions. No
|
|
20
|
+
grace period.
|
|
21
|
+
|
|
22
|
+
The fast-forward is owned by [`scripts/release.py`](../../scripts/release.py)
|
|
23
|
+
and runs after the GitHub Release is published. The release pipeline
|
|
24
|
+
is **not green** until `main == <new-tag>` at the remote.
|
|
25
|
+
|
|
26
|
+
`main` is therefore a **moving stable trunk pointer**, not a feature
|
|
27
|
+
branch. External readers (README, AGENTS.md, marketplace metadata, npm
|
|
28
|
+
tarball provenance) reading `main` see the artefact that was last
|
|
29
|
+
published, not work-in-progress.
|
|
30
|
+
|
|
31
|
+
## Protocol
|
|
32
|
+
|
|
33
|
+
1. `scripts/release.py` cuts `release/X.Y.Z`, bumps version files,
|
|
34
|
+
opens a release PR against `main`, waits for CI, merges.
|
|
35
|
+
2. The merge commit on `main` becomes the tag's commit; the tag is
|
|
36
|
+
pushed.
|
|
37
|
+
3. `publish-npm.yml` and the marketplace flow trigger on the tag.
|
|
38
|
+
4. The release pipeline asserts `git rev-parse origin/main ==
|
|
39
|
+
git rev-parse refs/tags/X.Y.Z` before exit-0.
|
|
40
|
+
5. If a hotfix lands on `release/X.Y.Z` after step 1 but before step 4,
|
|
41
|
+
the FF still happens — release-branch commits are part of the
|
|
42
|
+
release, not a separate trunk.
|
|
43
|
+
|
|
44
|
+
### Why fast-forward, not merge
|
|
45
|
+
|
|
46
|
+
Fast-forward keeps `main` linear with the tag history. A merge-commit
|
|
47
|
+
on top of the tag would put `main` at a SHA that is **not** the tag's
|
|
48
|
+
SHA, re-introducing the exact divergence this contract closes.
|
|
49
|
+
|
|
50
|
+
If a fast-forward is impossible (force-push to `main`, divergent
|
|
51
|
+
history, abandoned release-prep), the pipeline **fails loudly**; the
|
|
52
|
+
operator either resets `main` manually with an audit trail or aborts
|
|
53
|
+
the release.
|
|
54
|
+
|
|
55
|
+
## CI Gate (P1.3)
|
|
56
|
+
|
|
57
|
+
[`scripts/check_release_trunk_sync.py`](../../scripts/check_release_trunk_sync.py)
|
|
58
|
+
runs on every `release/X.Y.Z` branch (detected by `git rev-parse
|
|
59
|
+
--abbrev-ref HEAD` matching `^release/\d+\.\d+\.\d+$`).
|
|
60
|
+
|
|
61
|
+
It enforces: **`main` is at most ONE tagged release behind the
|
|
62
|
+
release-prep branch's target version.**
|
|
63
|
+
|
|
64
|
+
- On `release/2.11.0`: `main` may be at `2.10.0` or `2.11.0`. `2.9.0`
|
|
65
|
+
or older → **hard fail**.
|
|
66
|
+
- On any other branch class (feature, fix, chore, docs, the agent's
|
|
67
|
+
own `feat/road-to-productization` branch): the check is a **no-op**
|
|
68
|
+
exit-0 — feature branches never trip the gate.
|
|
69
|
+
- Wired into `task ci` as `check-release-trunk-sync`. No warning-only
|
|
70
|
+
mode; the exit code is the gate.
|
|
71
|
+
|
|
72
|
+
### Bootstrap mode
|
|
73
|
+
|
|
74
|
+
When the repo state does not yet match the gate (transitional first
|
|
75
|
+
run after this contract lands), the check reads
|
|
76
|
+
`docs/contracts/release-trunk-sync.bootstrap` for an opt-out window
|
|
77
|
+
keyed by current version. The bootstrap file is purged at the next
|
|
78
|
+
release. Absence of the file = gate is live.
|
|
79
|
+
|
|
80
|
+
## Rollback
|
|
81
|
+
|
|
82
|
+
Revertible by removing `check-release-trunk-sync` from `Taskfile.yml`
|
|
83
|
+
and deleting `scripts/check_release_trunk_sync.py`. No state, no
|
|
84
|
+
schema, no migration. Branch-detection key (`release/X.Y.Z`) is
|
|
85
|
+
already used by `scripts/release.py` so removing this contract does
|
|
86
|
+
not orphan the convention.
|
|
87
|
+
|
|
88
|
+
## Risks
|
|
89
|
+
|
|
90
|
+
| # | Risk | Mitigation |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| 1 | Gate fires on feature branches mid-PR | Branch-name regex; non-`release/` branches no-op exit-0 |
|
|
93
|
+
| 2 | Hotfix release leaves `main` behind | FF runs **after** hotfix commits land on the release branch |
|
|
94
|
+
| 3 | Manual tag (no `scripts/release.py`) skips the FF | Out of scope of this contract — covered by `release-guard.yml` which fails on tag/version mismatch; manual tags already break the pipeline |
|
|
95
|
+
| 4 | Detached HEAD or shallow checkout breaks detection | Check gracefully exits-0 with a `::warning::` line when `git rev-parse --abbrev-ref HEAD == HEAD` (detached) |
|
|
96
|
+
|
|
97
|
+
## See also
|
|
98
|
+
|
|
99
|
+
- [`scripts/release.py`](../../scripts/release.py) — release pipeline owner.
|
|
100
|
+
- [`.github/workflows/release-guard.yml`](../../.github/workflows/release-guard.yml)
|
|
101
|
+
— tag/version-file integrity gate (orthogonal: this contract handles
|
|
102
|
+
trunk position, release-guard handles version-string integrity).
|
|
103
|
+
- [`agents/roadmaps/road-to-productization.md`](../../agents/roadmaps/road-to-productization.md)
|
|
104
|
+
§ Phase 1.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
stability: beta
|
|
3
|
+
keep-beta-until: 2026-08-12
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
# Rule-Interaction Matrix
|
|
@@ -99,6 +100,31 @@ junior (yields). For `complements`, ordering is documentary only.
|
|
|
99
100
|
in the rule files.
|
|
100
101
|
- Skill ↔ rule interactions — the matrix is rule-only. Skills are
|
|
101
102
|
invoked, not always-active.
|
|
103
|
+
- **Orchestration-layer surfaces** (AI Council, Memory, Work-Engine /
|
|
104
|
+
Decision-Engine): these are runtime systems, not `always`-rules.
|
|
105
|
+
Their interactions are governed by their own contracts and stay
|
|
106
|
+
out of this matrix by design — see "Out of scope" below.
|
|
107
|
+
|
|
108
|
+
## Out of scope — orchestration surfaces (Council × Memory × Work-Engine)
|
|
109
|
+
|
|
110
|
+
The matrix is **rule-only**. The orchestration layer is governed by
|
|
111
|
+
dedicated contracts; cross-referencing them here would duplicate the
|
|
112
|
+
source of truth and weaken it. Canonical contracts:
|
|
113
|
+
|
|
114
|
+
| Surface | Canonical contract |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Decision-Engine gates (`min_confidence`, `block_on_risk`, `require_memory_hits`, `on_block`) | [`decision-engine-gates.md`](decision-engine-gates.md) |
|
|
117
|
+
| Decision-trace shape (what the engine emits per phase) | [`decision-trace-v1.md`](decision-trace-v1.md) |
|
|
118
|
+
| Memory contract (entries, scopes, retention) | [`agent-memory-contract.md`](agent-memory-contract.md) |
|
|
119
|
+
| Memory visibility in the trace (`affected` keys) | [`memory-visibility-v1.md`](memory-visibility-v1.md) |
|
|
120
|
+
| AI-Council consultation flow | [`../skills/ai-council/SKILL.md`](../../.agent-src.uncompressed/skills/ai-council/SKILL.md) |
|
|
121
|
+
|
|
122
|
+
Where an `always`-rule **does** interact with one of these surfaces
|
|
123
|
+
(e.g. `non-destructive-by-default` gating a memory-driven action), the
|
|
124
|
+
gate lives in the rule and the precedence is captured in this matrix
|
|
125
|
+
as a rule-pair (the orchestration surface is the *occasion*, not a
|
|
126
|
+
participant). For Council ↔ Memory ↔ Work-Engine interactions among
|
|
127
|
+
themselves, the dedicated contracts above are authoritative.
|
|
102
128
|
|
|
103
129
|
## See also
|
|
104
130
|
|
package/docs/customization.md
CHANGED
|
@@ -139,7 +139,7 @@ Rules:
|
|
|
139
139
|
| Setting | Default | Description |
|
|
140
140
|
|---|---|---|
|
|
141
141
|
| `agent_config_version` | *(empty)* | Exact semver pin of the agent-config release (see above). Empty = unpinned. |
|
|
142
|
-
| `cost_profile` | `
|
|
142
|
+
| `cost_profile` | `balanced` | Token budget (`minimal`, `balanced`, `full`, `custom`) — rationale: [`docs/contracts/cost-profile-defaults.md`](contracts/cost-profile-defaults.md) |
|
|
143
143
|
| `personal.user_name` | *(empty)* | User's first name for personalized responses |
|
|
144
144
|
| `personal.minimal_output` | `true` | Suppress intermediate output |
|
|
145
145
|
| `personal.play_by_play` | `false` | Share intermediate findings during analysis |
|
package/docs/getting-started.md
CHANGED
|
@@ -123,9 +123,11 @@ The system supports four configuration profiles:
|
|
|
123
123
|
Set your profile in `.agent-settings.yml`:
|
|
124
124
|
|
|
125
125
|
```yaml
|
|
126
|
-
cost_profile:
|
|
126
|
+
cost_profile: balanced
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
`balanced` is the default — kernel + tier-1 auto-rules. Rationale:
|
|
130
|
+
[`docs/contracts/cost-profile-defaults.md`](contracts/cost-profile-defaults.md).
|
|
129
131
|
You can override any individual setting. See [Customization](customization.md) for details.
|
|
130
132
|
|
|
131
133
|
---
|
package/docs/installation.md
CHANGED
|
@@ -240,8 +240,8 @@ wrapper (`./agent-config`) can fall through to it when no
|
|
|
240
240
|
The orchestrator chains payload sync and bridge generation:
|
|
241
241
|
|
|
242
242
|
```bash
|
|
243
|
-
bash scripts/install # defaults to cost_profile=
|
|
244
|
-
bash scripts/install --profile=
|
|
243
|
+
bash scripts/install # defaults to cost_profile=balanced
|
|
244
|
+
bash scripts/install --profile=minimal
|
|
245
245
|
bash scripts/install --force # overwrite existing bridges
|
|
246
246
|
bash scripts/install --skip-bridges # payload only
|
|
247
247
|
bash scripts/install --skip-sync # bridges only
|
|
@@ -287,7 +287,7 @@ regardless of which AI tool they use.** No per-developer plugin installation nee
|
|
|
287
287
|
After initial setup, commit these files:
|
|
288
288
|
|
|
289
289
|
```
|
|
290
|
-
.agent-settings.yml ← shared profile (e.g., cost_profile:
|
|
290
|
+
.agent-settings.yml ← shared profile (e.g., cost_profile: balanced)
|
|
291
291
|
agents/installed-tools.lock ← AI bill of materials (ADR-008, Phase 3)
|
|
292
292
|
.augment/ ← rules, skills, commands (symlinks)
|
|
293
293
|
.cursor/rules/ ← Cursor rules (symlinks)
|
|
@@ -517,16 +517,18 @@ The system works immediately with sensible defaults. Optionally, create `.agent-
|
|
|
517
517
|
to choose a profile:
|
|
518
518
|
|
|
519
519
|
```yaml
|
|
520
|
-
cost_profile:
|
|
520
|
+
cost_profile: balanced
|
|
521
521
|
```
|
|
522
522
|
|
|
523
523
|
| Profile | What's active | For whom |
|
|
524
524
|
|---|---|---|
|
|
525
|
-
| `minimal`
|
|
525
|
+
| `minimal` | Kernel only — Iron-Law floor, zero router | Token-constrained agents |
|
|
526
526
|
| `balanced` | + Runtime dispatcher + shell handler | Most teams |
|
|
527
527
|
| `full` | + Tool adapters (GitHub, Jira) | Platform teams |
|
|
528
528
|
|
|
529
|
-
No profile configured = `
|
|
529
|
+
No profile configured = `balanced` behavior (default). Rationale:
|
|
530
|
+
[`docs/contracts/cost-profile-defaults.md`](contracts/cost-profile-defaults.md).
|
|
531
|
+
→ [Full profile details](customization.md)
|
|
530
532
|
|
|
531
533
|
---
|
|
532
534
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Beta-review-marker checker for `docs/contracts/`.
|
|
4
|
+
|
|
5
|
+
Every contract whose frontmatter declares `stability: beta` MUST carry
|
|
6
|
+
exactly one of the following frontmatter markers (per
|
|
7
|
+
`docs/contracts/STABILITY.md` § Beta-review markers, ratified in
|
|
8
|
+
`road-to-productization.md` § P5.4):
|
|
9
|
+
|
|
10
|
+
- `promote-to: stable`
|
|
11
|
+
- `keep-beta-until: YYYY-MM-DD` (max 90 days from the last review)
|
|
12
|
+
- `superseded-by: <contract-id>`
|
|
13
|
+
|
|
14
|
+
Exit codes: 0 = clean, 1 = violations found, 3 = internal error.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
python3 scripts/check_beta_review_markers.py
|
|
18
|
+
python3 scripts/check_beta_review_markers.py --json
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
from dataclasses import asdict, dataclass
|
|
28
|
+
from datetime import date, timedelta
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
32
|
+
CONTRACTS_DIR = Path("docs/contracts")
|
|
33
|
+
|
|
34
|
+
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
|
|
35
|
+
STABILITY_RE = re.compile(r"^stability:\s*(\w+)\s*$", re.MULTILINE)
|
|
36
|
+
PROMOTE_RE = re.compile(r"^promote-to:\s*stable\s*$", re.MULTILINE)
|
|
37
|
+
KEEP_RE = re.compile(r"^keep-beta-until:\s*(\d{4}-\d{2}-\d{2})\s*$", re.MULTILINE)
|
|
38
|
+
SUPERSEDED_RE = re.compile(r"^superseded-by:\s*\S+\s*$", re.MULTILINE)
|
|
39
|
+
|
|
40
|
+
MAX_REVIEW_WINDOW_DAYS = 90
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Violation:
|
|
45
|
+
file: str
|
|
46
|
+
reason: str
|
|
47
|
+
severity: str # "error" | "warning"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def read_frontmatter(path: Path) -> str | None:
|
|
51
|
+
if not path.exists():
|
|
52
|
+
return None
|
|
53
|
+
txt = path.read_text(encoding="utf-8")
|
|
54
|
+
m = FRONTMATTER_RE.match(txt)
|
|
55
|
+
return m.group(1) if m else None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_one(path: Path, today: date) -> list[Violation]:
|
|
59
|
+
fm = read_frontmatter(path)
|
|
60
|
+
if fm is None:
|
|
61
|
+
return []
|
|
62
|
+
sm = STABILITY_RE.search(fm)
|
|
63
|
+
if not sm or sm.group(1) != "beta":
|
|
64
|
+
return []
|
|
65
|
+
markers = [
|
|
66
|
+
("promote-to", bool(PROMOTE_RE.search(fm))),
|
|
67
|
+
("keep-beta-until", bool(KEEP_RE.search(fm))),
|
|
68
|
+
("superseded-by", bool(SUPERSEDED_RE.search(fm))),
|
|
69
|
+
]
|
|
70
|
+
set_markers = [name for name, present in markers if present]
|
|
71
|
+
rel = str(path.relative_to(ROOT))
|
|
72
|
+
if not set_markers:
|
|
73
|
+
return [Violation(
|
|
74
|
+
file=rel,
|
|
75
|
+
reason="stability=beta but no review marker; add one of "
|
|
76
|
+
"`promote-to: stable` | `keep-beta-until: <date>` | "
|
|
77
|
+
"`superseded-by: <id>` (see STABILITY.md § Beta-review markers)",
|
|
78
|
+
severity="error",
|
|
79
|
+
)]
|
|
80
|
+
if len(set_markers) > 1:
|
|
81
|
+
return [Violation(
|
|
82
|
+
file=rel,
|
|
83
|
+
reason=f"multiple beta-review markers set ({', '.join(set_markers)}); "
|
|
84
|
+
"exactly one is allowed",
|
|
85
|
+
severity="error",
|
|
86
|
+
)]
|
|
87
|
+
km = KEEP_RE.search(fm)
|
|
88
|
+
if km:
|
|
89
|
+
review_date = date.fromisoformat(km.group(1))
|
|
90
|
+
max_date = today + timedelta(days=MAX_REVIEW_WINDOW_DAYS)
|
|
91
|
+
if review_date > max_date:
|
|
92
|
+
return [Violation(
|
|
93
|
+
file=rel,
|
|
94
|
+
reason=f"keep-beta-until={review_date} exceeds the "
|
|
95
|
+
f"{MAX_REVIEW_WINDOW_DAYS}-day window (max: {max_date})",
|
|
96
|
+
severity="error",
|
|
97
|
+
)]
|
|
98
|
+
return []
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main() -> int:
|
|
102
|
+
ap = argparse.ArgumentParser()
|
|
103
|
+
ap.add_argument("--json", action="store_true", help="machine-readable output")
|
|
104
|
+
args = ap.parse_args()
|
|
105
|
+
today = date.today()
|
|
106
|
+
violations: list[Violation] = []
|
|
107
|
+
for p in sorted((ROOT / CONTRACTS_DIR).glob("*.md")):
|
|
108
|
+
violations.extend(check_one(p, today))
|
|
109
|
+
if args.json:
|
|
110
|
+
print(json.dumps({"violations": [asdict(v) for v in violations]}, indent=2))
|
|
111
|
+
else:
|
|
112
|
+
if not violations:
|
|
113
|
+
print("✅ All beta contracts carry a valid review marker.")
|
|
114
|
+
else:
|
|
115
|
+
for v in violations:
|
|
116
|
+
icon = "❌" if v.severity == "error" else "⚠️ "
|
|
117
|
+
print(f"{icon} {v.file}: {v.reason}")
|
|
118
|
+
print(f"\n{len(violations)} violation(s).")
|
|
119
|
+
return 1 if any(v.severity == "error" for v in violations) else 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
try:
|
|
124
|
+
sys.exit(main())
|
|
125
|
+
except Exception as exc: # pragma: no cover
|
|
126
|
+
print(f"internal error: {exc}", file=sys.stderr)
|
|
127
|
+
sys.exit(3)
|