@event4u/agent-config 1.18.0 → 1.19.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 (126) hide show
  1. package/.agent-src/commands/council/default.md +74 -76
  2. package/.agent-src/commands/feature/roadmap.md +22 -0
  3. package/.agent-src/commands/roadmap/create.md +38 -6
  4. package/.agent-src/commands/roadmap/execute.md +36 -9
  5. package/.agent-src/rules/agent-authority.md +1 -0
  6. package/.agent-src/rules/agent-docs.md +1 -0
  7. package/.agent-src/rules/analysis-skill-routing.md +1 -0
  8. package/.agent-src/rules/architecture.md +1 -0
  9. package/.agent-src/rules/artifact-drafting-protocol.md +1 -0
  10. package/.agent-src/rules/artifact-engagement-recording.md +1 -0
  11. package/.agent-src/rules/ask-when-uncertain.md +1 -0
  12. package/.agent-src/rules/augment-portability.md +1 -0
  13. package/.agent-src/rules/augment-source-of-truth.md +1 -0
  14. package/.agent-src/rules/autonomous-execution.md +1 -0
  15. package/.agent-src/rules/capture-learnings.md +1 -0
  16. package/.agent-src/rules/chat-history-cadence.md +34 -0
  17. package/.agent-src/rules/chat-history-ownership.md +1 -0
  18. package/.agent-src/rules/chat-history-visibility.md +1 -0
  19. package/.agent-src/rules/cli-output-handling.md +2 -2
  20. package/.agent-src/rules/command-suggestion-policy.md +1 -0
  21. package/.agent-src/rules/commit-conventions.md +1 -0
  22. package/.agent-src/rules/commit-policy.md +1 -0
  23. package/.agent-src/rules/context-hygiene.md +22 -0
  24. package/.agent-src/rules/direct-answers.md +1 -0
  25. package/.agent-src/rules/docker-commands.md +1 -0
  26. package/.agent-src/rules/docs-sync.md +1 -0
  27. package/.agent-src/rules/downstream-changes.md +1 -0
  28. package/.agent-src/rules/e2e-testing.md +1 -0
  29. package/.agent-src/rules/guidelines.md +1 -0
  30. package/.agent-src/rules/improve-before-implement.md +1 -0
  31. package/.agent-src/rules/language-and-tone.md +1 -0
  32. package/.agent-src/rules/laravel-translations.md +1 -0
  33. package/.agent-src/rules/markdown-safe-codeblocks.md +1 -0
  34. package/.agent-src/rules/minimal-safe-diff.md +1 -0
  35. package/.agent-src/rules/missing-tool-handling.md +1 -0
  36. package/.agent-src/rules/model-recommendation.md +1 -0
  37. package/.agent-src/rules/no-cheap-questions.md +1 -0
  38. package/.agent-src/rules/no-roadmap-references.md +1 -0
  39. package/.agent-src/rules/non-destructive-by-default.md +1 -0
  40. package/.agent-src/rules/onboarding-gate.md +26 -0
  41. package/.agent-src/rules/package-ci-checks.md +1 -0
  42. package/.agent-src/rules/php-coding.md +1 -0
  43. package/.agent-src/rules/preservation-guard.md +1 -0
  44. package/.agent-src/rules/review-routing-awareness.md +1 -0
  45. package/.agent-src/rules/reviewer-awareness.md +1 -0
  46. package/.agent-src/rules/roadmap-progress-sync.md +22 -0
  47. package/.agent-src/rules/role-mode-adherence.md +2 -2
  48. package/.agent-src/rules/rule-type-governance.md +1 -0
  49. package/.agent-src/rules/runtime-safety.md +1 -0
  50. package/.agent-src/rules/scope-control.md +1 -0
  51. package/.agent-src/rules/security-sensitive-stop.md +1 -0
  52. package/.agent-src/rules/size-enforcement.md +1 -0
  53. package/.agent-src/rules/skill-improvement-trigger.md +1 -0
  54. package/.agent-src/rules/skill-quality.md +1 -0
  55. package/.agent-src/rules/slash-command-routing-policy.md +39 -0
  56. package/.agent-src/rules/think-before-action.md +1 -0
  57. package/.agent-src/rules/token-efficiency.md +1 -0
  58. package/.agent-src/rules/tool-safety.md +1 -0
  59. package/.agent-src/rules/ui-audit-gate.md +1 -0
  60. package/.agent-src/rules/upstream-proposal.md +1 -0
  61. package/.agent-src/rules/user-interaction.md +1 -0
  62. package/.agent-src/rules/verify-before-complete.md +1 -0
  63. package/.agent-src/skills/roadmap-management/SKILL.md +29 -4
  64. package/.agent-src/skills/verify-completion-evidence/SKILL.md +8 -1
  65. package/.agent-src/templates/agent-settings.md +16 -0
  66. package/.agent-src/templates/roadmaps.md +8 -3
  67. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +9 -0
  68. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +4 -0
  69. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +4 -0
  70. package/.agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.py +163 -0
  71. package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +111 -0
  72. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +36 -0
  73. package/.agent-src/templates/scripts/work_engine/scoring/decision_trace.py +141 -0
  74. package/.agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +125 -0
  75. package/.claude-plugin/marketplace.json +1 -1
  76. package/CHANGELOG.md +62 -0
  77. package/README.md +19 -19
  78. package/config/agent-settings.template.yml +23 -0
  79. package/docs/catalog.md +5 -2
  80. package/docs/contracts/adr-settings-sync-engine.md +127 -0
  81. package/docs/contracts/decision-trace-v1.md +146 -0
  82. package/docs/contracts/file-ownership-matrix.json +7 -0
  83. package/docs/contracts/hook-architecture-v1.md +213 -0
  84. package/docs/contracts/memory-visibility-v1.md +138 -0
  85. package/docs/contracts/one-off-script-lifecycle.md +109 -0
  86. package/docs/contracts/rule-interactions.yml +22 -0
  87. package/docs/customization.md +1 -0
  88. package/docs/development.md +4 -1
  89. package/docs/guidelines/agent-infra/layered-settings.md +32 -13
  90. package/package.json +1 -1
  91. package/scripts/agent-config +44 -0
  92. package/scripts/ai_council/bundler.py +3 -3
  93. package/scripts/ai_council/clients.py +24 -8
  94. package/scripts/ai_council/one_off_archive/2026-05/README.md +22 -0
  95. package/scripts/ai_council/one_off_archive/2026-05/_one_off_roundtrip.py +13 -8
  96. package/scripts/ai_council/one_off_archive/2026-05/_one_off_tier_retrofit.py +180 -0
  97. package/scripts/ai_council/session.py +92 -0
  98. package/scripts/capture_showcase_session.py +361 -0
  99. package/scripts/chat_history.py +11 -1
  100. package/scripts/check_always_budget.py +7 -2
  101. package/scripts/context_hygiene_hook.py +14 -6
  102. package/scripts/council_cli.py +357 -0
  103. package/scripts/hook_manifest.yaml +184 -0
  104. package/scripts/hooks/__init__.py +1 -0
  105. package/scripts/hooks/augment-dispatcher.sh +72 -0
  106. package/scripts/hooks/cline-dispatcher.sh +86 -0
  107. package/scripts/hooks/cursor-dispatcher.sh +76 -0
  108. package/scripts/hooks/dispatch_hook.py +348 -0
  109. package/scripts/hooks/envelope.py +98 -0
  110. package/scripts/hooks/gemini-dispatcher.sh +117 -0
  111. package/scripts/hooks/state_io.py +122 -0
  112. package/scripts/hooks/windsurf-dispatcher.sh +123 -0
  113. package/scripts/hooks_status.py +146 -0
  114. package/scripts/install.py +725 -87
  115. package/scripts/install.sh +1 -1
  116. package/scripts/lint_hook_manifest.py +216 -0
  117. package/scripts/lint_one_off_age.py +184 -0
  118. package/scripts/lint_rule_tiers.py +78 -0
  119. package/scripts/lint_showcase_sessions.py +148 -0
  120. package/scripts/minimal_safe_diff_hook.py +245 -0
  121. package/scripts/onboarding_gate_hook.py +13 -8
  122. package/scripts/readme_linter.py +12 -3
  123. package/scripts/roadmap_progress_hook.py +5 -0
  124. package/scripts/sync_agent_settings.py +32 -129
  125. package/scripts/sync_yaml_rt.py +734 -0
  126. package/scripts/verify_before_complete_hook.py +216 -0
@@ -0,0 +1,138 @@
1
+ ---
2
+ stability: beta
3
+ ---
4
+
5
+ # Memory-visibility v1
6
+
7
+ **Purpose.** Pin the format of the user-facing visibility line that
8
+ every memory-using `/work` and `/implement-ticket` run prints, so the
9
+ user can tell what the agent retrieved and what it ignored.
10
+ Complements [`agent-memory-contract.md`](agent-memory-contract.md):
11
+ that doc describes the **CLI surface and backend states**; this doc
12
+ describes the **operator-facing surface** the engine emits per turn.
13
+
14
+ **Scope.** Defines the line shape, the privacy floor, the opt-out
15
+ toggle, and the interaction with the chat-history heartbeat. Does
16
+ **not** define how memory entries are scored or routed — that is the
17
+ sibling agent-memory package.
18
+
19
+ Last refreshed: 2026-05-04.
20
+
21
+ ## Line shape
22
+
23
+ A single one-line ASCII record, prefixed with the memory icon `🧠`
24
+ and a single space:
25
+
26
+ ```
27
+ 🧠 Memory: <hits>/<asks> · ids=[<comma-separated-ids>]
28
+ ```
29
+
30
+ Examples:
31
+
32
+ ```
33
+ 🧠 Memory: 3/4 · ids=[mem_42, mem_57, mem_91]
34
+ 🧠 Memory: 0/2 · ids=[]
35
+ 🧠 Memory: 5/5 · ids=[mem_a01, mem_a02, mem_a03, …+2]
36
+ ```
37
+
38
+ Cap at 5 ids inline; remainder rendered as `…+N`. The full id list
39
+ lives in the decision-trace JSON
40
+ ([`decision-trace-v1.md`](decision-trace-v1.md)).
41
+
42
+ ## Field semantics
43
+
44
+ | Field | Meaning |
45
+ |---|---|
46
+ | `hits` | Count of `memory_retrieve_*` calls during this turn that returned ≥ 1 entry. |
47
+ | `asks` | Count of `memory_retrieve_*` calls during this turn — both successful and empty. |
48
+ | `ids` | Stable memory entry ids returned across all calls, deduped, ordered by retrieval timestamp. |
49
+
50
+ `hits ≤ asks` is invariant. If `asks == 0`, the engine MUST suppress
51
+ the line entirely — no `0/0` noise.
52
+
53
+ ## Privacy floor
54
+
55
+ The visibility line and the JSON it derives from MUST NOT contain:
56
+
57
+ - Entry **bodies**, summaries, or quoted snippets.
58
+ - Secrets, tokens, environment values, or paths outside the
59
+ package's `agents/state/` and `tests/` allowlist.
60
+ - User identifiers beyond what is already public in the working
61
+ directory's `.agent-settings.yml` (e.g. developer name).
62
+
63
+ The privacy floor is enforced by
64
+ `tests/contracts/test_memory_visibility_redaction.py` — any new
65
+ content path that ships memory output adds a fixture there.
66
+
67
+ ## Opt-out
68
+
69
+ On by default whenever memory is asked at all in a turn. Users can
70
+ suppress the visibility line via:
71
+
72
+ ```yaml
73
+ memory:
74
+ visibility: off
75
+ ```
76
+
77
+ Off-mode does not silence the underlying memory calls; it only stops
78
+ the line from rendering. The decision-trace JSON still records the
79
+ counts and ids for downstream metrics.
80
+
81
+ ## Interaction with chat-history heartbeat
82
+
83
+ The chat-history heartbeat (`📒` marker) and the memory-visibility
84
+ line are **independent**:
85
+
86
+ - Heartbeat fires on cadence boundaries
87
+ (`per_turn` / `per_phase` / `per_tool`).
88
+ - Visibility line fires whenever `asks ≥ 1` for the current turn,
89
+ regardless of cadence.
90
+ - Both render on the same reply when both fire — heartbeat first,
91
+ visibility line second, separated by a single newline.
92
+
93
+ The visibility line is **not** part of the heartbeat payload — that
94
+ keeps the heartbeat contract bytes-stable.
95
+
96
+ ## Cadence interaction
97
+
98
+ | Cost profile | Visibility line | Heartbeat |
99
+ |---|---|---|
100
+ | `lean` | suppress unless `asks ≥ 3` | per-phase |
101
+ | `standard` | always when `asks ≥ 1` | per-turn |
102
+ | `verbose` | always when `asks ≥ 1` | per-tool |
103
+
104
+ Cost-profile lookup respects `.agent-settings.yml`'s `cost_profile`
105
+ key. Default is `standard`.
106
+
107
+ ## Audit-as-memory feed
108
+
109
+ The visibility output produced by the engine is the input to the
110
+ audit-as-memory pipeline (consumed by the sibling distribution +
111
+ adoption work). Concretely:
112
+
113
+ - The engine emits the line + the underlying counts to the
114
+ decision-trace JSON.
115
+ - A consumer hook reads `agents/state/work/<work-id>/decision-trace-*.json`,
116
+ rolls counts up to the session level, and feeds the result back
117
+ into the agent-memory store as an audit entry.
118
+
119
+ This contract pins the **producer** side. The audit-feed consumer
120
+ lives outside the package's stable surface and must read this
121
+ contract before parsing.
122
+
123
+ ## Stability
124
+
125
+ Beta. Breaking changes between v1 and v2 are allowed in a minor
126
+ release if the change appears in `CHANGELOG.md` under a `### Breaking`
127
+ heading. Engines MUST gate on the visibility line shape — clients
128
+ parsing the stream MUST treat unknown trailing fields as forward-
129
+ compat extensions.
130
+
131
+ ## Cross-references
132
+
133
+ - CLI surface and backend states:
134
+ [`agent-memory-contract.md`](agent-memory-contract.md).
135
+ - Decision-trace JSON consumes the same counts:
136
+ [`decision-trace-v1.md`](decision-trace-v1.md).
137
+ - Privacy regression test path:
138
+ `tests/contracts/test_memory_visibility_redaction.py`.
@@ -0,0 +1,109 @@
1
+ ---
2
+ stability: beta
3
+ ---
4
+
5
+ # One-off-script lifecycle
6
+
7
+ **Purpose.** Pin the naming, location, age, and purge policy for
8
+ **one-off scripts** so the package does not accumulate a graveyard
9
+ under `scripts/`. One-off here means: a script written for a
10
+ specific migration, retrofit, audit, or council run, with no ongoing
11
+ caller and no place in the durable Taskfile.
12
+
13
+ **Scope.** Defines the file pattern, the directory, the maximum age,
14
+ the TTL extension mechanism, and the CI purge gate. Does **not**
15
+ specify the content of any specific one-off — that belongs to the
16
+ script itself or the cleanup-mechanics context.
17
+
18
+ Last refreshed: 2026-05-04.
19
+
20
+ ## Naming
21
+
22
+ One-off scripts MUST match this regex:
23
+
24
+ ```
25
+ ^_one_off_[a-z0-9-]+\.py$
26
+ ```
27
+
28
+ The `_one_off_` prefix is the load-bearing signal. Files outside
29
+ this prefix are treated as durable scripts and MUST be referenced by
30
+ the Taskfile or by another script.
31
+
32
+ ## Location
33
+
34
+ ```
35
+ scripts/_one_off/<YYYY-MM>/_one_off_<slug>.py
36
+ ```
37
+
38
+ `<YYYY-MM>` is the UTC month the script was first committed. The
39
+ month directory groups one-offs for archival sweeps. Scripts MUST
40
+ NOT live at `scripts/_one_off/_one_off_*.py` (no month) or under
41
+ `scripts/` directly (no `_one_off/`).
42
+
43
+ ## TTL
44
+
45
+ | State | Action |
46
+ |---|---|
47
+ | Age ≤ 60 days from month-directory date | active, no warning |
48
+ | 60 < Age ≤ 90 days | warning emitted by `lint_one_off_age.py`, no failure |
49
+ | Age > 90 days | `lint_one_off_age.py` fails CI; the script is purged in the next housekeeping pass |
50
+
51
+ Age = `today − first-of-month(<YYYY-MM>)` in UTC days. The 60-day
52
+ soft floor and 30-day grace window are intentional — they cover one
53
+ release cycle plus a sprint of grace.
54
+
55
+ ## TTL extension
56
+
57
+ A one-off MAY extend its TTL exactly once, by adding a frontmatter
58
+ block at the top of the script:
59
+
60
+ ```python
61
+ """
62
+ ---
63
+ ttl_extended_until: 2026-08-31
64
+ ttl_reason: blocked on PROJ-123 — re-runs after cutover
65
+ ---
66
+ """
67
+ ```
68
+
69
+ The linter respects `ttl_extended_until` if it is ≤ 180 days from
70
+ the file's `<YYYY-MM>` directory date. Beyond 180 days, the linter
71
+ hard-fails — no second extension. The intent is: if a "one-off" is
72
+ still live at six months, it is a durable concern and belongs in
73
+ `scripts/` or a Taskfile group.
74
+
75
+ ## Purge mechanism
76
+
77
+ `lint_one_off_age.py` runs in `task ci`. On a clean working tree, it
78
+ prints purge candidates as a list. Purge itself is a separate human-
79
+ or-CI action — `task purge-one-offs` removes flagged files. The
80
+ linter does not auto-delete.
81
+
82
+ ## Allowed exceptions
83
+
84
+ Two patterns are exempt from the prefix requirement:
85
+
86
+ - **Bundler / orchestrator helpers** under `scripts/ai_council/`
87
+ that exist to support the council CLI — they are not one-offs even
88
+ though council *runs* are one-offs.
89
+ - **`scripts/_one_off/<YYYY-MM>/README.md`** — a free-form readme is
90
+ allowed in each month directory documenting why the scripts exist.
91
+
92
+ Council run scripts that wrap a question and write the response file
93
+ DO live under `scripts/_one_off/<YYYY-MM>/` and DO follow the prefix
94
+ rule.
95
+
96
+ ## Cross-references
97
+
98
+ - The contract that defines council CLI surface (and so what gets
99
+ archived as a one-off): the council CLI section of the package's
100
+ command catalog.
101
+ - The cleanup-mechanics context for housekeeping passes:
102
+ `agents/contexts/cleanup-mechanics.md`.
103
+ - Linter implementation: `scripts/lint_one_off_age.py`.
104
+
105
+ ## Stability
106
+
107
+ Beta. Breaking changes (e.g. raising the age cap, changing the
108
+ prefix, or removing TTL extensions) require a minor-version bump and
109
+ a `### Breaking` entry in `CHANGELOG.md`.
@@ -221,6 +221,28 @@ pairs:
221
221
  - .agent-src.uncompressed/rules/ask-when-uncertain.md#iron-law--one-question-per-turn-always
222
222
  - .agent-src.uncompressed/rules/direct-answers.md#iron-law-3--brevity-by-default
223
223
 
224
+ - id: scope-x-verify-before-complete
225
+ rules: [verify-before-complete, scope-control]
226
+ relation: complements
227
+ conflict: >-
228
+ Agent has just finished a change that touches user-permission-gated
229
+ operations (push, branch, PR, tag) and is preparing to claim "done"
230
+ in the same turn. Both rules can fire: `verify-before-complete`
231
+ gates the completion claim on fresh evidence; `scope-control`
232
+ gates the git operation on explicit permission this turn.
233
+ resolution: >-
234
+ Both rules apply independently and compose. The
235
+ `verify-before-complete` Iron Law still requires fresh
236
+ verification output in this message before any "done" claim,
237
+ regardless of whether the user has authorised the next git op.
238
+ Conversely, verification passing does not authorise pushing or
239
+ merging — those stay behind the `scope-control` permission gate.
240
+ Skipping either is a rule violation; satisfying one does not
241
+ satisfy the other.
242
+ evidence:
243
+ - .agent-src.uncompressed/rules/verify-before-complete.md#the-iron-law
244
+ - .agent-src.uncompressed/rules/scope-control.md#git-operations--permission-gated
245
+
224
246
  - id: language-x-direct-answers
225
247
  rules: [language-and-tone, direct-answers]
226
248
  relation: complements
@@ -66,6 +66,7 @@ those sections.
66
66
  | `ai_council.cost_budget.max_calls` | `10` | Maximum council members per invocation. |
67
67
  | `ai_council.cost_budget.max_total_usd` | `0.0` | Per-invocation USD ceiling. `0` disables (token caps still apply). |
68
68
  | `ai_council.cost_budget.daily_limit_usd` | `0.0` | Rolling 24h USD ceiling across all `/council` calls. `0` disables. Ledger lives at `~/.config/agent-config/council-spend.jsonl` (mode 0600). |
69
+ | `ai_council.session_retention_days` | `14` | Auto-prune for `agents/council-sessions/` audit folders. Older session directories are removed on the next `save()`. `0` disables (keep forever). |
69
70
 
70
71
  > **Experimental.** AI Council is not yet validated by external users. API costs apply per consultation.
71
72
 
@@ -17,7 +17,10 @@
17
17
 
18
18
  ## Task Commands
19
19
 
20
- All commands use [Task](https://taskfile.dev/). See `Taskfile.yml` for the full list.
20
+ All commands use [Task](https://taskfile.dev/). The root `Taskfile.yml` orchestrates
21
+ `ci`/`_ci-*` and includes the four task groups under `taskfiles/`
22
+ (`ci-fast.yml`, `content.yml`, `engine.yml`, `release.yml`) with `flatten: true`,
23
+ so every task stays in the root namespace. Run `task --list` for the full list.
21
24
 
22
25
  ### CI & Verification
23
26
 
@@ -152,27 +152,46 @@ MUST follow these rules. Initial file creation and legacy migration
152
152
  are owned by `scripts/install.py`; these rules govern every edit
153
153
  after that.
154
154
 
155
+ The contract is **additive merge with user-line preservation** —
156
+ the user's file is the ground truth, the template only contributes
157
+ keys the user is missing. Round-trip parser and merger live in
158
+ [`scripts/sync_yaml_rt.py`](../../scripts/sync_yaml_rt.py); the
159
+ supported YAML subset (block-mappings, scalars, lists, comments,
160
+ CRLF/LF) is documented in its module docstring. The stdlib-only
161
+ choice (vs. `ruamel.yaml`) and its revisit triggers are recorded in
162
+ [`docs/contracts/adr-settings-sync-engine.md`](../../contracts/adr-settings-sync-engine.md).
163
+
155
164
  For each section in the template
156
- ([`agent-settings.md`](../../templates/agent-settings.md)), walked in
157
- template order:
165
+ ([`agent-settings.md`](../../templates/agent-settings.md)):
158
166
 
159
- - Keep the section header and its comments verbatim from the template.
160
167
  - For each key under the section:
161
- - **Key exists in user's file** → use the user's current value.
162
- - **Key missing** use the template default.
163
- - **Unknown sections/keys** the user has added preserve at the end
164
- of the section (or in a trailing `_user:` block if no matching
165
- section exists).
168
+ - **Key exists in user's file** → keep the user's line **verbatim**
169
+ (value, quoting, inline comment, indent all preserved).
170
+ - **Key missing** insert the template's line at the position
171
+ after the user's last preceding sibling that is also in the
172
+ template (max-index insertion).
173
+ - **Unknown sections/keys** the user has added → preserved verbatim
174
+ at their existing position. They are not moved to a trailing
175
+ `_user:` block, not re-prefixed, not flattened.
166
176
 
167
177
  Invariants:
168
178
 
169
- - Template section **order** always wins reorder existing keys to
170
- match.
179
+ - **User order wins.** Template order is only consulted to decide
180
+ where to insert missing keys; existing user keys are never
181
+ reordered.
171
182
  - Existing scalar values are **never overwritten** unless the user
172
183
  asked for that specific change.
173
- - New keys added to the template land with their default value.
174
- - Comments from the template replace user comments in the same
175
- position comments are documentation, not user data.
184
+ - New keys added to the template land with their default value and
185
+ the template's leading comments.
186
+ - **User comments are preserved verbatim** on every existing key.
187
+ Template comments only land with keys the merger inserts; once a
188
+ key is in the user's file, its surrounding comments are owned by
189
+ the user.
190
+ - Legacy `_user._user.foo` corruption (accumulated by older buggy
191
+ syncs) heals on the next sync — the leading `_user.` chain is
192
+ stripped and the leaf is re-homed at its template path, or kept
193
+ as a single-level orphan under `_user:` if no template home
194
+ exists.
176
195
  - Write with 2-space indent, no tabs, no trailing whitespace.
177
196
  - Never commit — `.agent-settings.yml` is git-ignored.
178
197
  - If a legacy flat `.agent-settings` (key=value) is still present,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Shared agent configuration \u2014 skills, rules, commands, guidelines, and templates for AI coding tools",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -76,9 +76,20 @@ Commands:
76
76
  Writes .augment/state/onboarding-gate.json from .agent-settings.yml
77
77
  context-hygiene:hook PostToolUse hook entry point (read JSON from stdin)
78
78
  Maintains .augment/state/context-hygiene.json (turn count, loop, freshness)
79
+ dispatch:hook Universal hook dispatcher (Phase 7, hook-architecture-v1.md)
80
+ Usage: dispatch:hook --platform <name> --event <event> [--native-event <native>]
81
+ Reads scripts/hook_manifest.yaml and runs the resolved concern chain.
82
+ hooks:status Print the runtime hook matrix (per-platform install + bindings)
83
+ Flags: --format json|table, --strict (CI), --project-root <path>
79
84
  telemetry:record Append one artefact-engagement event (default-off)
80
85
  telemetry:status Print artefact-engagement telemetry status (read-only)
81
86
  telemetry:report Aggregate the engagement log into a quartile report
87
+ council:estimate Pre-call council cost preview (no API call, no spend)
88
+ Usage: council:estimate <question> [--input-mode prompt|roadmap]
89
+ council:run Run the council. Requires --confirm to spend.
90
+ Usage: council:run <question> --output <path> --confirm
91
+ council:render Re-render a saved council responses JSON to markdown
92
+ Usage: council:render <responses.json>
82
93
  help Show this help
83
94
  --version, -V Print package version
84
95
 
@@ -102,6 +113,9 @@ Examples:
102
113
  ./agent-config telemetry:status --format json
103
114
  ./agent-config telemetry:report --since 30d --top 20
104
115
  ./agent-config telemetry:report --since 7d --format json --top 0
116
+ ./agent-config council:estimate prompt.txt
117
+ ./agent-config council:run prompt.txt --output agents/council-sessions/out.json --confirm
118
+ ./agent-config council:render agents/council-sessions/out.json
105
119
 
106
120
  All commands operate on the CURRENT DIRECTORY (your project root).
107
121
  The CLI is strictly consumer-facing. Maintainer tasks live in Taskfile.yml.
@@ -343,6 +357,20 @@ cmd_context_hygiene_hook() {
343
357
  exec python3 "$script" "$@"
344
358
  }
345
359
 
360
+ cmd_dispatch_hook() {
361
+ require_python3
362
+ local script
363
+ script="$(resolve_script "scripts/hooks/dispatch_hook.py")" || return 1
364
+ exec python3 "$script" "$@"
365
+ }
366
+
367
+ cmd_hooks_status() {
368
+ require_python3
369
+ local script
370
+ script="$(resolve_script "scripts/hooks_status.py")" || return 1
371
+ exec python3 "$script" "$@"
372
+ }
373
+
346
374
  cmd_chat_history_checkpoint() {
347
375
  require_python3
348
376
  local script
@@ -438,6 +466,17 @@ cmd_keys_install_openai() {
438
466
  exec bash "$script" "$@"
439
467
  }
440
468
 
469
+ # Council CLI — non-interactive wrapper around scripts.ai_council.orchestrator.
470
+ # Three subcommands share one Python entry point; we forward the subcommand
471
+ # verb so `./agent-config council:run --confirm` lands on `council_cli.py run`.
472
+ cmd_council() {
473
+ require_python3
474
+ local sub="$1"; shift || true
475
+ local script
476
+ script="$(resolve_script "scripts/council_cli.py")" || return 1
477
+ exec env PYTHONPATH="$PACKAGE_ROOT" python3 "$script" "$sub" "$@"
478
+ }
479
+
441
480
  main() {
442
481
  local cmd="${1-}"
443
482
  [[ $# -gt 0 ]] && shift || true
@@ -466,9 +505,14 @@ main() {
466
505
  roadmap-progress:hook) cmd_roadmap_progress_hook "$@" ;;
467
506
  onboarding-gate:hook) cmd_onboarding_gate_hook "$@" ;;
468
507
  context-hygiene:hook) cmd_context_hygiene_hook "$@" ;;
508
+ dispatch:hook) cmd_dispatch_hook "$@" ;;
509
+ hooks:status) cmd_hooks_status "$@" ;;
469
510
  telemetry:record) cmd_telemetry_record "$@" ;;
470
511
  telemetry:status) cmd_telemetry_status "$@" ;;
471
512
  telemetry:report) cmd_telemetry_report "$@" ;;
513
+ council:estimate) cmd_council estimate "$@" ;;
514
+ council:run) cmd_council run "$@" ;;
515
+ council:render) cmd_council render "$@" ;;
472
516
  help|--help|-h|"") usage ;;
473
517
  --version|-V) print_version ;;
474
518
  *)
@@ -38,11 +38,11 @@ class CouncilContext:
38
38
  # placeholder. Order matters — the most specific pattern goes first.
39
39
 
40
40
  _REDACTION_LINE_PATTERNS: list[tuple[re.Pattern[str], str]] = [
41
- (re.compile(r".*~?/?\.config/agent-config/[^/\s]+\.key.*"),
41
+ (re.compile(r"~?/?\.config/agent-config/[^/\s]+\.key"),
42
42
  "[redacted: agent-config key path]"),
43
- (re.compile(r"^\s*Authorization:\s.*", re.IGNORECASE),
43
+ (re.compile(r"^\s*Authorization:\s", re.IGNORECASE),
44
44
  "[redacted: Authorization header]"),
45
- (re.compile(r"(?i).*(api[_-]?key|secret|token|password)\s*[:=].*"),
45
+ (re.compile(r"(?i)(api[_-]?key|secret|token|password)\s*[:=]"),
46
46
  "[redacted: secret-like assignment]"),
47
47
  (re.compile(r"sk-ant-[A-Za-z0-9_\-]{8,}"), "[redacted: anthropic-key-like token]"),
48
48
  (re.compile(r"sk-[A-Za-z0-9_\-]{20,}"), "[redacted: openai-key-like token]"),
@@ -34,6 +34,16 @@ OPENAI_KEY_PATH = Path.home() / ".config" / "agent-config" / "openai.key"
34
34
  DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-5"
35
35
  DEFAULT_OPENAI_MODEL = "gpt-4o"
36
36
 
37
+ # OpenAI reasoning models (o1, o3, o4 families) reject `max_tokens` and the
38
+ # `system` role; they require `max_completion_tokens` and accept only `user`
39
+ # (and `developer`) messages.
40
+ _REASONING_PREFIXES = ("o1", "o3", "o4")
41
+
42
+
43
+ def _is_reasoning_model(model: str) -> bool:
44
+ name = model.lower()
45
+ return any(name == p or name.startswith(p + "-") for p in _REASONING_PREFIXES)
46
+
37
47
 
38
48
  class KeyGateError(RuntimeError):
39
49
  """Raised when a provider key file violates the 0600 contract."""
@@ -189,15 +199,21 @@ class OpenAIClient(ExternalAIClient):
189
199
 
190
200
  def ask(self, system_prompt: str, user_prompt: str, max_tokens: int = 1024) -> CouncilResponse:
191
201
  t0 = time.monotonic()
202
+ kwargs: dict[str, object] = {"model": self.model}
203
+ if _is_reasoning_model(self.model):
204
+ # o1/o3/o4 reasoning models reject `max_tokens` and `system` role.
205
+ kwargs["max_completion_tokens"] = max_tokens
206
+ kwargs["messages"] = [
207
+ {"role": "user", "content": f"{system_prompt}\n\n---\n\n{user_prompt}"},
208
+ ]
209
+ else:
210
+ kwargs["max_tokens"] = max_tokens
211
+ kwargs["messages"] = [
212
+ {"role": "system", "content": system_prompt},
213
+ {"role": "user", "content": user_prompt},
214
+ ]
192
215
  try:
193
- response = self._client.chat.completions.create(
194
- model=self.model,
195
- max_tokens=max_tokens,
196
- messages=[
197
- {"role": "system", "content": system_prompt},
198
- {"role": "user", "content": user_prompt},
199
- ],
200
- )
216
+ response = self._client.chat.completions.create(**kwargs)
201
217
  except Exception as exc: # noqa: BLE001 - normalise all SDK errors
202
218
  return CouncilResponse(
203
219
  provider=self.name, model=self.model, text="",
@@ -8,6 +8,28 @@
8
8
  > `scripts/check_one_off_location.py` enforces that no new
9
9
  > `_one_off_*.py` lands outside this folder.
10
10
 
11
+ ## Going forward — use the CLI, not new one-offs
12
+
13
+ > **Canonical pattern (Phase 6.7+):** new council runs go through
14
+ > `./agent-config council:{estimate,run,render}`. The CLI handles
15
+ > bundling, redaction, the cost gate, the `0600` key contract, the
16
+ > `enabled` check, and session persistence — every concern these
17
+ > archived one-offs reimplemented inline.
18
+ >
19
+ > ```bash
20
+ > ./agent-config council:estimate <question.md>
21
+ > ./agent-config council:run <question.md> \
22
+ > --output agents/council-sessions/<UTC-ts>.json --confirm
23
+ > ./agent-config council:render agents/council-sessions/<UTC-ts>.json
24
+ > ```
25
+ >
26
+ > Wire-level access (`scripts.ai_council.orchestrator`,
27
+ > `scripts.ai_council.bundler`) is still public for tests and library
28
+ > use, but writing a new `_one_off_*.py` purely to fan out to the
29
+ > council members is **not** the path. The scripts below are kept as
30
+ > historical evidence of the runs that produced specific roadmap
31
+ > decisions; they are not a template for new work.
32
+
11
33
  ## Lifecycle rule (uniform — Phase 0.2 of context-layer-maturity)
12
34
 
13
35
  > A one-off is **archived**, never deleted. The session manifest under
@@ -1,14 +1,19 @@
1
- """One-off Phase-1 round-trip runner.
1
+ """One-off Phase-1 round-trip runner — HISTORICAL ARCHIVE.
2
2
 
3
- Used exactly once to generate the evidence artefact required to lift
4
- the capture-only fence on `road-to-ai-council.md` Phase 2+ and the
5
- end-to-end verification on `road-to-council-modes.md` Phase 2a.
3
+ Going forward, council runs go through the CLI:
6
4
 
7
- Not part of the public CLI surface — `/council` remains the supported
8
- entry point. This script is committed under `scripts/ai_council/` so
9
- the evidence is reproducible from the git history alone.
5
+ ./agent-config council:estimate <question.md>
6
+ ./agent-config council:run <question.md> \
7
+ --output agents/council-sessions/<UTC-ts>.json --confirm
8
+ ./agent-config council:render agents/council-sessions/<UTC-ts>.json
10
9
 
11
- Invocation:
10
+ This script predates `scripts/council_cli.py` (Phase 6.7) and is kept
11
+ only as the evidence artefact that lifted the capture-only fence on
12
+ `road-to-ai-council.md` Phase 2+ and the end-to-end verification on
13
+ `road-to-council-modes.md` Phase 2a. Do **not** copy it as a template
14
+ for new one-offs — write a question file and use the CLI instead.
15
+
16
+ Invocation (historical):
12
17
  .venv/bin/python -m scripts.ai_council._one_off_roundtrip
13
18
  """
14
19
  from __future__ import annotations