@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +12 -11
- package/CHANGELOG.md +99 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/conformance.md +24 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
- package/dist/agent-src/rules/artifact-engagement-recording.md +4 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/brand-source-of-truth.md +33 -6
- package/dist/agent-src/rules/context-hygiene.md +3 -1
- package/dist/agent-src/rules/council-availability.md +93 -0
- package/dist/agent-src/rules/cross-source-consistency.md +3 -0
- package/dist/agent-src/rules/delegation-policy.md +29 -5
- package/dist/agent-src/rules/design-fidelity.md +3 -0
- package/dist/agent-src/rules/evaluator-independence.md +2 -1
- package/dist/agent-src/rules/external-code-graph-interop.md +2 -1
- package/dist/agent-src/rules/finance-safety-floor.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +1 -1
- package/dist/agent-src/rules/history-discipline.md +3 -1
- package/dist/agent-src/rules/legal-safety-floor.md +1 -1
- package/dist/agent-src/rules/no-pr-progress-comments.md +3 -1
- package/dist/agent-src/rules/onboarding-gate.md +3 -1
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +4 -1
- package/dist/agent-src/rules/role-mode-adherence.md +1 -1
- package/dist/agent-src/rules/scale-discipline.md +3 -1
- package/dist/agent-src/rules/self-repair-loop.md +103 -0
- package/dist/agent-src/rules/session-canary.md +2 -1
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
- package/dist/agent-src/rules/token-budget-discipline.md +3 -1
- package/dist/agent-src/rules/tool-safety.md +5 -1
- package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
- package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
- package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +6 -10
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +7 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
- package/dist/cli-delegate/{chunk-BCPIOAGV.js → chunk-2BE5I2MN.js} +8 -8
- package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
- package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
- package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
- package/dist/cli-delegate/{chunk-3KJIMFTB.js → chunk-PQSAQKHW.js} +466 -304
- package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +134 -0
- package/dist/cli-delegate/cmd_conformance.js +7 -7
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +4 -4
- package/dist/cli-delegate/cmd_explain.js +80 -330
- package/dist/cli-delegate/cmd_export.js +5 -5
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_mcp_available.js +433 -0
- package/dist/cli-delegate/cmd_migrate.js +3 -3
- package/dist/cli-delegate/cmd_packs_active.js +153 -0
- package/dist/cli-delegate/cmd_preflight.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +5 -5
- package/dist/cli-delegate/cmd_route_audit.js +8 -8
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +272 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
- package/dist/cli-delegate/cmd_settings_set.js +7 -2967
- package/dist/cli-delegate/cmd_sync.js +5 -5
- package/dist/cli-delegate/cmd_uninstall.js +5 -5
- package/dist/cli-delegate/cmd_update.js +4 -4
- package/dist/cli-delegate/cmd_upgrade.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +5 -5
- package/dist/cli-delegate/cmd_versions.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +100 -40
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +9004 -6132
- package/dist/install/install.mjs +697 -507
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +127 -20
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +80 -15
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +29 -8
- package/docs/guidelines/agent-infra/memory-access.md +14 -3
- package/package.json +1 -1
- package/src/agent-src/templates/AGENTS.md +6 -10
- package/src/config/budgets.yml +29 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +26 -0
- package/src/scripts/_cli/cmd_brand_status.ts +174 -0
- package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
- package/src/scripts/_cli/cmd_packs_active.ts +225 -0
- package/src/scripts/_cli/cmd_settings_get.ts +324 -0
- package/src/scripts/_dispatch.bash +57 -0
- package/src/scripts/_lib/agent_settings.ts +82 -16
- package/src/scripts/_lib/carrier_divergence.ts +135 -0
- package/src/scripts/_lib/host_capability.ts +84 -1
- package/src/scripts/_lib/prompt_shape.ts +88 -0
- package/src/scripts/_lib/review_skipped_record.ts +102 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
- package/src/scripts/_lib/self_repair.ts +342 -0
- package/src/scripts/_lib/self_repair_store.ts +100 -0
- package/src/scripts/_lib/transcript_entry.ts +80 -0
- package/src/scripts/check_branch_freshness.ts +255 -0
- package/src/scripts/check_council_config_location.ts +125 -1
- package/src/scripts/check_standing_rule_delivery.ts +314 -0
- package/src/scripts/config/profiles.ts +13 -3
- package/src/scripts/conformance_scan.ts +272 -24
- package/src/scripts/council_availability_hook.ts +188 -0
- package/src/scripts/council_cli.ts +126 -7
- package/src/scripts/hook_manifest.yaml +107 -26
- package/src/scripts/hooks/block_no_verify.ts +39 -2
- package/src/scripts/hooks/block_unauthorized_git.ts +156 -7
- package/src/scripts/hooks/concern_registry.ts +10 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +450 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
- package/src/scripts/install.ts +151 -0
- package/src/scripts/language_mirror_hook.ts +30 -37
- package/src/scripts/measure_scope_dedup.ts +19 -13
- package/src/scripts/onboarding_gate_hook.ts +40 -1
- package/src/scripts/orchestration_record.ts +4 -1
- package/src/scripts/preamble_byte_census.ts +9 -20
- package/src/scripts/report_carrier_divergence.ts +364 -0
- package/src/scripts/report_skill_activation.ts +257 -0
- package/src/scripts/report_skill_obligation_violations.ts +579 -0
- package/src/scripts/routing_doctor.ts +77 -6
- package/src/scripts/self_repair_cli.ts +240 -0
- 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
|
-
/**
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
680
|
-
|
|
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
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
const [
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
|