@event4u/agent-config 9.27.0 → 9.28.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 (138) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +12 -11
  3. package/CHANGELOG.md +99 -0
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/analyze/conformance.md +24 -0
  6. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
  7. package/dist/agent-src/rules/artifact-engagement-recording.md +4 -1
  8. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  9. package/dist/agent-src/rules/brand-source-of-truth.md +33 -6
  10. package/dist/agent-src/rules/context-hygiene.md +3 -1
  11. package/dist/agent-src/rules/council-availability.md +93 -0
  12. package/dist/agent-src/rules/cross-source-consistency.md +3 -0
  13. package/dist/agent-src/rules/delegation-policy.md +29 -5
  14. package/dist/agent-src/rules/design-fidelity.md +3 -0
  15. package/dist/agent-src/rules/evaluator-independence.md +2 -1
  16. package/dist/agent-src/rules/external-code-graph-interop.md +2 -1
  17. package/dist/agent-src/rules/finance-safety-floor.md +1 -1
  18. package/dist/agent-src/rules/git-history-discipline.md +1 -1
  19. package/dist/agent-src/rules/history-discipline.md +3 -1
  20. package/dist/agent-src/rules/legal-safety-floor.md +1 -1
  21. package/dist/agent-src/rules/no-pr-progress-comments.md +3 -1
  22. package/dist/agent-src/rules/onboarding-gate.md +3 -1
  23. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +4 -1
  24. package/dist/agent-src/rules/role-mode-adherence.md +1 -1
  25. package/dist/agent-src/rules/scale-discipline.md +3 -1
  26. package/dist/agent-src/rules/self-repair-loop.md +103 -0
  27. package/dist/agent-src/rules/session-canary.md +2 -1
  28. package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
  29. package/dist/agent-src/rules/token-budget-discipline.md +3 -1
  30. package/dist/agent-src/rules/tool-safety.md +5 -1
  31. package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
  32. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  33. package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
  34. package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
  35. package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
  36. package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
  37. package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
  38. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  39. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  40. package/dist/agent-src/templates/AGENTS.md +6 -10
  41. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  42. package/dist/cli/registry.js +7 -0
  43. package/dist/cli/registry.js.map +1 -1
  44. package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
  45. package/dist/cli-delegate/{chunk-BCPIOAGV.js → chunk-2BE5I2MN.js} +8 -8
  46. package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
  47. package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
  48. package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
  49. package/dist/cli-delegate/{chunk-3KJIMFTB.js → chunk-PQSAQKHW.js} +466 -304
  50. package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
  51. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  52. package/dist/cli-delegate/cmd_brand_status.js +134 -0
  53. package/dist/cli-delegate/cmd_conformance.js +7 -7
  54. package/dist/cli-delegate/cmd_converge.js +3 -3
  55. package/dist/cli-delegate/cmd_doctor.js +4 -4
  56. package/dist/cli-delegate/cmd_explain.js +80 -330
  57. package/dist/cli-delegate/cmd_export.js +5 -5
  58. package/dist/cli-delegate/cmd_fleet.js +3 -3
  59. package/dist/cli-delegate/cmd_handoff.js +2 -2
  60. package/dist/cli-delegate/cmd_mcp_available.js +433 -0
  61. package/dist/cli-delegate/cmd_migrate.js +3 -3
  62. package/dist/cli-delegate/cmd_packs_active.js +153 -0
  63. package/dist/cli-delegate/cmd_preflight.js +1 -1
  64. package/dist/cli-delegate/cmd_prune.js +5 -5
  65. package/dist/cli-delegate/cmd_route_audit.js +8 -8
  66. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  67. package/dist/cli-delegate/cmd_settings_get.js +272 -0
  68. package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
  69. package/dist/cli-delegate/cmd_settings_set.js +7 -2967
  70. package/dist/cli-delegate/cmd_sync.js +5 -5
  71. package/dist/cli-delegate/cmd_uninstall.js +5 -5
  72. package/dist/cli-delegate/cmd_update.js +4 -4
  73. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  74. package/dist/cli-delegate/cmd_validate.js +5 -5
  75. package/dist/cli-delegate/cmd_versions.js +4 -4
  76. package/dist/discovery/deprecation-report.md +1 -1
  77. package/dist/discovery/discovery-manifest.json +100 -40
  78. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  79. package/dist/discovery/discovery-manifest.summary.md +3 -3
  80. package/dist/discovery/orphan-report.md +1 -1
  81. package/dist/discovery/packs.json +7 -5
  82. package/dist/discovery/trust-report.md +11 -11
  83. package/dist/discovery/workspaces.json +6 -4
  84. package/dist/hooks/dispatch.js +9004 -6132
  85. package/dist/install/install.mjs +697 -507
  86. package/dist/mcp/registry-manifest.json +3 -3
  87. package/dist/mcp/server.json +2 -2
  88. package/dist/mcp/server.mjs +127 -20
  89. package/dist/router.json +1 -1
  90. package/dist/scripts/_lib/agent_settings.js +80 -15
  91. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  92. package/docs/contracts/settings-classes.md +29 -8
  93. package/docs/guidelines/agent-infra/memory-access.md +14 -3
  94. package/package.json +1 -1
  95. package/src/agent-src/templates/AGENTS.md +6 -10
  96. package/src/config/budgets.yml +29 -0
  97. package/src/config/evaluator-budgets.json +4 -4
  98. package/src/config/gate-coverage.yml +26 -0
  99. package/src/scripts/_cli/cmd_brand_status.ts +174 -0
  100. package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
  101. package/src/scripts/_cli/cmd_packs_active.ts +225 -0
  102. package/src/scripts/_cli/cmd_settings_get.ts +324 -0
  103. package/src/scripts/_dispatch.bash +57 -0
  104. package/src/scripts/_lib/agent_settings.ts +82 -16
  105. package/src/scripts/_lib/carrier_divergence.ts +135 -0
  106. package/src/scripts/_lib/host_capability.ts +84 -1
  107. package/src/scripts/_lib/prompt_shape.ts +88 -0
  108. package/src/scripts/_lib/review_skipped_record.ts +102 -0
  109. package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
  110. package/src/scripts/_lib/self_repair.ts +342 -0
  111. package/src/scripts/_lib/self_repair_store.ts +100 -0
  112. package/src/scripts/_lib/transcript_entry.ts +80 -0
  113. package/src/scripts/check_branch_freshness.ts +255 -0
  114. package/src/scripts/check_council_config_location.ts +125 -1
  115. package/src/scripts/check_standing_rule_delivery.ts +314 -0
  116. package/src/scripts/config/profiles.ts +13 -3
  117. package/src/scripts/conformance_scan.ts +272 -24
  118. package/src/scripts/council_availability_hook.ts +188 -0
  119. package/src/scripts/council_cli.ts +126 -7
  120. package/src/scripts/hook_manifest.yaml +107 -26
  121. package/src/scripts/hooks/block_no_verify.ts +39 -2
  122. package/src/scripts/hooks/block_unauthorized_git.ts +156 -7
  123. package/src/scripts/hooks/concern_registry.ts +10 -0
  124. package/src/scripts/hooks/delegation_nudge_hook.ts +450 -0
  125. package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
  126. package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
  127. package/src/scripts/install.ts +151 -0
  128. package/src/scripts/language_mirror_hook.ts +30 -37
  129. package/src/scripts/measure_scope_dedup.ts +19 -13
  130. package/src/scripts/onboarding_gate_hook.ts +40 -1
  131. package/src/scripts/orchestration_record.ts +4 -1
  132. package/src/scripts/preamble_byte_census.ts +9 -20
  133. package/src/scripts/report_carrier_divergence.ts +364 -0
  134. package/src/scripts/report_skill_activation.ts +257 -0
  135. package/src/scripts/report_skill_obligation_violations.ts +579 -0
  136. package/src/scripts/routing_doctor.ts +77 -6
  137. package/src/scripts/self_repair_cli.ts +240 -0
  138. package/src/scripts/self_repair_hook.ts +241 -0
@@ -0,0 +1,324 @@
1
+ /**
2
+ * `agent-config settings:get <key>` — the value, and the file it came from.
3
+ *
4
+ * Phase 2.2 of `road-to-capability-answerability`, and the general answer to
5
+ * five separate settings-key defects rather than five prose edits. Before this
6
+ * verb, "what is this setting, and which file did it come from" was unanswerable
7
+ * by any command: `settings:check` validates, `settings:sync` merges,
8
+ * `settings:set` writes, and the one function that computes exactly
9
+ * `[key, value, source_path]` — `iter_setting_overrides` — had a test and no
10
+ * production caller at all. This exposes it.
11
+ *
12
+ * Read-only BY CONSTRUCTION, which is a design constraint and not a promise:
13
+ * this module imports no writer, opens no file for writing, and has no flag
14
+ * that could. The roadmap's Risk 3 names the failure it is avoiding — a read
15
+ * verb inviting a write verb until the C-class fence in `settings:set` has a
16
+ * general bypass beside it.
17
+ *
18
+ * Four things are reported, and the last two are why it is a probe:
19
+ *
20
+ * - **the effective value**, resolved through the same cascade the loader walks;
21
+ * - **the winning source path**, plus every layer that set the key, so a
22
+ * surprising value can be traced to the file that caused it;
23
+ * - **absent ≠ default**, for the nine carved-out keys where a reader resolves
24
+ * an absent key to something other than the template's default. Reporting
25
+ * "not set" for `quality.local_auto_run` without saying that absent DISARMS
26
+ * the gate the template arms would be a true statement that misleads;
27
+ * - **the silent whitelist drop**. `load_agent_settings` filters the user-global
28
+ * layer through `MERGEABLE_KEYS` and discards everything else without a word.
29
+ * A user who sets a non-whitelisted key user-globally gets no error, no
30
+ * warning, and no effect. This verb says so.
31
+ *
32
+ * Exit codes: `0` answered (including "not set anywhere", which is an answer) ·
33
+ * `2` usage error.
34
+ */
35
+
36
+ import * as fs from 'node:fs';
37
+ import * as path from 'node:path';
38
+ import process from 'node:process';
39
+ import { fileURLToPath, pathToFileURL } from 'node:url';
40
+
41
+ import { load as yamlLoad } from 'js-yaml';
42
+
43
+ import {
44
+ iter_setting_overrides,
45
+ load_agent_settings,
46
+ MERGEABLE_KEYS,
47
+ USER_GLOBAL_FILENAME,
48
+ } from '../_lib/agent_settings.js';
49
+ import { resolve_with_fallback, write_target } from '../_lib/user_global_paths.js';
50
+ import { resolvePackageRoot } from '../_lib/package_root.js';
51
+ import { SETTINGS_CARVE_OUT, type CarveOutKey } from '../../shared/settingsCarveOut.js';
52
+ import {
53
+ buildSettingsClassIndex,
54
+ classOfPath,
55
+ getSettingsLeaf,
56
+ parseSettingsClassRows,
57
+ type SettingsClass,
58
+ } from '../../shared/settingsClasses.js';
59
+
60
+ const PACKAGE_ROOT = resolvePackageRoot(import.meta.url);
61
+
62
+ /** Where the class contract ships, relative to the package root. */
63
+ const CONTRACT_RELATIVE = 'docs/contracts/settings-classes.md';
64
+
65
+ /** Where the shipped defaults live, relative to the package root. */
66
+ const TEMPLATE_RELATIVE = path.join('src', 'config', 'agent-settings.template.yml');
67
+
68
+ /**
69
+ * Key shapes whose VALUE is never printed, only its presence.
70
+ *
71
+ * A general settings reader is a general secret reader unless it is told
72
+ * otherwise, and the settings tree really does carry credentials — the
73
+ * user-global file on the machine this was built on holds
74
+ * `secrets.link_encryption_key`. The roadmap does not ask for this; it is the
75
+ * cross-cutting control the surface implies, and adding it after the verb
76
+ * shipped would mean the leak existed first.
77
+ *
78
+ * Matched on the dotted path, so both `secrets.*` and any leaf named like a
79
+ * credential are covered regardless of where they sit.
80
+ */
81
+ const SECRET_KEY_RE = /(^|\.)(secrets?)(\.|$)|(^|\.)[a-z0-9_]*(api_key|token|password|passwd|secret|credential|private_key|encryption_key)$/i;
82
+
83
+ /** `true` when the value for this dotted key must be masked in output. */
84
+ export function isSecretKey(key: string): boolean {
85
+ return SECRET_KEY_RE.test(key);
86
+ }
87
+
88
+ /** The value as printed: masked for credential-shaped keys, verbatim otherwise. */
89
+ export function displayValue(key: string, value: unknown): unknown {
90
+ if (value === undefined || value === null) return value;
91
+ return isSecretKey(key) ? '«redacted — credential-shaped key»' : value;
92
+ }
93
+
94
+ /** One layer that set the key, in cascade order (later wins). */
95
+ export interface SettingsLayer {
96
+ value: unknown;
97
+ file: string;
98
+ }
99
+
100
+ export interface SettingsGetOptions {
101
+ key: string;
102
+ cwd: string;
103
+ packageRoot: string;
104
+ json: boolean;
105
+ }
106
+
107
+ export interface SettingsGetResult {
108
+ code: 0 | 2;
109
+ out: string[];
110
+ err: string[];
111
+ }
112
+
113
+ /**
114
+ * Every layer that set `key`, in cascade order.
115
+ *
116
+ * Delegates to `iter_setting_overrides` rather than re-walking the cascade:
117
+ * a probe that computed its own answer could disagree with the loader, and a
118
+ * settings probe that disagrees with the loader is worse than no probe.
119
+ */
120
+ export function layersFor(key: string, cwd: string): SettingsLayer[] {
121
+ const layers: SettingsLayer[] = [];
122
+ for (const [dotted, value, file] of iter_setting_overrides({ cwd })) {
123
+ if (dotted === key) layers.push({ value, file });
124
+ }
125
+ return layers;
126
+ }
127
+
128
+ /** The template's shipped default for `key`, or `undefined` when it has none. */
129
+ export function templateDefault(packageRoot: string, key: string): unknown {
130
+ let parsed: unknown;
131
+ try {
132
+ parsed = yamlLoad(fs.readFileSync(path.join(packageRoot, TEMPLATE_RELATIVE), 'utf-8'));
133
+ } catch {
134
+ return undefined;
135
+ }
136
+ return getSettingsLeaf(parsed, key);
137
+ }
138
+
139
+ /** The carve-out row for `key`, when absent does not mean the template default. */
140
+ export function carveOutFor(key: string): CarveOutKey | undefined {
141
+ return SETTINGS_CARVE_OUT.find((c) => c.key === key);
142
+ }
143
+
144
+ /**
145
+ * `true` when the key is present in the user-global file but is NOT whitelisted,
146
+ * i.e. the loader read it and threw it away.
147
+ *
148
+ * Returns the resolved user-global path alongside so the report can name the
149
+ * file the user actually edited — telling somebody their value was dropped
150
+ * without telling them which file it was dropped from is half an answer.
151
+ */
152
+ export function userGlobalDrop(key: string): { dropped: boolean; file: string } {
153
+ const file = resolve_with_fallback(USER_GLOBAL_FILENAME) ?? write_target(USER_GLOBAL_FILENAME);
154
+ if (MERGEABLE_KEYS.includes(key)) return { dropped: false, file };
155
+ let raw: unknown;
156
+ try {
157
+ raw = yamlLoad(fs.readFileSync(file, 'utf-8'));
158
+ } catch {
159
+ return { dropped: false, file };
160
+ }
161
+ return { dropped: getSettingsLeaf(raw, key) !== undefined, file };
162
+ }
163
+
164
+ /**
165
+ * The class governing `key`, or `null` when the contract is unreadable or has
166
+ * no row on the path.
167
+ *
168
+ * `classOfPath` rather than an exact lookup: a class-C key whose value is a map
169
+ * has children that never appear as their own rows, and reporting them as
170
+ * unclassified would understate the fence around them.
171
+ */
172
+ function _classFor(packageRoot: string, key: string): SettingsClass | null {
173
+ let text: string;
174
+ try {
175
+ text = fs.readFileSync(path.join(packageRoot, CONTRACT_RELATIVE), 'utf-8');
176
+ } catch {
177
+ return null;
178
+ }
179
+ return classOfPath(buildSettingsClassIndex(parseSettingsClassRows(text)), key) ?? null;
180
+ }
181
+
182
+ export function runSettingsGet(opts: SettingsGetOptions): SettingsGetResult {
183
+ const out: string[] = [];
184
+ const err: string[] = [];
185
+
186
+ const merged = load_agent_settings({ cwd: opts.cwd });
187
+ const effective = getSettingsLeaf(merged, opts.key);
188
+ const layers = layersFor(opts.key, opts.cwd);
189
+ const winning = layers.length > 0 ? (layers[layers.length - 1] as SettingsLayer) : null;
190
+ const cls = _classFor(opts.packageRoot, opts.key);
191
+ const fallback = templateDefault(opts.packageRoot, opts.key);
192
+ const carveOut = carveOutFor(opts.key);
193
+ const drop = userGlobalDrop(opts.key);
194
+
195
+ if (opts.json) {
196
+ out.push(
197
+ JSON.stringify(
198
+ {
199
+ key: opts.key,
200
+ value: displayValue(opts.key, effective) ?? null,
201
+ redacted: isSecretKey(opts.key),
202
+ set: winning !== null,
203
+ source: winning?.file ?? null,
204
+ layers: layers.map((l) => ({ value: displayValue(opts.key, l.value), file: l.file })),
205
+ class: cls,
206
+ template_default: fallback ?? null,
207
+ absent_is_not_default: carveOut
208
+ ? { resolves_to: carveOut.absentResolvesTo, reader: carveOut.reader }
209
+ : null,
210
+ user_global_dropped: drop.dropped ? drop.file : null,
211
+ },
212
+ null,
213
+ 2,
214
+ ),
215
+ );
216
+ return { code: 0, out, err };
217
+ }
218
+
219
+ if (winning !== null) {
220
+ out.push(`${opts.key} = ${JSON.stringify(displayValue(opts.key, effective))}`);
221
+ out.push(` source ${winning.file}`);
222
+ } else {
223
+ out.push(`${opts.key} — not set in any settings file`);
224
+ out.push(
225
+ ` default ${
226
+ fallback === undefined
227
+ ? '(none in the template)'
228
+ : JSON.stringify(displayValue(opts.key, fallback))
229
+ }`,
230
+ );
231
+ }
232
+ out.push(` class ${cls ?? '(unclassified — no row in the class contract)'}`);
233
+
234
+ if (layers.length > 1) {
235
+ out.push(' layers (cascade order, last wins)');
236
+ for (const layer of layers) {
237
+ out.push(` ${JSON.stringify(displayValue(opts.key, layer.value))} ← ${layer.file}`);
238
+ }
239
+ }
240
+
241
+ if (winning === null && carveOut !== undefined) {
242
+ out.push(
243
+ '',
244
+ '⚠️ Absent is NOT the template default for this key.',
245
+ ` A reader resolves the absent key to: ${carveOut.absentResolvesTo}`,
246
+ ` Reader: ${carveOut.reader}`,
247
+ ' So "not set" and "set to the default" behave differently here.',
248
+ );
249
+ }
250
+
251
+ if (drop.dropped) {
252
+ out.push(
253
+ '',
254
+ '⚠️ This key IS present in your user-global file and is being discarded.',
255
+ ` File: ${drop.file}`,
256
+ ' Only the keys on the MERGEABLE_KEYS whitelist cascade from user-global into',
257
+ ' a project; everything else is filtered out silently by load_agent_settings.',
258
+ ' Set it in the project settings file, or the value has no effect anywhere.',
259
+ );
260
+ }
261
+
262
+ return { code: 0, out, err };
263
+ }
264
+
265
+ interface ParsedArgv {
266
+ ok: boolean;
267
+ message?: string;
268
+ key?: string;
269
+ json?: boolean;
270
+ }
271
+
272
+ export function parseArgv(argv: readonly string[]): ParsedArgv {
273
+ const positional: string[] = [];
274
+ let json = false;
275
+ for (const a of argv) {
276
+ if (a === '--json') {
277
+ json = true;
278
+ } else if (a === '-h' || a === '--help') {
279
+ return { ok: false, message: 'usage: agent-config settings:get <key> [--json]' };
280
+ } else if (a.startsWith('--')) {
281
+ return { ok: false, message: `unknown flag: ${a}` };
282
+ } else {
283
+ positional.push(a);
284
+ }
285
+ }
286
+ if (positional.length !== 1) {
287
+ return { ok: false, message: 'usage: agent-config settings:get <key> [--json]' };
288
+ }
289
+ return { ok: true, key: positional[0] as string, json };
290
+ }
291
+
292
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
293
+ const parsed = parseArgv(argv);
294
+ if (!parsed.ok) {
295
+ process.stderr.write(`${parsed.message ?? 'usage error'}\n`);
296
+ return 2;
297
+ }
298
+ const result = runSettingsGet({
299
+ key: parsed.key as string,
300
+ cwd: process.cwd(),
301
+ packageRoot: PACKAGE_ROOT,
302
+ json: parsed.json === true,
303
+ });
304
+ for (const line of result.out) process.stdout.write(`${line}\n`);
305
+ for (const line of result.err) process.stderr.write(`${line}\n`);
306
+ return result.code;
307
+ }
308
+
309
+ function _isCliEntry(): boolean {
310
+ if (process.argv[1] === undefined) return false;
311
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
312
+ if (import.meta.url === argvUrl) return true;
313
+ try {
314
+ return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
315
+ } catch {
316
+ return false;
317
+ }
318
+ }
319
+
320
+ if (_isCliEntry()) {
321
+ process.exitCode = main();
322
+ }
323
+
324
+ export { CONTRACT_RELATIVE, PACKAGE_ROOT, TEMPLATE_RELATIVE };
@@ -73,6 +73,12 @@ TS-shell native (run via the installed `agent-config` binary):
73
73
  ui:serve Start the local UI server (127.0.0.1, auto-picked port)
74
74
  workspaces ls List workspaces from the discovery manifest
75
75
  packs ls List packs from the discovery manifest
76
+ brand:status Is a consumer brand layer present, and at which of the
77
+ four canonical tokens.json paths. Flags a dot-prefixed
78
+ .tokens.json, which nothing reads.
79
+ packs:active Which packs are active HERE, and from which file.
80
+ Names the degraded case (settings file without a
81
+ profile.id → zero packs) that nothing else reports.
76
82
  commands [ls|explain] List/explain the command surface from the discovery manifest
77
83
  mcp-server Turnkey read-only stdio MCP server (no repo clone; ADR-085)
78
84
  doctor-shell Probe the TS-shell environment
@@ -153,6 +159,13 @@ Tier 1 — power-user (release shape, audit, migration):
153
159
  Usage: council:run <question> --output <path> --confirm
154
160
  council:render Re-render a saved council responses JSON to markdown
155
161
  Usage: council:render <responses.json>
162
+ council:status Report whether an AI council is configured, and from where
163
+ (no API call, no spend)
164
+ self-repair:status List queued agent-config defect records (read-only)
165
+ self-repair:release Publish one defect record — a PR when the fix can be
166
+ pushed, else an issue. Running it is your Hard-Floor
167
+ confirmation.
168
+ Usage: self-repair:release <fingerprint> [--dry-run]
156
169
  EOF
157
170
  fi
158
171
 
@@ -163,6 +176,9 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
163
176
  mcp:render Render mcp.json → .cursor/mcp.json, .windsurf/mcp.json
164
177
  (pass --claude-desktop to also write user-scope config)
165
178
  mcp:check Dry-run mcp:render; exit non-zero if targets are stale
179
+ mcp:available What is declared in mcp.json, whether each server's
180
+ command is launchable, and the separate static tool
181
+ registry. No handshake — says so in the output.
166
182
  mcp:setup Verify the tsx runtime + MCP server module and
167
183
  print the client config snippet
168
184
  (one-line MCP server onboarding; idempotent)
@@ -199,6 +215,9 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
199
215
  ~/.event4u/agent-config/ (the global-only consumer surface,
200
216
  ADR-020). Idempotent; --force overwrites a non-empty global
201
217
  file, --dry-run lists intended copies with zero writes.
218
+ settings:get Read one setting: its value, the file it came from, its
219
+ class, and whether "absent" differs from the default.
220
+ Usage: settings:get <key> [--json]
202
221
  settings:set Set one setting in the global file. Refuses every class-C
203
222
  (guarded) key from docs/contracts/settings-classes.md, and
204
223
  refuses everything when that contract is unreadable.
@@ -595,6 +614,26 @@ cmd_sessions_claim() {
595
614
  exec_ts "$PACKAGE_ROOT/src/scripts/sessions_cli.ts" claim "$@"
596
615
  }
597
616
 
617
+ # Capability probes (road-to-capability-answerability). All three resolve from
618
+ # PACKAGE_ROOT rather than the consumer projection: each imports package-internal
619
+ # libraries under src/scripts/_lib/ or src/shared/, which are never projected into
620
+ # dist/agent-src/scripts/. Same shape as cmd_settings_set.
621
+ cmd_packs_active() {
622
+ exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_packs_active.ts" "$@"
623
+ }
624
+
625
+ cmd_settings_get() {
626
+ exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_settings_get.ts" "$@"
627
+ }
628
+
629
+ cmd_mcp_available() {
630
+ exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_mcp_available.ts" "$@"
631
+ }
632
+
633
+ cmd_brand_status() {
634
+ exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_brand_status.ts" "$@"
635
+ }
636
+
598
637
  cmd_roadmap_progress() {
599
638
  local script
600
639
  script="$(resolve_script "dist/agent-src/scripts/update_roadmap_progress.ts" ".augment/scripts/update_roadmap_progress.ts")"
@@ -1047,6 +1086,17 @@ cmd_council() {
1047
1086
  exec_ts "$script" "$sub" "$@"
1048
1087
  }
1049
1088
 
1089
+ # `self-repair:{status,release}` — the queued-defect reader and the single
1090
+ # gated outward step. `release` publishes (a PR, else an issue); running it IS
1091
+ # the user's Hard-Floor confirmation, which is why nothing else in the loop
1092
+ # may publish on its own.
1093
+ cmd_self_repair() {
1094
+ local sub="$1"; shift || true
1095
+ local script
1096
+ script="$(resolve_script "src/scripts/self_repair_cli.ts")" || return 1
1097
+ exec_ts "$script" "$sub" "$@"
1098
+ }
1099
+
1050
1100
  # `use --profile=<id>` — switch the active experience/profile. Writes
1051
1101
  # profile.id into the canonical .agent-settings.yml; the explicit
1052
1102
  # profile-switch seam named by ADR-040 (road-to-6.0.0-a Step 8).
@@ -1270,6 +1320,10 @@ main() {
1270
1320
  conformance:behavior) cmd_conformance_behavior "$@" ;;
1271
1321
  sessions:list) cmd_sessions_list "$@" ;;
1272
1322
  sessions:claim) cmd_sessions_claim "$@" ;;
1323
+ packs:active) cmd_packs_active "$@" ;;
1324
+ settings:get) cmd_settings_get "$@" ;;
1325
+ mcp:available) cmd_mcp_available "$@" ;;
1326
+ brand:status) cmd_brand_status "$@" ;;
1273
1327
  roadmap:progress) cmd_roadmap_progress "$@" ;;
1274
1328
  roadmap:progress-check) cmd_roadmap_progress_check "$@" ;;
1275
1329
  roadmap:archive) cmd_roadmap_archive "$@" ;;
@@ -1311,6 +1365,9 @@ main() {
1311
1365
  council:estimate) cmd_council estimate "$@" ;;
1312
1366
  council:run) cmd_council run "$@" ;;
1313
1367
  council:render) cmd_council render "$@" ;;
1368
+ council:status) cmd_council status "$@" ;;
1369
+ self-repair:status) cmd_self_repair status "$@" ;;
1370
+ self-repair:release) cmd_self_repair release "$@" ;;
1314
1371
  update) cmd_update "$@" ;;
1315
1372
  upgrade) cmd_upgrade "$@" ;;
1316
1373
  refresh) cmd_refresh "$@" ;;
@@ -191,13 +191,60 @@ export function DEFAULT_USER_GLOBAL_FILE(): string {
191
191
  return user_global_paths.write_target(USER_GLOBAL_FILENAME);
192
192
  }
193
193
 
194
- /** Return the active user-global settings path with legacy fallback. */
195
- function _resolve_user_global_file(): string {
196
- const found = user_global_paths.resolve_with_fallback(USER_GLOBAL_FILENAME);
197
- if (found !== null) {
198
- return found;
194
+ /**
195
+ * The canonical user-global settings file — `<event4u_root>/settings/.agent-settings.yml`.
196
+ *
197
+ * This is what the GUI settings route, the wizard route, and the installer all
198
+ * write, mirroring the project layer's `agents/settings/.agent-settings.yml`
199
+ * convention. Until `road-to-capability-answerability` 4.1 the loader did not
200
+ * read it at all: three writers, one reader, and the reader was looking at the
201
+ * other file. Every value a user set through the only surfaces that write it was
202
+ * therefore inert, silently — the exact shape of defect this roadmap closes.
203
+ */
204
+ export const USER_GLOBAL_CANONICAL_RELATIVE = 'settings/.agent-settings.yml';
205
+
206
+ /**
207
+ * Every user-global settings file to merge, in precedence order (later wins).
208
+ *
209
+ * Two entries, deliberately, rather than a replacement:
210
+ *
211
+ * - the FLAT `agent-settings.yml` stays first because it is not dead. It is the
212
+ * file `link_crypto.ts` reads directly for `secrets.link_encryption_key`, and
213
+ * dropping it from the cascade would change behaviour for anyone who put a
214
+ * whitelisted key there;
215
+ * - the CANONICAL `settings/.agent-settings.yml` is layered ON TOP, so the file
216
+ * the writers actually produce wins per key.
217
+ *
218
+ * Additive by construction: nothing that resolved before resolves differently
219
+ * unless the canonical file sets that key, and the user-global layer is filtered
220
+ * through {@link MERGEABLE_KEYS} downstream regardless — so the reachable blast
221
+ * radius is those keys, not the whole document.
222
+ */
223
+ export function user_global_settings_paths(): string[] {
224
+ const paths: string[] = [];
225
+ const flat = user_global_paths.resolve_with_fallback(USER_GLOBAL_FILENAME);
226
+ if (flat !== null) {
227
+ paths.push(flat);
199
228
  }
200
- return DEFAULT_USER_GLOBAL_FILE();
229
+ const canonical = user_global_paths.resolve_with_fallback(USER_GLOBAL_CANONICAL_RELATIVE);
230
+ if (canonical !== null) {
231
+ paths.push(canonical);
232
+ }
233
+ if (paths.length === 0) {
234
+ paths.push(DEFAULT_USER_GLOBAL_FILE());
235
+ }
236
+ return paths;
237
+ }
238
+
239
+ /**
240
+ * Return the active user-global settings path with legacy fallback.
241
+ *
242
+ * The HIGHEST-precedence existing file, so callers that want a single path (a
243
+ * report, an error message) name the one that actually decided the value.
244
+ */
245
+ function _resolve_user_global_file(): string {
246
+ const paths = user_global_settings_paths();
247
+ return paths[paths.length - 1] as string;
201
248
  }
202
249
 
203
250
  /**
@@ -206,11 +253,25 @@ function _resolve_user_global_file(): string {
206
253
  * the user-global file. Adding a key requires an ADR.
207
254
  */
208
255
  export const MERGEABLE_KEYS: readonly string[] = [
256
+ // `name`, `ide` and `personal.bot_icon` are the PRE-MIGRATION spellings.
257
+ // `install.ts` migrates `ide` → `personal.ide` and `pr_comment_bot_icon`
258
+ // into its `personal.` home, and this list was never moved with them — so
259
+ // the whitelist named keys the template does not have while the keys it
260
+ // does have were filtered out silently. A user setting either preference
261
+ // user-globally got no error, no warning, and no effect
262
+ // (road-to-capability-answerability 4.3, ADR-219).
263
+ //
264
+ // Both spellings are listed rather than replaced: a legacy file that still
265
+ // uses the old name keeps working, and nothing that resolved before
266
+ // resolves differently. `name` has no reader anywhere and no template key;
267
+ // it is kept only so this change stays purely additive.
209
268
  'name',
210
269
  'ide',
270
+ 'personal.ide',
211
271
  'rule_loading_tier',
212
272
  'memory.cadence',
213
273
  'personal.bot_icon',
274
+ 'personal.pr_comment_bot_icon',
214
275
  'personal.autonomy',
215
276
  'telegraph.speak_scope',
216
277
  // Knowledge-card global cross-project sharing is a USER-GLOBAL setting
@@ -676,8 +737,12 @@ export function load_agent_settings(
676
737
  const verbose = options.verbose ?? false;
677
738
  const cwd = options.cwd ?? null;
678
739
 
679
- const user_global_raw =
680
- _read_yaml(user_global_path ? user_global_path : _resolve_user_global_file()) ?? {};
740
+ // An explicit path is a caller override (tests, tooling) and stays single;
741
+ // otherwise every user-global file merges in precedence order.
742
+ const user_global_raw: SettingsDict = {};
743
+ for (const p of user_global_path ? [user_global_path] : user_global_settings_paths()) {
744
+ _deep_merge(user_global_raw, _read_yaml(p) ?? {});
745
+ }
681
746
 
682
747
  const [user_global_filtered, ignored] = _filter_whitelist(user_global_raw, MERGEABLE_KEYS);
683
748
  if (verbose && ignored.length > 0) {
@@ -923,14 +988,15 @@ export function* iter_setting_overrides(
923
988
  const user_global_path = options.user_global_path ?? null;
924
989
  const cwd = options.cwd ?? null;
925
990
 
926
- const user_global_path_resolved = user_global_path
927
- ? user_global_path
928
- : _resolve_user_global_file();
929
- const user_global_raw = _read_yaml(user_global_path_resolved) ?? {};
930
- const [user_global_filtered] = _filter_whitelist(user_global_raw, MERGEABLE_KEYS);
931
- if (Object.keys(user_global_filtered).length > 0) {
932
- for (const key of _leaf_paths(user_global_filtered)) {
933
- yield [key, _get_dotted(user_global_filtered, key), user_global_path_resolved];
991
+ // One yield PER FILE, not one per merged user-global layer: the whole point
992
+ // of this generator is that the third element names the file that set the
993
+ // value, and collapsing two user-global files into one would attribute a
994
+ // value to a file that does not contain it.
995
+ for (const p of user_global_path ? [user_global_path] : user_global_settings_paths()) {
996
+ const raw = _read_yaml(p) ?? {};
997
+ const [filtered] = _filter_whitelist(raw, MERGEABLE_KEYS);
998
+ for (const key of _leaf_paths(filtered)) {
999
+ yield [key, _get_dotted(filtered, key), p];
934
1000
  }
935
1001
  }
936
1002