@event4u/agent-config 9.24.0 → 9.26.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/CHANGELOG.md +136 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/conformance.md +182 -0
- package/dist/agent-src/commands/analyze/inbox.md +33 -2
- package/dist/agent-src/commands/review/changes.md +6 -5
- package/dist/agent-src/commands/roadmap/next.md +194 -0
- package/dist/agent-src/commands/roadmap.md +12 -4
- package/dist/agent-src/contexts/execution/subagent-routing.md +4 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
- package/dist/agent-src/rules/delegation-policy.md +3 -2
- package/dist/agent-src/rules/design-fidelity.md +17 -1
- package/dist/agent-src/rules/design-review-after-ui-write.md +116 -0
- package/dist/agent-src/rules/evaluator-independence.md +111 -0
- package/dist/agent-src/rules/session-canary.md +15 -1
- package/dist/agent-src/rules/settings-ask-protocol.md +176 -0
- package/dist/agent-src/rules/token-budget-discipline.md +6 -3
- package/dist/agent-src/rules/ui-audit-gate.md +3 -0
- package/dist/agent-src/rules/user-interaction.md +23 -0
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +1 -1
- package/dist/agent-src/skills/recursive-verification/SKILL.md +13 -0
- package/dist/agent-src/skills/rule-writing/SKILL.md +45 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +114 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +4 -1
- package/dist/cli-delegate/cmd_route_explain.js +4 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +146 -28
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +16 -11
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +12 -7
- package/dist/hooks/dispatch.js +1501 -511
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/router.json +1 -1
- package/dist/server/nicknameResolver.js +116 -0
- package/dist/server/nicknameResolver.js.map +1 -0
- package/dist/server/routes/ping.js +29 -11
- package/dist/server/routes/ping.js.map +1 -1
- package/dist/shared/interactiveContext.js +73 -0
- package/dist/shared/interactiveContext.js.map +1 -0
- package/dist/shared/nicknamePrefill.js +59 -0
- package/dist/shared/nicknamePrefill.js.map +1 -0
- package/dist/shared/settingsAsks.js +128 -0
- package/dist/shared/settingsAsks.js.map +1 -0
- package/dist/shared/settingsConsent.js +93 -0
- package/dist/shared/settingsConsent.js.map +1 -0
- package/package.json +1 -1
- package/src/config/agents-paths.yml +15 -0
- package/src/config/evaluator-budgets.json +68 -68
- package/src/config/gate-coverage.yml +131 -5
- package/src/config/gate-violation-baselines.json +11 -0
- package/src/scripts/_cli/cmd_route_audit.ts +4 -1
- package/src/scripts/_cli/cmd_route_explain.ts +9 -1
- package/src/scripts/_dispatch.bash +12 -0
- package/src/scripts/ai-video/stitch.sh +27 -9
- package/src/scripts/before_complete_hook.ts +146 -5
- package/src/scripts/bench_ab_v2_run.ts +272 -16
- package/src/scripts/bench_ab_v2_stats.ts +156 -3
- package/src/scripts/check_completion_review.ts +80 -3
- package/src/scripts/check_corpus_staleness.ts +692 -0
- package/src/scripts/check_depth_budget.ts +295 -0
- package/src/scripts/check_gate_coverage.ts +67 -0
- package/src/scripts/check_hook_bundle_freshness.ts +183 -0
- package/src/scripts/conformance_scan.ts +422 -0
- package/src/scripts/dispatch_r2_reviewer.ts +31 -2
- package/src/scripts/git_authorization_hook.ts +328 -0
- package/src/scripts/hook_manifest.yaml +69 -9
- package/src/scripts/hooks/block_no_verify.ts +3 -1
- package/src/scripts/hooks/block_unauthorized_git.ts +371 -0
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/design_slop_hook.ts +13 -1
- package/src/scripts/hooks/dispatch_hook.ts +97 -2
- package/src/scripts/hooks/evidence_independence.ts +368 -0
- package/src/scripts/language_mirror_hook.ts +456 -0
- package/src/scripts/lint_abstraction_thresholds.ts +41 -8
- package/src/scripts/lint_agents_layout.ts +2 -0
- package/src/scripts/lint_decision_impact.ts +246 -0
- package/src/scripts/lint_example_fences.ts +609 -0
- package/src/scripts/lint_hook_manifest.ts +29 -0
- package/src/scripts/lint_rule_interactions.ts +90 -0
- package/src/scripts/lint_rule_migration_ledger.ts +480 -0
- package/src/scripts/lint_skill_descriptions.ts +86 -2
- package/src/scripts/memory_learn_hook.ts +109 -6
- package/src/scripts/pr_url_reminder_hook.ts +17 -2
- package/src/scripts/release.ts +38 -1
- package/src/scripts/schemas/review-findings.schema.json +99 -0
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/shared/interactiveContext.ts +98 -0
- package/src/shared/nicknamePrefill.ts +96 -0
- package/src/shared/settingsAsks.ts +176 -0
- package/src/shared/settingsConsent.ts +136 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-question budget, as a function rather than as a promise.
|
|
3
|
+
*
|
|
4
|
+
* Phase 5 of `road-to-zero-ceremony-settings`. The rule
|
|
5
|
+
* `src/rules/settings-ask-protocol.md` states the budget — at most ONE settings
|
|
6
|
+
* question per command execution, every further undecided key taking its
|
|
7
|
+
* conservative default silently and being named in the end-summary. Prose is
|
|
8
|
+
* where that obligation has to live, because no gate can count the questions in
|
|
9
|
+
* a chat turn.
|
|
10
|
+
*
|
|
11
|
+
* But the *split* is not a judgement call: given the keys an execution needs
|
|
12
|
+
* decided, which one is asked and what the others resolve to is fully
|
|
13
|
+
* determined. Leaving that determined part in prose too would mean the roadmap's
|
|
14
|
+
* own exit criterion — "a planted fixture needing three B decisions asks once,
|
|
15
|
+
* assumes two conservatively, and lists both in the summary" — had nothing to
|
|
16
|
+
* run against. So the split is computed here and the rule points at it.
|
|
17
|
+
*
|
|
18
|
+
* What this module does NOT do, stated so nobody mistakes it for the fence:
|
|
19
|
+
* it does not ask, it does not write, and it cannot stop an agent that ignores
|
|
20
|
+
* it. The write fence is one layer down and is real —
|
|
21
|
+
* `src/scripts/_cli/cmd_settings_set.ts` and the Fastify settings route both
|
|
22
|
+
* refuse every C-class key and fail closed on an unreadable class contract.
|
|
23
|
+
*
|
|
24
|
+
* Pure — no I/O, no clock, no environment.
|
|
25
|
+
*
|
|
26
|
+
* @see src/rules/settings-ask-protocol.md
|
|
27
|
+
* @see docs/contracts/settings-classes.md
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { classOfPath, isConservativeDefault, type SettingsClass } from './settingsClasses.js';
|
|
31
|
+
|
|
32
|
+
/** A key this execution resolved without asking, and how to change it later. */
|
|
33
|
+
export interface SilentDefault {
|
|
34
|
+
key: string;
|
|
35
|
+
/** The value taken — the template default, which for B is conservative. */
|
|
36
|
+
value: unknown;
|
|
37
|
+
/** The exact command that changes it, for the end-summary line. */
|
|
38
|
+
command: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A key that never reaches the ask path, and the reason it does not. */
|
|
42
|
+
export interface SkippedKey {
|
|
43
|
+
key: string;
|
|
44
|
+
reason:
|
|
45
|
+
| 'class-a-never-asked'
|
|
46
|
+
| 'class-c-guarded'
|
|
47
|
+
| 'unclassified'
|
|
48
|
+
| 'already-decided'
|
|
49
|
+
| 'non-conservative-default';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface AskPlan {
|
|
53
|
+
/** The single key that gets this execution's question, or `null` for none. */
|
|
54
|
+
ask: string | null;
|
|
55
|
+
/** Undecided keys that took their default silently, in the order given. */
|
|
56
|
+
silent: SilentDefault[];
|
|
57
|
+
/** Keys excluded from the ask path entirely, with the reason. */
|
|
58
|
+
skipped: SkippedKey[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The end-summary line for a silently defaulted key.
|
|
63
|
+
*
|
|
64
|
+
* `--source manual` and not `jit-answer`: the user did not answer a question, so
|
|
65
|
+
* stamping the write as a just-in-time answer would record a consent nobody
|
|
66
|
+
* gave. The provenance vocabulary exists to keep exactly that distinction.
|
|
67
|
+
*/
|
|
68
|
+
export function changeCommand(key: string): string {
|
|
69
|
+
return `agent-config settings:set ${key} <value> --source manual`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Plan one execution's settings asks.
|
|
74
|
+
*
|
|
75
|
+
* `needed` is the keys this execution genuinely cannot proceed without, in the
|
|
76
|
+
* order they became needed — the caller's own ordering is respected because the
|
|
77
|
+
* first key to actually block the run is the one that earned the question.
|
|
78
|
+
*
|
|
79
|
+
* `decided` is every key already settled on any resolution layer. A decided key
|
|
80
|
+
* is not a candidate: re-asking a settled question is the cheap question the
|
|
81
|
+
* ask-discipline rules forbid, and it is the single most likely way this budget
|
|
82
|
+
* gets spent on nothing.
|
|
83
|
+
*
|
|
84
|
+
* `defaultOf` supplies the template default. It returns `undefined` for a key
|
|
85
|
+
* the template does not carry, which is treated as unclassified rather than as
|
|
86
|
+
* a default of `undefined` — a missing template entry is a defect, not a value.
|
|
87
|
+
*/
|
|
88
|
+
export function planSettingsAsks(
|
|
89
|
+
needed: readonly string[],
|
|
90
|
+
classes: ReadonlyMap<string, SettingsClass>,
|
|
91
|
+
defaultOf: (key: string) => unknown,
|
|
92
|
+
decided: ReadonlySet<string> = new Set(),
|
|
93
|
+
options: { interactive?: boolean } = {},
|
|
94
|
+
): AskPlan {
|
|
95
|
+
const plan: AskPlan = { ask: null, silent: [], skipped: [] };
|
|
96
|
+
const seen = new Set<string>();
|
|
97
|
+
// Non-TTY / CI / headless: nobody can answer, so the budget is ZERO rather
|
|
98
|
+
// than one. Every candidate takes its conservative default and the plan
|
|
99
|
+
// carries no ask — "no questions, ever", as a computed fact rather than as a
|
|
100
|
+
// promise the caller has to remember. Nothing is written either: this
|
|
101
|
+
// function never persists, and with no ask there is no `jit-answer` write.
|
|
102
|
+
const interactive = options.interactive ?? true;
|
|
103
|
+
|
|
104
|
+
for (const key of needed) {
|
|
105
|
+
// A duplicate in the caller's list must not consume the budget twice.
|
|
106
|
+
if (seen.has(key)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
seen.add(key);
|
|
110
|
+
|
|
111
|
+
if (decided.has(key)) {
|
|
112
|
+
plan.skipped.push({ key, reason: 'already-decided' });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const cls = classOfPath(classes, key);
|
|
117
|
+
if (cls === undefined) {
|
|
118
|
+
// Unclassified is guarded everywhere else in this system; it is
|
|
119
|
+
// guarded here too. Never fall back to "probably fine".
|
|
120
|
+
plan.skipped.push({ key, reason: 'unclassified' });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (cls === 'A') {
|
|
124
|
+
plan.skipped.push({ key, reason: 'class-a-never-asked' });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (cls === 'C') {
|
|
128
|
+
// A C key set to `ask` still produces a RUNTIME question, but that
|
|
129
|
+
// answer lasts one run and is never persisted, so it is not this
|
|
130
|
+
// budget's business. Persisting it stays on the human path.
|
|
131
|
+
plan.skipped.push({ key, reason: 'class-c-guarded' });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const fallback = defaultOf(key);
|
|
136
|
+
if (fallback === undefined) {
|
|
137
|
+
plan.skipped.push({ key, reason: 'unclassified' });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (interactive && plan.ask === null) {
|
|
142
|
+
plan.ask = key;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!isConservativeDefault(fallback)) {
|
|
147
|
+
// Taking a permissive value silently would be a decision made in
|
|
148
|
+
// the user's name — the one outcome the Iron Law names outright.
|
|
149
|
+
// Surfacing it as skipped forces the caller to deal with it instead
|
|
150
|
+
// of inheriting a permission nobody granted.
|
|
151
|
+
plan.skipped.push({ key, reason: 'non-conservative-default' });
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
plan.silent.push({ key, value: fallback, command: changeCommand(key) });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return plan;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The end-summary block for a plan, or `null` when nothing was defaulted.
|
|
163
|
+
*
|
|
164
|
+
* `null` rather than an empty string so a caller cannot append a heading with no
|
|
165
|
+
* rows under it. This text rides INSIDE the single end-summary that
|
|
166
|
+
* `direct-answers` already requires — it is never a second summary.
|
|
167
|
+
*/
|
|
168
|
+
export function silentDefaultsSummary(plan: AskPlan): string | null {
|
|
169
|
+
if (plan.silent.length === 0) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const lines = plan.silent.map(
|
|
173
|
+
(s) => `- \`${s.key}\` → ${JSON.stringify(s.value)} (default) · change: \`${s.command}\``,
|
|
174
|
+
);
|
|
175
|
+
return `Settings resolved without asking this run:\n${lines.join('\n')}`;
|
|
176
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a permissive B-class value a RECORDED CONSENT, or just a value?
|
|
3
|
+
*
|
|
4
|
+
* Phase 5 of `road-to-zero-ceremony-settings`, and the answer to that
|
|
5
|
+
* roadmap's Risk 3 — *"the provenance stamp becomes decoration; an unread stamp
|
|
6
|
+
* is cost with no return"*. Before this module the sidecar
|
|
7
|
+
* `settings/.agent-settings.provenance.json` was written by `settings:set` and
|
|
8
|
+
* read by exactly one caller, the GUI, to **display** it. Nothing decided
|
|
9
|
+
* anything on it. This is the reader that makes it load-bearing.
|
|
10
|
+
*
|
|
11
|
+
* The distinction it draws, which a bare value cannot: `true` that a human chose
|
|
12
|
+
* and `true` that the machine inferred are the same byte and are not the same
|
|
13
|
+
* permission. `auto-detected` is the provenance a machine writes about itself —
|
|
14
|
+
* a locale, an installed binary, a detected host. Letting it grant a consent
|
|
15
|
+
* would mean the agent could arrive at its own permission by observing the
|
|
16
|
+
* world, which is the confused-deputy shape in miniature.
|
|
17
|
+
*
|
|
18
|
+
* Modelled on `src/scripts/hooks/block_unauthorized_git.ts`: answer from a
|
|
19
|
+
* recorded fact rather than from the model's recollection, and treat a missing
|
|
20
|
+
* record as NOT granted rather than as probably-fine.
|
|
21
|
+
*
|
|
22
|
+
* WHAT THIS IS NOT. It is not a hook and it does not intercept anything. No
|
|
23
|
+
* B-class key currently gates an action through a guard — the two that gate
|
|
24
|
+
* behaviour at all (`personal.open_edited_files`, `memory.learn_on_session_end`)
|
|
25
|
+
* are read as values by the installer, the wizard and one session_end concern,
|
|
26
|
+
* and the *action* they govern is prose. So the enforcement gradient is honest
|
|
27
|
+
* and steep: this function is deterministic, the obligation to call it is
|
|
28
|
+
* model-carried, and on a prose-only host ask-once can degrade to ask-never.
|
|
29
|
+
*
|
|
30
|
+
* Pure — no I/O. Callers supply the value and the sidecar entry.
|
|
31
|
+
*
|
|
32
|
+
* @see src/rules/settings-ask-protocol.md
|
|
33
|
+
* @see docs/contracts/settings-classes.md
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { isConservativeDefault, type SettingsClass } from './settingsClasses.js';
|
|
37
|
+
|
|
38
|
+
/** The provenance vocabulary `settings:set` writes. Mirrors its own enum. */
|
|
39
|
+
export type ConsentSource = 'auto-detected' | 'jit-answer' | 'manual' | 'gui';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The sources that can carry a human decision.
|
|
43
|
+
*
|
|
44
|
+
* `jit-answer` is a question the user answered. `gui` is a human at the settings
|
|
45
|
+
* editor. `manual` is a hand-edit or an explicit CLI write on the user's
|
|
46
|
+
* instruction. `auto-detected` is excluded on purpose — see the module header.
|
|
47
|
+
*/
|
|
48
|
+
const HUMAN_SOURCES: readonly ConsentSource[] = ['jit-answer', 'gui', 'manual'];
|
|
49
|
+
|
|
50
|
+
export type ConsentVerdict =
|
|
51
|
+
/** A human decided, and the value permits the action. */
|
|
52
|
+
| 'granted'
|
|
53
|
+
/** The value is the conservative default — absent and no are the same thing. */
|
|
54
|
+
| 'withheld-default'
|
|
55
|
+
/** Permissive, but nothing records who decided it. Fail closed. */
|
|
56
|
+
| 'withheld-unrecorded'
|
|
57
|
+
/** Permissive and recorded, but recorded as a machine inference. */
|
|
58
|
+
| 'withheld-machine-inferred'
|
|
59
|
+
/** Not a consent question at all: A needs none, C is human-only. */
|
|
60
|
+
| 'not-a-consent-key';
|
|
61
|
+
|
|
62
|
+
export interface ConsentQuery {
|
|
63
|
+
/** The key's class, from the contract — `undefined` means unclassified. */
|
|
64
|
+
cls: SettingsClass | undefined;
|
|
65
|
+
/** The effective value after normal resolution. */
|
|
66
|
+
value: unknown;
|
|
67
|
+
/**
|
|
68
|
+
* The sidecar entry for this key, or `undefined` when none exists.
|
|
69
|
+
*
|
|
70
|
+
* `undefined` covers both "the sidecar has no row" and "there is no sidecar",
|
|
71
|
+
* which are the same fact from here: nothing records a decision.
|
|
72
|
+
*/
|
|
73
|
+
source?: ConsentSource | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* `true` when the value came from a file only a human can write — a
|
|
76
|
+
* project-local `.agent-settings.yml` or a hand-edited user file.
|
|
77
|
+
*
|
|
78
|
+
* The class contract is explicit that *"the user may edit anything in their
|
|
79
|
+
* own file with an editor"*, so a hand-edit IS the consent even though it
|
|
80
|
+
* carries no stamp. Without this the check would refuse the one path the
|
|
81
|
+
* contract guarantees, which is how a fence becomes a bug.
|
|
82
|
+
*/
|
|
83
|
+
handEdited?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The verdict for one consent-gated action.
|
|
88
|
+
*
|
|
89
|
+
* Order matters: class first (a C key never reaches a consent question at all),
|
|
90
|
+
* then the value (conservative means no, regardless of provenance), then the
|
|
91
|
+
* record.
|
|
92
|
+
*/
|
|
93
|
+
export function consentVerdict(query: ConsentQuery): ConsentVerdict {
|
|
94
|
+
if (query.cls !== 'B') {
|
|
95
|
+
return 'not-a-consent-key';
|
|
96
|
+
}
|
|
97
|
+
if (isConservativeDefault(query.value)) {
|
|
98
|
+
return 'withheld-default';
|
|
99
|
+
}
|
|
100
|
+
if (query.handEdited === true) {
|
|
101
|
+
return 'granted';
|
|
102
|
+
}
|
|
103
|
+
if (query.source === undefined) {
|
|
104
|
+
return 'withheld-unrecorded';
|
|
105
|
+
}
|
|
106
|
+
if (!HUMAN_SOURCES.includes(query.source)) {
|
|
107
|
+
return 'withheld-machine-inferred';
|
|
108
|
+
}
|
|
109
|
+
return 'granted';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** `true` only for `granted` — every other verdict withholds. */
|
|
113
|
+
export function consentGranted(query: ConsentQuery): boolean {
|
|
114
|
+
return consentVerdict(query) === 'granted';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* One line explaining a withheld verdict, for the surface that refused.
|
|
119
|
+
*
|
|
120
|
+
* `null` for `granted`, so a caller cannot accidentally print a refusal reason
|
|
121
|
+
* on the allow path.
|
|
122
|
+
*/
|
|
123
|
+
export function withheldReason(verdict: ConsentVerdict, key: string): string | null {
|
|
124
|
+
switch (verdict) {
|
|
125
|
+
case 'granted':
|
|
126
|
+
return null;
|
|
127
|
+
case 'withheld-default':
|
|
128
|
+
return `${key} is at its conservative default — absent and no are the same answer.`;
|
|
129
|
+
case 'withheld-unrecorded':
|
|
130
|
+
return `${key} permits this, but nothing records who decided it. Ask, or set it via the GUI.`;
|
|
131
|
+
case 'withheld-machine-inferred':
|
|
132
|
+
return `${key} was auto-detected, not decided. A machine inference is not a consent.`;
|
|
133
|
+
case 'not-a-consent-key':
|
|
134
|
+
return `${key} is not a class-B consent key — this gate does not apply to it.`;
|
|
135
|
+
}
|
|
136
|
+
}
|