@event4u/agent-config 1.39.0 → 1.41.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/orchestrate.md +123 -0
- package/.agent-src/commands/sync-gitignore/fix.md +135 -0
- package/.agent-src/commands/sync-gitignore.md +31 -5
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +30 -2
- package/.agent-src/skills/subagent-orchestration/SKILL.md +9 -0
- package/.agent-src/skills/using-git-worktrees/SKILL.md +25 -0
- package/.agent-src/templates/agent-settings.md +9 -0
- package/.agent-src/templates/scripts/work_engine/orchestration.py +168 -0
- package/.claude-plugin/marketplace.json +3 -1
- package/CHANGELOG.md +75 -0
- package/README.md +52 -26
- package/bin/install.php +13 -6
- package/config/agent-settings.template.yml +21 -0
- package/docs/DISTRIBUTION_CHECKLIST.md +169 -0
- package/docs/architecture.md +1 -1
- package/docs/catalog.md +5 -3
- package/docs/contracts/audit-log-v1.md +142 -0
- package/docs/contracts/command-clusters.md +2 -0
- package/docs/contracts/file-ownership-matrix.json +47 -0
- package/docs/contracts/mcp-discovery-phase-notice.md +56 -0
- package/docs/contracts/mcp-tool-stub-envelope.md +78 -0
- package/docs/contracts/orchestration-dsl-v1.md +152 -0
- package/docs/getting-started.md +1 -1
- package/docs/installation.md +132 -0
- package/docs/setup/mcp-client-config.md +94 -13
- package/docs/setup/mcp-cloud-setup.md +32 -1
- package/docs/setup/per-ide/aider.md +48 -0
- package/docs/setup/per-ide/claude-code.md +108 -0
- package/docs/setup/per-ide/claude-desktop.md +173 -0
- package/docs/setup/per-ide/cline.md +43 -0
- package/docs/setup/per-ide/codex.md +46 -0
- package/docs/setup/per-ide/copilot.md +80 -0
- package/docs/setup/per-ide/cursor.md +125 -0
- package/docs/setup/per-ide/gemini-cli.md +45 -0
- package/docs/setup/per-ide/windsurf.md +120 -0
- package/package.json +1 -1
- package/scripts/_lib/script_output.py +15 -11
- package/scripts/ai_council/session.py +14 -8
- package/scripts/chat_history.py +29 -53
- package/scripts/command_suggester/settings.py +15 -13
- package/scripts/compile_router.py +13 -9
- package/scripts/compress.py +175 -20
- package/scripts/council_cli.py +9 -3
- package/scripts/extract_audit_patterns.py +202 -0
- package/scripts/install +156 -1
- package/scripts/install.py +270 -10
- package/scripts/install.sh +52 -7
- package/scripts/lint_orchestration_dsl.py +214 -0
- package/scripts/mcp_parity_smoke.py +20 -2
- package/scripts/mcp_server/catalog.py +125 -0
- package/scripts/mcp_server/consumer_tool_catalog.json +275 -0
- package/scripts/mcp_server/telemetry.py +128 -0
- package/scripts/mcp_server/tools.py +474 -15
- package/scripts/mcp_telemetry_health.py +214 -0
- package/scripts/mcp_telemetry_query.py +203 -0
- package/scripts/mcp_telemetry_store.py +211 -0
- package/scripts/memory_signal.py +12 -10
- package/scripts/pack_mcp_content.py +18 -4
- package/scripts/skill_linter.py +9 -0
- package/scripts/sync_gitignore.py +56 -1
- package/templates/claude_desktop_config.json.template +22 -0
- package/templates/cursor-rule.mdc.j2 +7 -0
- package/templates/global-install-manifest.yml +91 -0
- package/templates/marketing-copy.yml +64 -0
- package/templates/windsurf-rule.md.j2 +7 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# GitHub Copilot Setup
|
|
2
|
+
|
|
3
|
+
GitHub Copilot Chat (VS Code, JetBrains, Neovim, `gh copilot` CLI)
|
|
4
|
+
reads `.github/copilot-instructions.md` for project-level guidance and
|
|
5
|
+
falls back to `AGENTS.md` where supported.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- GitHub Copilot subscription (Individual, Business, or Enterprise).
|
|
10
|
+
- Copilot Chat enabled in your IDE.
|
|
11
|
+
- Node.js ≥ 18 for the install entrypoints.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @event4u/create-agent-config init --tools=copilot
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Populates:
|
|
20
|
+
|
|
21
|
+
- `.github/copilot-instructions.md` — Copilot's project-level prompt
|
|
22
|
+
- `AGENTS.md` — canonical agent self-orientation
|
|
23
|
+
- `.agent-settings.yml` — per-project knobs
|
|
24
|
+
|
|
25
|
+
The package keeps `.github/copilot-instructions.md` deliberately thin
|
|
26
|
+
(it points back to `AGENTS.md`) so all surfaces share a single source
|
|
27
|
+
of truth.
|
|
28
|
+
|
|
29
|
+
## VS Code Copilot Chat
|
|
30
|
+
|
|
31
|
+
Auto-loads `.github/copilot-instructions.md` once you reload the VS
|
|
32
|
+
Code window after install. Verify in the Copilot Chat panel —
|
|
33
|
+
*"What is this repo?"* should answer using the AGENTS.md emergency
|
|
34
|
+
triage block.
|
|
35
|
+
|
|
36
|
+
## JetBrains Copilot
|
|
37
|
+
|
|
38
|
+
JetBrains Copilot 1.5+ reads the same `.github/copilot-instructions.md`
|
|
39
|
+
file. No extra steps; reload the project after install.
|
|
40
|
+
|
|
41
|
+
## Neovim Copilot
|
|
42
|
+
|
|
43
|
+
`copilot.lua` and `CopilotChat.nvim` honor
|
|
44
|
+
`.github/copilot-instructions.md`. No extra config needed.
|
|
45
|
+
|
|
46
|
+
## `gh copilot` CLI
|
|
47
|
+
|
|
48
|
+
The `gh copilot` plugin (`gh extension install github/gh-copilot`)
|
|
49
|
+
reads the repo context including `AGENTS.md` and
|
|
50
|
+
`.github/copilot-instructions.md` when invoked from the repo root.
|
|
51
|
+
|
|
52
|
+
## Suppressing Copilot PR review noise
|
|
53
|
+
|
|
54
|
+
Copilot's PR auto-review can flag the package's own kernel rules as
|
|
55
|
+
"unusual phrasing". The package ships a Copilot-suppression rule
|
|
56
|
+
([`augment-portability`](../../../.augment/rules/augment-portability.md))
|
|
57
|
+
that documents this trade-off.
|
|
58
|
+
|
|
59
|
+
## Verification
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
test -f .github/copilot-instructions.md
|
|
63
|
+
test -f AGENTS.md
|
|
64
|
+
gh copilot --version # if you want CLI plugin
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Troubleshooting
|
|
68
|
+
|
|
69
|
+
| Symptom | Fix |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Copilot ignores the file | Reload the IDE window after install. |
|
|
72
|
+
| File missing after install | Re-run `npx @event4u/create-agent-config init --tools=copilot`. |
|
|
73
|
+
| Copilot PR review too noisy | See the `copilot-config` skill for suppression patterns. |
|
|
74
|
+
|
|
75
|
+
## Cross-references
|
|
76
|
+
|
|
77
|
+
- [`AGENTS.md`](../../../AGENTS.md) — canonical agent self-orientation.
|
|
78
|
+
- [`.augment/skills/copilot-config/SKILL.md`](../../../.augment/skills/copilot-config/SKILL.md)
|
|
79
|
+
— tuning Copilot output and suppressing review noise.
|
|
80
|
+
- [`docs/installation.md`](../../installation.md) — install matrix index.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Cursor Setup
|
|
2
|
+
|
|
3
|
+
Cursor reads two rule formats:
|
|
4
|
+
|
|
5
|
+
- **Modern (`.mdc`)** — `.cursor/rules/<rule>.mdc` with YAML frontmatter
|
|
6
|
+
(`description`, `globs`, `alwaysApply`). Preferred for any 2025+
|
|
7
|
+
Cursor build.
|
|
8
|
+
- **Legacy (`.cursorrules`)** — single-file aggregate at the repo root.
|
|
9
|
+
Still read by older Cursor versions; the package keeps it for
|
|
10
|
+
backward compatibility.
|
|
11
|
+
|
|
12
|
+
The package ships **both** so you don't have to pick.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- Cursor 0.45+ (any 2025/2026 build): <https://cursor.com>.
|
|
17
|
+
- Node.js ≥ 18.
|
|
18
|
+
|
|
19
|
+
## Project install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @event4u/create-agent-config init --tools=cursor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This populates:
|
|
26
|
+
|
|
27
|
+
- `.cursor/rules/*.mdc` — one file per rule, modern frontmatter format
|
|
28
|
+
- `.cursor/commands/*.md` — slash commands mirrored from `.agent-src/commands/`
|
|
29
|
+
- `.cursorrules` — legacy single-file aggregate
|
|
30
|
+
- `.agent-settings.yml` — per-project knobs
|
|
31
|
+
|
|
32
|
+
Combine surfaces if you use both Cursor and Claude Code:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx @event4u/create-agent-config init --tools=cursor,claude-code
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Global install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @event4u/agent-config global --tools=cursor
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Seeds `~/.cursor/rules/imported/event4u/` with the curated kernel +
|
|
45
|
+
top-N skills. Cursor merges global + workspace rules — workspace wins
|
|
46
|
+
on conflicts.
|
|
47
|
+
|
|
48
|
+
## Modern `.mdc` frontmatter
|
|
49
|
+
|
|
50
|
+
Each `.mdc` file has the Cursor-shaped header:
|
|
51
|
+
|
|
52
|
+
```mdc
|
|
53
|
+
---
|
|
54
|
+
description: Scope control — no unsolicited architectural changes
|
|
55
|
+
globs:
|
|
56
|
+
alwaysApply: true
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
# Scope Control
|
|
60
|
+
...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `alwaysApply: true` ↔ source `type: "always"` (kernel rules).
|
|
64
|
+
- `alwaysApply: false` ↔ Cursor model decides per turn (auto rules).
|
|
65
|
+
- `globs:` is intentionally empty in the package's projection — apply
|
|
66
|
+
per-rule if you need path-scoped rules in your fork.
|
|
67
|
+
|
|
68
|
+
## Cursor commands
|
|
69
|
+
|
|
70
|
+
`.cursor/commands/<slug>.md` mirrors `.claude/commands/`. Nested
|
|
71
|
+
clusters (e.g. `council/default.md`) flatten to `council-default.md` so
|
|
72
|
+
Cursor's command palette stays flat.
|
|
73
|
+
|
|
74
|
+
## Marketplace install (planned — Phase 7 / S35)
|
|
75
|
+
|
|
76
|
+
The Cursor marketplace listing is filed in
|
|
77
|
+
`road-to-simplicity-and-everywhere.md` Phase 7. Once accepted you'll
|
|
78
|
+
be able to install via Cursor's Extensions panel without `npx`.
|
|
79
|
+
|
|
80
|
+
## MCP block (when MCP Phase 3 ships)
|
|
81
|
+
|
|
82
|
+
Add to `.cursor/mcp.json` (Cursor's project-scoped MCP config):
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"event4u-agent-config": {
|
|
88
|
+
"command": "npx",
|
|
89
|
+
"args": ["-y", "@event4u/agent-config-mcp"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Track <https://github.com/event4u-app/agent-config> for the actual
|
|
96
|
+
release tag — until `road-to-mcp-full-coverage` Phase 3 ships, this
|
|
97
|
+
block is informational.
|
|
98
|
+
|
|
99
|
+
## Verification
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
ls -la .cursor/rules/ | head -5 # *.mdc files exist
|
|
103
|
+
ls -la .cursor/commands/| head -5 # *.md command files exist
|
|
104
|
+
test -f .cursorrules # legacy aggregate exists
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
In Cursor itself: open the chat panel — settings should show the rules
|
|
108
|
+
under **Project Rules**.
|
|
109
|
+
|
|
110
|
+
## Troubleshooting
|
|
111
|
+
|
|
112
|
+
| Symptom | Fix |
|
|
113
|
+
|---|---|
|
|
114
|
+
| Rules not picked up | Cursor < 0.45 — upgrade or rely on `.cursorrules`. |
|
|
115
|
+
| Modern + legacy duplicate triggers | Disable `.cursorrules` in Cursor settings. |
|
|
116
|
+
| Command missing in palette | `task generate-tools` then reload Cursor window. |
|
|
117
|
+
| Global rules ignored | Cursor needs `~/.cursor/rules/` — check OS path expansion. |
|
|
118
|
+
|
|
119
|
+
## Cross-references
|
|
120
|
+
|
|
121
|
+
- [`docs/installation.md`](../../installation.md) — install matrix index.
|
|
122
|
+
- [`AGENTS.md`](../../../AGENTS.md) — package self-orientation; Cursor
|
|
123
|
+
reads it via the projected rules.
|
|
124
|
+
- [`templates/cursor-rule.mdc.j2`](../../../templates/cursor-rule.mdc.j2) —
|
|
125
|
+
template used by the projection generator.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Gemini CLI Setup
|
|
2
|
+
|
|
3
|
+
Google's Gemini CLI reads `GEMINI.md` (which is a symlink to `AGENTS.md`
|
|
4
|
+
in the package's projection) for project context.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- Gemini CLI installed: <https://github.com/google-gemini/gemini-cli>.
|
|
9
|
+
- Node.js ≥ 18 (for the install entrypoints).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @event4u/create-agent-config init --tools=gemini
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Populates:
|
|
18
|
+
|
|
19
|
+
- `GEMINI.md` → `AGENTS.md` — symlink so Gemini CLI loads the same
|
|
20
|
+
self-orientation as Codex / Aider / Augment.
|
|
21
|
+
- `AGENTS.md` — canonical content (single source of truth).
|
|
22
|
+
- `.agent-settings.yml` — per-project knobs.
|
|
23
|
+
|
|
24
|
+
## Verification
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
test -L GEMINI.md && readlink GEMINI.md # → AGENTS.md
|
|
28
|
+
gemini --version # confirm CLI installed
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
In a Gemini CLI session: `GEMINI.md` informs every turn — verify by
|
|
32
|
+
asking *"what is this repo?"* and confirming the answer matches
|
|
33
|
+
`AGENTS.md`'s emergency-triage block.
|
|
34
|
+
|
|
35
|
+
## Troubleshooting
|
|
36
|
+
|
|
37
|
+
| Symptom | Fix |
|
|
38
|
+
|---|---|
|
|
39
|
+
| Gemini CLI doesn't see `GEMINI.md` | Some Gemini versions require absolute paths — `gemini --context $(pwd)/GEMINI.md`. |
|
|
40
|
+
| Symlink broken on Windows | Re-run installer; on Windows the projection may emit a copy instead of a symlink. |
|
|
41
|
+
|
|
42
|
+
## Cross-references
|
|
43
|
+
|
|
44
|
+
- [`AGENTS.md`](../../../AGENTS.md) — canonical agent self-orientation.
|
|
45
|
+
- [`docs/installation.md`](../../installation.md) — install matrix index.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Windsurf Setup
|
|
2
|
+
|
|
3
|
+
Windsurf reads two rule formats:
|
|
4
|
+
|
|
5
|
+
- **Wave-8 (`.windsurf/rules/`)** — per-rule `.md` files with
|
|
6
|
+
`trigger`, `description`, `globs` frontmatter. Preferred for
|
|
7
|
+
Windsurf 1.5+.
|
|
8
|
+
- **Legacy (`.windsurfrules`)** — single-file aggregate at the repo
|
|
9
|
+
root. Older Windsurf builds and the Cascade chat fallback both still
|
|
10
|
+
read it.
|
|
11
|
+
|
|
12
|
+
The package ships **both**.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- Windsurf 1.0+ (Codeium): <https://codeium.com/windsurf>.
|
|
17
|
+
- Node.js ≥ 18.
|
|
18
|
+
|
|
19
|
+
## Project install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @event4u/create-agent-config init --tools=windsurf
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Populates:
|
|
26
|
+
|
|
27
|
+
- `.windsurf/rules/*.md` — modern Wave-8 per-rule files
|
|
28
|
+
- `.windsurf/workflows/*.md` — slash-command workflows
|
|
29
|
+
- `.windsurfrules` — legacy single-file aggregate
|
|
30
|
+
- `.agent-settings.yml` — per-project knobs
|
|
31
|
+
|
|
32
|
+
Combine with other surfaces:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx @event4u/create-agent-config init --tools=windsurf,claude-code,cursor
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Global install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @event4u/agent-config global --tools=windsurf
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Seeds `~/.codeium/windsurf/global_workflows/` with the curated
|
|
45
|
+
workflow set (see [`templates/global-install-manifest.yml`](../../../templates/global-install-manifest.yml)).
|
|
46
|
+
Available across every project; per-workspace `.windsurf/workflows/`
|
|
47
|
+
takes precedence on slug collisions.
|
|
48
|
+
|
|
49
|
+
## Wave-8 frontmatter
|
|
50
|
+
|
|
51
|
+
Each rule under `.windsurf/rules/` has the Windsurf-shaped header:
|
|
52
|
+
|
|
53
|
+
```md
|
|
54
|
+
---
|
|
55
|
+
trigger: always_on
|
|
56
|
+
description: Scope control — no unsolicited architectural changes
|
|
57
|
+
globs:
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
# Scope Control
|
|
61
|
+
...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- `trigger: always_on` ↔ source `type: "always"` (kernel rules).
|
|
65
|
+
- `trigger: model_decision` ↔ Cascade decides per turn (auto rules).
|
|
66
|
+
- `globs:` is intentionally empty in the package's projection — set
|
|
67
|
+
per-rule in your fork if you want path-scoped triggering.
|
|
68
|
+
|
|
69
|
+
## Workflows
|
|
70
|
+
|
|
71
|
+
`.windsurf/workflows/<slug>.md` mirrors `.claude/commands/`. Cluster
|
|
72
|
+
commands flatten to `<cluster>-<name>.md`. Cascade lists all workflow
|
|
73
|
+
files in its workflow palette.
|
|
74
|
+
|
|
75
|
+
## Cascade integration
|
|
76
|
+
|
|
77
|
+
Cascade (Windsurf's built-in agent) reads `.windsurf/rules/` and
|
|
78
|
+
`.windsurf/workflows/` automatically. No separate registration step is
|
|
79
|
+
needed once the files are on disk.
|
|
80
|
+
|
|
81
|
+
When Cascade asks a clarifying question, the package's `user-interaction`
|
|
82
|
+
rule (kernel, `always_on`) applies — Cascade will surface numbered
|
|
83
|
+
options with a single recommendation.
|
|
84
|
+
|
|
85
|
+
## Workspace vs global precedence
|
|
86
|
+
|
|
87
|
+
| Layer | Path | Precedence |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| Workspace | `.windsurf/rules/` + `.windsurf/workflows/` | wins on conflicts |
|
|
90
|
+
| Global | `~/.codeium/windsurf/global_workflows/` | falls back when workspace silent |
|
|
91
|
+
|
|
92
|
+
Reuse the same `--tools=windsurf` flag for both — `init` writes
|
|
93
|
+
workspace, `global` writes user-level.
|
|
94
|
+
|
|
95
|
+
## Verification
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ls .windsurf/rules/ | head -5 # *.md per-rule files
|
|
99
|
+
ls .windsurf/workflows/ | head -5 # *.md workflow files
|
|
100
|
+
test -f .windsurfrules # legacy aggregate exists
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
In Windsurf itself: open Cascade → Workflows panel — listed workflows
|
|
104
|
+
should match `ls .windsurf/workflows/`.
|
|
105
|
+
|
|
106
|
+
## Troubleshooting
|
|
107
|
+
|
|
108
|
+
| Symptom | Fix |
|
|
109
|
+
|---|---|
|
|
110
|
+
| Rules not picked up | Windsurf < 1.0 — upgrade or rely on `.windsurfrules`. |
|
|
111
|
+
| Workflow not in Cascade panel | Reload window after `task generate-tools`. |
|
|
112
|
+
| Global workflows missing | Check `~/.codeium/windsurf/global_workflows/` exists. |
|
|
113
|
+
| Frontmatter parse error | Re-run `python3 scripts/compress.py --generate-tools`. |
|
|
114
|
+
|
|
115
|
+
## Cross-references
|
|
116
|
+
|
|
117
|
+
- [`docs/installation.md`](../../installation.md) — install matrix index.
|
|
118
|
+
- [`templates/windsurf-rule.md.j2`](../../../templates/windsurf-rule.md.j2)
|
|
119
|
+
— template used by the projection generator.
|
|
120
|
+
- [`AGENTS.md`](../../../AGENTS.md) — package self-orientation.
|
package/package.json
CHANGED
|
@@ -43,20 +43,24 @@ def _read_settings_level(settings_path: Path) -> str | None:
|
|
|
43
43
|
"""Read verbosity.script_output from .agent-settings.yml.
|
|
44
44
|
|
|
45
45
|
Returns None when the file is missing, PyYAML is unavailable, or
|
|
46
|
-
the key is absent. Errors fall through to the default level.
|
|
46
|
+
the key is absent. Errors fall through to the default level. Goes
|
|
47
|
+
through the centralized loader (road-to-portable-dev-preferences P3)
|
|
48
|
+
so the tolerance contract — missing file, malformed YAML, no PyYAML
|
|
49
|
+
— degrades uniformly across scripts. ``verbosity.script_output`` is
|
|
50
|
+
not on the user-global whitelist, so a value there is silently
|
|
51
|
+
ignored; the project file is the only source for this knob.
|
|
47
52
|
"""
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
# Lazy import — supports both `python3 scripts/foo.py` (sys.path
|
|
54
|
+
# contains scripts/) and `pytest` (scripts._lib is a proper package).
|
|
50
55
|
try:
|
|
51
|
-
import
|
|
56
|
+
from _lib.agent_settings import load_agent_settings # type: ignore[import-not-found] # noqa: PLC0415
|
|
52
57
|
except ImportError:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
section = data.get("verbosity") if isinstance(data, dict) else None
|
|
58
|
+
from scripts._lib.agent_settings import ( # noqa: PLC0415
|
|
59
|
+
load_agent_settings,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
data = load_agent_settings(project_path=settings_path)
|
|
63
|
+
section = data.get("verbosity")
|
|
60
64
|
if not isinstance(section, dict):
|
|
61
65
|
return None
|
|
62
66
|
value = section.get("script_output")
|
|
@@ -88,15 +88,21 @@ def _load_retention_days(settings_path: Path | None = None) -> int:
|
|
|
88
88
|
file, invalid YAML, missing key, non-int value). Pruning never
|
|
89
89
|
blocks the council on a settings error.
|
|
90
90
|
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
# Centralized loader (road-to-portable-dev-preferences P3): tolerance
|
|
92
|
+
# contract handles missing file / malformed YAML / no PyYAML uniformly.
|
|
93
|
+
# ``ai_council.session_retention_days`` is not whitelisted, so the
|
|
94
|
+
# user-global file cannot override the project value.
|
|
94
95
|
try:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
from scripts._lib.agent_settings import load_agent_settings
|
|
97
|
+
except ImportError: # pragma: no cover — script-style invocation
|
|
98
|
+
import sys as _sys
|
|
99
|
+
from pathlib import Path as _Path
|
|
100
|
+
_sys.path.insert(0, str(_Path(__file__).resolve().parent.parent))
|
|
101
|
+
from _lib.agent_settings import load_agent_settings # type: ignore[import-not-found]
|
|
102
|
+
|
|
103
|
+
path = settings_path or SETTINGS_FILE
|
|
104
|
+
data = load_agent_settings(project_path=path)
|
|
105
|
+
ai = data.get("ai_council")
|
|
100
106
|
if not isinstance(ai, dict):
|
|
101
107
|
return DEFAULT_RETENTION_DAYS
|
|
102
108
|
raw = ai.get("session_retention_days", DEFAULT_RETENTION_DAYS)
|
package/scripts/chat_history.py
CHANGED
|
@@ -609,27 +609,36 @@ def status(*, path: Path | None = None) -> dict[str, Any]:
|
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
|
|
612
|
+
def _load_chat_history_section(settings_path: Path) -> dict | None:
|
|
613
|
+
"""Return the ``chat_history`` mapping from .agent-settings.yml or None.
|
|
614
|
+
|
|
615
|
+
Centralized loader (road-to-portable-dev-preferences P3): tolerance
|
|
616
|
+
contract handles missing file / malformed YAML / no PyYAML uniformly.
|
|
617
|
+
No ``chat_history.*`` keys are whitelisted, so user-global cannot
|
|
618
|
+
leak into this section — the project file remains authoritative.
|
|
619
|
+
"""
|
|
620
|
+
try:
|
|
621
|
+
from scripts._lib.agent_settings import load_agent_settings
|
|
622
|
+
except ImportError: # pragma: no cover — script-style invocation
|
|
623
|
+
import sys as _sys
|
|
624
|
+
from pathlib import Path as _Path
|
|
625
|
+
_sys.path.insert(0, str(_Path(__file__).resolve().parent))
|
|
626
|
+
from _lib.agent_settings import load_agent_settings # type: ignore[import-not-found]
|
|
627
|
+
|
|
628
|
+
data = load_agent_settings(project_path=settings_path)
|
|
629
|
+
section = data.get("chat_history")
|
|
630
|
+
return section if isinstance(section, dict) else None
|
|
631
|
+
|
|
632
|
+
|
|
612
633
|
def _read_chat_history_enabled(settings_path: Path) -> bool:
|
|
613
634
|
"""Read chat_history.enabled from .agent-settings.yml.
|
|
614
635
|
|
|
615
636
|
Returns False when the file is missing, malformed, lacks the
|
|
616
637
|
`chat_history` section, or sets enabled to false. Default-deny so
|
|
617
638
|
`turn-check` is safe to run from projects that have not opted in.
|
|
618
|
-
PyYAML is imported lazily — the rest of this module works without it.
|
|
619
639
|
"""
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
try:
|
|
623
|
-
import yaml # type: ignore[import-untyped]
|
|
624
|
-
except ImportError:
|
|
625
|
-
return True # fail open: settings file present but no parser
|
|
626
|
-
try:
|
|
627
|
-
with settings_path.open(encoding="utf-8") as fh:
|
|
628
|
-
data = yaml.safe_load(fh) or {}
|
|
629
|
-
except (OSError, yaml.YAMLError):
|
|
630
|
-
return False
|
|
631
|
-
section = data.get("chat_history") if isinstance(data, dict) else None
|
|
632
|
-
if not isinstance(section, dict):
|
|
640
|
+
section = _load_chat_history_section(settings_path)
|
|
641
|
+
if section is None:
|
|
633
642
|
return False
|
|
634
643
|
return bool(section.get("enabled", False))
|
|
635
644
|
|
|
@@ -739,19 +748,8 @@ VALID_PLATFORMS = tuple(PLATFORM_EVENT_MAP.keys())
|
|
|
739
748
|
|
|
740
749
|
def _read_chat_history_frequency(settings_path: Path) -> str:
|
|
741
750
|
"""Read chat_history.frequency from .agent-settings.yml. Default per_phase."""
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
try:
|
|
745
|
-
import yaml # type: ignore[import-untyped]
|
|
746
|
-
except ImportError:
|
|
747
|
-
return "per_phase"
|
|
748
|
-
try:
|
|
749
|
-
with settings_path.open(encoding="utf-8") as fh:
|
|
750
|
-
data = yaml.safe_load(fh) or {}
|
|
751
|
-
except (OSError, yaml.YAMLError):
|
|
752
|
-
return "per_phase"
|
|
753
|
-
section = data.get("chat_history") if isinstance(data, dict) else None
|
|
754
|
-
if not isinstance(section, dict):
|
|
751
|
+
section = _load_chat_history_section(settings_path)
|
|
752
|
+
if section is None:
|
|
755
753
|
return "per_phase"
|
|
756
754
|
val = str(section.get("frequency", "per_phase")).lower()
|
|
757
755
|
return val if val in VALID_FREQS else "per_phase"
|
|
@@ -764,19 +762,8 @@ def _read_chat_history_max_sessions(settings_path: Path) -> int:
|
|
|
764
762
|
Used by ``prune_sessions`` to decide how many distinct ``s`` tags
|
|
765
763
|
survive in the body.
|
|
766
764
|
"""
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
try:
|
|
770
|
-
import yaml # type: ignore[import-untyped]
|
|
771
|
-
except ImportError:
|
|
772
|
-
return DEFAULT_MAX_SESSIONS
|
|
773
|
-
try:
|
|
774
|
-
with settings_path.open(encoding="utf-8") as fh:
|
|
775
|
-
data = yaml.safe_load(fh) or {}
|
|
776
|
-
except (OSError, yaml.YAMLError):
|
|
777
|
-
return DEFAULT_MAX_SESSIONS
|
|
778
|
-
section = data.get("chat_history") if isinstance(data, dict) else None
|
|
779
|
-
if not isinstance(section, dict):
|
|
765
|
+
section = _load_chat_history_section(settings_path)
|
|
766
|
+
if section is None:
|
|
780
767
|
return DEFAULT_MAX_SESSIONS
|
|
781
768
|
try:
|
|
782
769
|
n = int(section.get("max_sessions", DEFAULT_MAX_SESSIONS))
|
|
@@ -794,19 +781,8 @@ def _read_text_limits(settings_path: Path) -> dict[str, int]:
|
|
|
794
781
|
values are clamped to 0. Non-int values are silently dropped.
|
|
795
782
|
"""
|
|
796
783
|
out = dict(DEFAULT_TEXT_LIMITS)
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
try:
|
|
800
|
-
import yaml # type: ignore[import-untyped]
|
|
801
|
-
except ImportError:
|
|
802
|
-
return out
|
|
803
|
-
try:
|
|
804
|
-
with settings_path.open(encoding="utf-8") as fh:
|
|
805
|
-
data = yaml.safe_load(fh) or {}
|
|
806
|
-
except (OSError, yaml.YAMLError):
|
|
807
|
-
return out
|
|
808
|
-
section = data.get("chat_history") if isinstance(data, dict) else None
|
|
809
|
-
if not isinstance(section, dict):
|
|
784
|
+
section = _load_chat_history_section(settings_path)
|
|
785
|
+
if section is None:
|
|
810
786
|
return out
|
|
811
787
|
overrides = section.get("text_limits")
|
|
812
788
|
if not isinstance(overrides, dict):
|
|
@@ -42,20 +42,22 @@ def load_settings(settings_path: Path | str | None = None) -> Settings:
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def _read_section(path: Path) -> dict[str, Any] | None:
|
|
45
|
-
"""Return the ``commands.suggestion`` mapping or ``None`` on any miss.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
"""Return the ``commands.suggestion`` mapping or ``None`` on any miss.
|
|
46
|
+
|
|
47
|
+
Centralized loader (road-to-portable-dev-preferences P3): tolerance
|
|
48
|
+
contract handles missing file / malformed YAML / no PyYAML uniformly.
|
|
49
|
+
No ``commands.*`` keys are whitelisted, so user-global cannot cascade
|
|
50
|
+
into this section.
|
|
51
|
+
"""
|
|
52
52
|
try:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
from scripts._lib.agent_settings import load_agent_settings
|
|
54
|
+
except ImportError: # pragma: no cover — script-style invocation
|
|
55
|
+
import sys as _sys
|
|
56
|
+
from pathlib import Path as _Path
|
|
57
|
+
_sys.path.insert(0, str(_Path(__file__).resolve().parent.parent))
|
|
58
|
+
from _lib.agent_settings import load_agent_settings # type: ignore[import-not-found]
|
|
59
|
+
|
|
60
|
+
data = load_agent_settings(project_path=path)
|
|
59
61
|
commands = data.get("commands")
|
|
60
62
|
if not isinstance(commands, dict):
|
|
61
63
|
return None
|
|
@@ -100,16 +100,20 @@ def _normalize_trigger(item) -> dict | None:
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
def _load_settings() -> dict:
|
|
103
|
-
"""Read .agent-settings.yml for compile-time toggles.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
"""Read .agent-settings.yml for compile-time toggles.
|
|
104
|
+
|
|
105
|
+
Centralized loader (road-to-portable-dev-preferences P3): tolerance
|
|
106
|
+
contract handles missing file / malformed YAML / no PyYAML uniformly.
|
|
107
|
+
"""
|
|
107
108
|
try:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
from scripts._lib.agent_settings import load_agent_settings
|
|
110
|
+
except ImportError: # pragma: no cover — script-style invocation
|
|
111
|
+
import sys as _sys
|
|
112
|
+
from pathlib import Path as _Path
|
|
113
|
+
_sys.path.insert(0, str(_Path(__file__).resolve().parent))
|
|
114
|
+
from _lib.agent_settings import load_agent_settings # type: ignore[import-not-found]
|
|
115
|
+
|
|
116
|
+
return load_agent_settings(project_path=SETTINGS_PATH)
|
|
113
117
|
|
|
114
118
|
|
|
115
119
|
def _collect(rules_dir: Path) -> dict:
|