@event4u/agent-config 9.22.0 → 9.24.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 +84 -238
- package/README.md +2 -2
- package/dist/agent-src/commands/agent-handoff.md +10 -3
- package/dist/agent-src/commands/analyze/inbox.md +190 -0
- package/dist/agent-src/commands/analyze.md +4 -3
- package/dist/agent-src/commands/optimize/deep.md +13 -0
- package/dist/agent-src/commands/pr/create.md +6 -6
- package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +12 -2
- package/dist/agent-src/contexts/execution/mandated-lines.md +180 -0
- package/dist/agent-src/rules/context-hygiene.md +20 -0
- package/dist/agent-src/rules/downstream-changes.md +26 -0
- package/dist/agent-src/rules/think-before-action.md +25 -0
- package/dist/agent-src/rules/token-budget-discipline.md +46 -3
- package/dist/agent-src/rules/token-efficiency.md +13 -0
- package/dist/agent-src/rules/ui-audit-gate.md +33 -2
- package/dist/agent-src/skills/adversarial-review/SKILL.md +18 -4
- package/dist/agent-src/skills/decision-review/SKILL.md +49 -0
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +5 -1
- package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +16 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +14 -1
- package/dist/agent-src/skills/rule-writing/SKILL.md +33 -0
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +31 -0
- package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
- 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/chunk-DVRS2KVR.js +4602 -0
- package/dist/cli-delegate/{chunk-ELMFOQPW.js → chunk-SJQMJI7M.js} +8 -4597
- package/dist/cli-delegate/cmd_export.js +2 -1
- package/dist/cli-delegate/cmd_prune.js +2 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -1
- package/dist/cli-delegate/cmd_settings_set.js +3370 -0
- package/dist/cli-delegate/cmd_sync.js +2 -1
- package/dist/cli-delegate/cmd_uninstall.js +2 -1
- package/dist/cli-delegate/cmd_validate.js +2 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +54 -27
- 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 +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +79 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/scripts/condense.js +47 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/routes/settings.js +82 -0
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/shared/settingsClasses.js +191 -0
- package/dist/shared/settingsClasses.js.map +1 -0
- package/docs/contracts/settings-classes.md +342 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +23 -2
- package/docs/guidelines/agent-infra/false-green.md +187 -0
- package/docs/guidelines/agent-infra/gate-authoring.md +177 -0
- package/package.json +3 -1
- package/src/config/ci-local-parity.yml +12 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +54 -12
- package/src/config/gate-violation-baselines.json +6 -0
- package/src/config/pack-size-budget.json +4 -3
- package/src/scripts/_cli/cmd_settings_set.ts +509 -0
- package/src/scripts/_dispatch.bash +16 -0
- package/src/scripts/_lib/gate_ledger.ts +295 -0
- package/src/scripts/_lib/gate_result.ts +101 -0
- package/src/scripts/_lib/gate_self_test.ts +129 -0
- package/src/scripts/_lib/measured_render.ts +97 -0
- package/src/scripts/_lib/ratchet_base_ref.ts +373 -0
- package/src/scripts/check_gate_completeness.ts +244 -0
- package/src/scripts/check_gate_coverage.ts +35 -6
- package/src/scripts/check_references.ts +125 -15
- package/src/scripts/check_review_dispositions.ts +14 -1
- package/src/scripts/check_suppression_hygiene.ts +503 -0
- package/src/scripts/condense.ts +47 -2
- package/src/scripts/hook_manifest.yaml +23 -0
- package/src/scripts/hooks/dispatch_hook.ts +65 -1
- package/src/scripts/hooks/host_semantics.ts +140 -0
- package/src/scripts/lint_framework_leakage.ts +147 -15
- package/src/scripts/lint_mandated_lines.ts +339 -0
- package/src/scripts/lint_roadmap_complexity.ts +102 -11
- package/src/scripts/lint_roadmap_family_cap.ts +191 -0
- package/src/scripts/lint_settings_classes.ts +397 -0
- package/src/scripts/lint_token_budget_discipline.ts +109 -1
- package/src/scripts/measure_rule_absoluta.ts +231 -0
- package/src/scripts/report_imperative_density.ts +229 -0
- package/src/scripts/verify_physical_move.ts +10 -1
- package/src/server/io/yamlIO.ts +173 -0
- package/src/shared/settingsClasses.ts +235 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings A/B/C classes — the pure half.
|
|
3
|
+
*
|
|
4
|
+
* `docs/contracts/settings-classes.md` is the single source: one markdown table
|
|
5
|
+
* row per template leaf, each carrying the class that decides **who may write
|
|
6
|
+
* that key**. This module parses that table and nothing else. There is
|
|
7
|
+
* deliberately no generated JSON twin — a second machine-readable copy is a
|
|
8
|
+
* second thing to drift, and the whole point of the contract is that the fence
|
|
9
|
+
* and the prose explaining it cannot disagree.
|
|
10
|
+
*
|
|
11
|
+
* Pure module — no I/O. Consumed by the lint (`src/scripts/lint_settings_classes.ts`),
|
|
12
|
+
* the `settings set` writer, and the Fastify settings route, so all three
|
|
13
|
+
* refuse the same keys for the same reason.
|
|
14
|
+
*
|
|
15
|
+
* @see docs/contracts/settings-classes.md
|
|
16
|
+
*/
|
|
17
|
+
/** The closed class vocabulary. A row outside it is a defect, not a new class. */
|
|
18
|
+
export const SETTINGS_CLASSES = ['A', 'B', 'C'];
|
|
19
|
+
export function isSettingsClass(value) {
|
|
20
|
+
return SETTINGS_CLASSES.includes(value);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `| \`some.key\` | C | \`false\` | why |` → one row.
|
|
24
|
+
*
|
|
25
|
+
* The class column is what separates the key table from the prose tables above
|
|
26
|
+
* it, several of which also open with a backticked key. A second cell that is
|
|
27
|
+
* not a single bare letter is not a key row.
|
|
28
|
+
*/
|
|
29
|
+
const ROW_RE = /^\|\s*`([^`]+)`\s*\|\s*([^|]*?)\s*\|/;
|
|
30
|
+
export function parseSettingsClassRows(markdown) {
|
|
31
|
+
const rows = [];
|
|
32
|
+
const lines = markdown.split('\n');
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const m = ROW_RE.exec(lines[i] ?? '');
|
|
35
|
+
if (m === null) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const key = m[1] ?? '';
|
|
39
|
+
const cls = m[2] ?? '';
|
|
40
|
+
if (!/^[A-Za-z]$/.test(cls)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
rows.push({ key, cls, line: i + 1 });
|
|
44
|
+
}
|
|
45
|
+
return rows;
|
|
46
|
+
}
|
|
47
|
+
/** `| A — preference | 27 |` and `| **Total** | **140** |` → the declared tallies. */
|
|
48
|
+
export function parseDeclaredClassCounts(markdown) {
|
|
49
|
+
const read = (re) => {
|
|
50
|
+
const m = re.exec(markdown);
|
|
51
|
+
const raw = m?.[1];
|
|
52
|
+
return raw === undefined ? null : Number.parseInt(raw, 10);
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
A: read(/^\|\s*A\s+—\s+preference\s*\|\s*(\d+)\s*\|/m),
|
|
56
|
+
B: read(/^\|\s*B\s+—\s+consent\s*\|\s*(\d+)\s*\|/m),
|
|
57
|
+
C: read(/^\|\s*C\s+—\s+guarded\s*\|\s*(\d+)\s*\|/m),
|
|
58
|
+
total: read(/^\|\s*\*\*Total\*\*\s*\|\s*\*\*(\d+)\*\*\s*\|/m),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Dotted key → class, for the rows whose class is valid.
|
|
63
|
+
*
|
|
64
|
+
* A row with an unparseable class is DROPPED rather than defaulted, so a
|
|
65
|
+
* caller asking "may I write this?" gets `undefined` — which every caller
|
|
66
|
+
* treats as refuse. A typo in the contract must never widen the fence.
|
|
67
|
+
*/
|
|
68
|
+
export function buildSettingsClassIndex(rows) {
|
|
69
|
+
const index = new Map();
|
|
70
|
+
for (const row of rows) {
|
|
71
|
+
if (!isSettingsClass(row.cls) || index.has(row.key)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
index.set(row.key, row.cls);
|
|
75
|
+
}
|
|
76
|
+
return index;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Conservative-default test for class B (half one of the B invariant).
|
|
80
|
+
*
|
|
81
|
+
* A sparse settings file means absent = default. A B key whose default is the
|
|
82
|
+
* permissive value makes "never asked" indistinguishable from "answered yes",
|
|
83
|
+
* which turns the ask into decoration on a decision already taken in the
|
|
84
|
+
* user's name. These are the values that cannot carry a permission.
|
|
85
|
+
*
|
|
86
|
+
* Half two of the invariant — that the ask fires on the user's own request and
|
|
87
|
+
* never at a moment the agent chose — is prose in the contract, not code. No
|
|
88
|
+
* gate can read the agent's reason for picking a moment.
|
|
89
|
+
*/
|
|
90
|
+
export function isConservativeDefault(value) {
|
|
91
|
+
if (value === null || value === false || value === '' || value === 0) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return value.length === 0;
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === 'object' && value !== null) {
|
|
98
|
+
return Object.keys(value).length === 0;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Dotted leaf paths of a parsed settings tree.
|
|
104
|
+
*
|
|
105
|
+
* A leaf is anything that is not a NON-EMPTY map. An empty map is a real
|
|
106
|
+
* configurable value with a real default (`subagents.host_capabilities: {}`),
|
|
107
|
+
* so it is a leaf — one more than the template↔schema parity walk produces.
|
|
108
|
+
* That divergence is deliberate: a key the parity walk skips is exactly the
|
|
109
|
+
* key that would otherwise reach the writer with no class.
|
|
110
|
+
*/
|
|
111
|
+
export function settingsLeafPaths(value, prefix = '') {
|
|
112
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
113
|
+
return [prefix];
|
|
114
|
+
}
|
|
115
|
+
const entries = Object.entries(value);
|
|
116
|
+
if (entries.length === 0) {
|
|
117
|
+
return [prefix];
|
|
118
|
+
}
|
|
119
|
+
return entries.flatMap(([k, v]) => settingsLeafPaths(v, prefix === '' ? k : `${prefix}.${k}`));
|
|
120
|
+
}
|
|
121
|
+
/** Read one dotted path out of a parsed settings tree; `undefined` when absent. */
|
|
122
|
+
export function getSettingsLeaf(root, dotted) {
|
|
123
|
+
let node = root;
|
|
124
|
+
for (const part of dotted.split('.')) {
|
|
125
|
+
if (typeof node !== 'object' || node === null || Array.isArray(node)) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
node = node[part];
|
|
129
|
+
}
|
|
130
|
+
return node;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The class governing a dotted path, walking to the nearest classified ancestor.
|
|
134
|
+
*
|
|
135
|
+
* An exact row wins. Failing that, the longest classified prefix does: a
|
|
136
|
+
* class-C key whose value is a MAP has children that never appear in a flattened
|
|
137
|
+
* diff under the key's own name, so `subagents.host_capabilities.subagent_spawn`
|
|
138
|
+
* must inherit the C on `subagents.host_capabilities` rather than read as
|
|
139
|
+
* unclassified. Returns `undefined` when nothing on the path is classified —
|
|
140
|
+
* and every caller treats that as guarded, not as free.
|
|
141
|
+
*/
|
|
142
|
+
export function classOfPath(index, dotted) {
|
|
143
|
+
const exact = index.get(dotted);
|
|
144
|
+
if (exact !== undefined) {
|
|
145
|
+
return exact;
|
|
146
|
+
}
|
|
147
|
+
const parts = dotted.split('.');
|
|
148
|
+
for (let end = parts.length - 1; end > 0; end--) {
|
|
149
|
+
const ancestor = index.get(parts.slice(0, end).join('.'));
|
|
150
|
+
if (ancestor !== undefined) {
|
|
151
|
+
return ancestor;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
/** Absent / null / empty-scalar — the three spellings of "not set" in a settings file. */
|
|
157
|
+
export function isUnsetSettingValue(value) {
|
|
158
|
+
return value === undefined || value === null || value === '';
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The changed paths a human must confirm before the write lands.
|
|
162
|
+
*
|
|
163
|
+
* Extracted from the Fastify write route so the fail-closed branch is testable
|
|
164
|
+
* without booting a server around it. That branch is the one a single inverted
|
|
165
|
+
* operator turns into a fail-OPEN, and a gate whose most dangerous line has no
|
|
166
|
+
* test is a gate that passes review once and rots after.
|
|
167
|
+
*
|
|
168
|
+
* `classes === null` means the contract could not be read. Every changed key is
|
|
169
|
+
* then guarded: unverifiable is not the same as unguarded. With a contract in
|
|
170
|
+
* hand, a key is guarded when its nearest classified ancestor is C, and ALSO
|
|
171
|
+
* when nothing on its path is classified at all — the same per-key refusal the
|
|
172
|
+
* CLI writer applies.
|
|
173
|
+
*
|
|
174
|
+
* Pairs where both sides are unset are dropped first: a key written as `null`
|
|
175
|
+
* reads back as `''` through the comment-preserving merge, and a confirmation
|
|
176
|
+
* that appears on every save is one nobody reads.
|
|
177
|
+
*/
|
|
178
|
+
export function guardedChangedKeys(classes, changed) {
|
|
179
|
+
return changed
|
|
180
|
+
.filter((c) => !(isUnsetSettingValue(c.from) && isUnsetSettingValue(c.to)))
|
|
181
|
+
.map((c) => c.path)
|
|
182
|
+
.filter((key) => {
|
|
183
|
+
if (classes === null) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
const cls = classOfPath(classes, key);
|
|
187
|
+
return cls === 'C' || cls === undefined;
|
|
188
|
+
})
|
|
189
|
+
.sort();
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=settingsClasses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settingsClasses.js","sourceRoot":"","sources":["../../src/shared/settingsClasses.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Settings classes — A / B / C
|
|
2
|
+
|
|
3
|
+
> Every leaf key in `src/config/agent-settings.template.yml` carries exactly one
|
|
4
|
+
> class. The class answers ONE question: **who may write this key.**
|
|
5
|
+
>
|
|
6
|
+
> `src/scripts/lint_settings_classes.ts` fails the build when a template key has
|
|
7
|
+
> no row here, when a row names a key the template does not have, or when a row
|
|
8
|
+
> carries a class outside `A | B | C`. The table below is the single source —
|
|
9
|
+
> there is no second machine-readable copy to drift from it.
|
|
10
|
+
|
|
11
|
+
Origin: Phase 1 of `road-to-zero-ceremony-settings`. It exists because the
|
|
12
|
+
package is about to grow an agent-writable settings path (`settings set`), and
|
|
13
|
+
an agent-writable path without a fence is an injection surface: anything that
|
|
14
|
+
can reach the model's input can reach a setting, and some of these settings
|
|
15
|
+
raise a spend ceiling, empty an allowlist, or switch off a gate.
|
|
16
|
+
|
|
17
|
+
## The three classes
|
|
18
|
+
|
|
19
|
+
| Class | Who may write it | Is the user ever asked? |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| **A — preference** | the agent (via `settings set`), the GUI, a hand-edit | **never.** It resolves to its default until someone states otherwise. |
|
|
22
|
+
| **B — consent** | the agent, but only after ONE just-in-time ask that the user answered | **once**, then persisted with provenance. |
|
|
23
|
+
| **C — guarded** | the GUI's write route or a hand-edit, by a human | never asked by the agent; **every agent write path refuses it.** |
|
|
24
|
+
|
|
25
|
+
## What the fence covers — and what it does not
|
|
26
|
+
|
|
27
|
+
**It governs writes, not reads.** Every class is readable by everything. A C
|
|
28
|
+
class does not hide a value; it refuses to let a non-human change it.
|
|
29
|
+
|
|
30
|
+
**It governs writes, not asks.** Six settings ship an `ask` value in their own
|
|
31
|
+
enum (`tokens.rich_skills`, `subagents.auto`, `subagents.budget_routing`,
|
|
32
|
+
`subagents.adversarial_council`, `worktrees.mode`, `decision_engine.on_block`).
|
|
33
|
+
Those are C-class here, and that is not a contradiction: the class says who may
|
|
34
|
+
*persist* a new value; the `ask` value says what happens *at runtime* when the
|
|
35
|
+
setting is already set to `ask`. A C-class key set to `ask` still produces a
|
|
36
|
+
runtime question, the answer applies to that run, and persisting it stays on the
|
|
37
|
+
human path.
|
|
38
|
+
|
|
39
|
+
**It is not a permission model for the user.** The user may edit anything in
|
|
40
|
+
their own file with an editor. C means the *agent* does not do it for them.
|
|
41
|
+
|
|
42
|
+
## Why "master flag → C" is read narrowly
|
|
43
|
+
|
|
44
|
+
The roadmap's instruction is *"put every budget-raising key, allow/deny list,
|
|
45
|
+
kill-switch, strict mode, and master flag in C"*. Read literally, "master flag"
|
|
46
|
+
swallows every boolean named `enabled`, and a fence that contains everything
|
|
47
|
+
fences nothing — it just makes the writer useless and pushes the next author to
|
|
48
|
+
carve an exception.
|
|
49
|
+
|
|
50
|
+
So the rule applied here is the acceptance criterion's own wording — **no key
|
|
51
|
+
with an irreversible or spend-raising effect sits outside C** — expanded into
|
|
52
|
+
eight tests. A key is C when it governs any of:
|
|
53
|
+
|
|
54
|
+
1. money, tokens, or a quota;
|
|
55
|
+
2. an allow-list or a deny-list;
|
|
56
|
+
3. a gate, a confirmation, or a safety floor;
|
|
57
|
+
4. the agent's own authority, autonomy, or reasoning discipline;
|
|
58
|
+
5. which code runs or which artefacts project into the agent's context;
|
|
59
|
+
6. egress of repository or conversation content;
|
|
60
|
+
7. a credential, an executed binary, or a filesystem path;
|
|
61
|
+
8. audit, observability, or a background safety check — anything that changes
|
|
62
|
+
what the user can later **see** about what the agent did.
|
|
63
|
+
|
|
64
|
+
Test 8 came out of the council review below, and it is the only one that looks
|
|
65
|
+
backwards. Tests 1–7 ask *what can the attacker do*; test 8 asks *what can the
|
|
66
|
+
attacker hide*. A settings write that erases the trail is not less severe than
|
|
67
|
+
one that raises a budget — it is the one that makes the other invisible.
|
|
68
|
+
|
|
69
|
+
`commands.suggestion.enabled` is the worked counter-example: it is a master
|
|
70
|
+
switch, and it is **A**, because switching it off removes no gate, no spend, no
|
|
71
|
+
authority, and no audit trail — it removes a convenience.
|
|
72
|
+
|
|
73
|
+
## B eligibility — the invariant
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
A KEY MAY BE CLASS B ONLY IF ITS TEMPLATE DEFAULT IS THE CONSERVATIVE VALUE,
|
|
77
|
+
AND THE ASK FIRES ONLY WHEN THE USER'S OWN REQUEST NEEDS THE SETTING —
|
|
78
|
+
NEVER AT A MOMENT THE AGENT CHOSE.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Half one — the conservative default.** A sparse settings file means *absent =
|
|
82
|
+
default*. If a B key shipped with the permissive value as its default, then
|
|
83
|
+
"never asked" and "answered yes" would be indistinguishable, and the ask would
|
|
84
|
+
be decoration on a decision already made in the user's name. Under this
|
|
85
|
+
invariant an unanswered B key is always the safe value, and the ask is the only
|
|
86
|
+
path to the permissive one. `lint_settings_classes` enforces this half
|
|
87
|
+
mechanically: a B row whose template default is not `false` / `""` / `0` / `[]`
|
|
88
|
+
/ `null` / `{}` fails the build.
|
|
89
|
+
|
|
90
|
+
**Half two — who picks the moment.** The council review below found the hole in
|
|
91
|
+
half one: an agent free to choose *when* to ask can engineer consent. Asking
|
|
92
|
+
"may I learn from this session?" straight after solving something painful is
|
|
93
|
+
still a just-in-time ask, and the answer is still coerced. So the trigger must
|
|
94
|
+
be the user's own request arriving at a point where the setting is genuinely
|
|
95
|
+
required — the user asked for an edit, so `personal.open_edited_files` is asked;
|
|
96
|
+
the user ended the session, so `memory.learn_on_session_end` is asked. A
|
|
97
|
+
threshold the agent watched, a moment it judged favourable, or a convenient lull
|
|
98
|
+
are not triggers. This half is **prose, not lint** — no gate can read the
|
|
99
|
+
agent's reason for choosing a moment, and claiming otherwise would be the
|
|
100
|
+
coverage inflation this repository's gate discipline exists to refuse.
|
|
101
|
+
|
|
102
|
+
That invariant is why B is small — **three keys**. It is not a placeholder for a
|
|
103
|
+
larger set; it is the set that passes both halves.
|
|
104
|
+
|
|
105
|
+
| Key | Why a just-in-time ask is the right shape | Conservative default |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `personal.open_edited_files` | it starts running the binary named in `personal.ide` on every edit — the user should say yes once before that begins | `false` |
|
|
108
|
+
| `personal.canary_name` | un-inferrable, and answering arms the session-degradation canary; one keypress to accept the prefill | `""` (canary dark) |
|
|
109
|
+
| `memory.learn_on_session_end` | it turns on automatic memory writes at the end of every session — a standing write the user should authorise | `false` |
|
|
110
|
+
|
|
111
|
+
## Counts
|
|
112
|
+
|
|
113
|
+
| Class | Keys |
|
|
114
|
+
|---|---|
|
|
115
|
+
| A — preference | 27 |
|
|
116
|
+
| B — consent | 3 |
|
|
117
|
+
| C — guarded | 110 |
|
|
118
|
+
| **Total** | **140** |
|
|
119
|
+
|
|
120
|
+
The total is every leaf in the template, where *leaf* means anything that is not
|
|
121
|
+
a **non-empty** map. An empty map (`subagents.host_capabilities: {}`) is a real
|
|
122
|
+
configurable value with a real default, so it is a leaf here — one row more than
|
|
123
|
+
the template↔schema parity test walks, deliberately, because a key with no
|
|
124
|
+
class is exactly what this contract exists to prevent.
|
|
125
|
+
|
|
126
|
+
`secrets.link_encryption_key` is commented out in the template and therefore has
|
|
127
|
+
no row. It is a credential; were it ever uncommented it is C, and the lint would
|
|
128
|
+
demand a row before the build went green.
|
|
129
|
+
|
|
130
|
+
## The table
|
|
131
|
+
|
|
132
|
+
Rows follow template order, so a diff against the template reads straight down.
|
|
133
|
+
|
|
134
|
+
| Key | Class | Default | Why |
|
|
135
|
+
|---|---|---|---|
|
|
136
|
+
| `agent_config_version` | C | `""` | installer-owned pin selecting which package code executes |
|
|
137
|
+
| `profile.id` | C | `developer` | master axis selecting the projected skill and command surface |
|
|
138
|
+
| `projection.mode` | C | `scoped` | governs which artefacts reach the host trees at all |
|
|
139
|
+
| `projection.rule_workspaces` | C | 9 workspaces | allowlist deciding which rules reach the agent |
|
|
140
|
+
| `projection.rule_packs` | C | `[]` | second allowlist axis on rule projection |
|
|
141
|
+
| `discipline_profile` | C | `__DISCIPLINE_PROFILE__` | master switch for the discipline rule tier |
|
|
142
|
+
| `rule_loading_tier` | C | `__RULE_LOADING_TIER__` | legacy master switch for rule loading |
|
|
143
|
+
| `lean_projection.mode` | C | `eager-all` | `thin` removes rule bodies from the agent's context |
|
|
144
|
+
| `telegraph.speak` | C | `false` | ships a rule body; a token-cost lever in both directions |
|
|
145
|
+
| `telegraph.speak_scope` | A | `"off"` | output register once that rule ships |
|
|
146
|
+
| `tokens.rich_skills` | C | `"on"` | token-spend lever |
|
|
147
|
+
| `cost.budgets.daily` | C | `0` | rolling spend ceiling |
|
|
148
|
+
| `cost.budgets.weekly` | C | `0` | rolling spend ceiling |
|
|
149
|
+
| `cost.budgets.monthly` | C | `0` | rolling spend ceiling |
|
|
150
|
+
| `cost.budgets.per_tier.cheap` | C | `null` | per-tier spend ceiling |
|
|
151
|
+
| `cost.budgets.per_tier.medium` | C | `null` | per-tier spend ceiling |
|
|
152
|
+
| `cost.budgets.per_tier.strong` | C | `null` | per-tier spend ceiling |
|
|
153
|
+
| `cost.enforcement` | C | `advisory` | decides whether a breach blocks or is merely logged |
|
|
154
|
+
| `model.auto_switch` | C | `suggest` | authorises native model overrides, which is spend |
|
|
155
|
+
| `personal.ide` | C | `""` | names a binary the agent would execute |
|
|
156
|
+
| `personal.open_edited_files` | B | `false` | starts invoking that binary on every edit |
|
|
157
|
+
| `personal.rtk_installed` | A | `false` | auto-detected machine fact |
|
|
158
|
+
| `personal.minimal_output` | A | `true` | reply-shape preference |
|
|
159
|
+
| `personal.play_by_play` | A | `false` | narration preference |
|
|
160
|
+
| `personal.canary_name` | B | `""` | arms the session-degradation canary |
|
|
161
|
+
| `personal.pr_comment_bot_icon` | A | `false` | comment cosmetics |
|
|
162
|
+
| `personal.pr_progress_comments` | C | `false` | authorises unsolicited outbound comments on a PR |
|
|
163
|
+
| `personal.autonomy` | C | `auto` | suppresses confirmation questions |
|
|
164
|
+
| `personal.user_type` | C | `"__USER_TYPE__"` | master axis filtering the projected surface |
|
|
165
|
+
| `verbosity.intent_announcements` | A | `false` | narration preference |
|
|
166
|
+
| `verbosity.preview_artifacts` | C | `false` | removes pre-action review of commits, PRs, and branches |
|
|
167
|
+
| `verbosity.routine_confirmations` | C | `false` | removes confirmation prompts |
|
|
168
|
+
| `verbosity.offer_council_in_delivery` | A | `false` | offers a paid step; never takes it |
|
|
169
|
+
| `verbosity.post_action_reports` | A | `minimal` | size of a status block |
|
|
170
|
+
| `project.pr_template` | C | `.github/pull_request_template.md` | filesystem path whose contents reach an outbound PR |
|
|
171
|
+
| `project.upstream_repo` | C | `""` | destination of outbound improvement PRs |
|
|
172
|
+
| `project.improvement_pr_branch_prefix` | A | `improve/agent-` | branch-name cosmetics |
|
|
173
|
+
| `github.pr_reply_method` | A | `create_review_comment` | picks between two endpoints of one operation |
|
|
174
|
+
| `augment.rules_use_symlinks` | A | `false` | reversible install mechanics |
|
|
175
|
+
| `eloquent.access_style` | A | `getters_setters` | code convention |
|
|
176
|
+
| `chat_history.enabled` | C | `true` | kill-switch over a path that writes conversation content to disk |
|
|
177
|
+
| `chat_history.frequency` | C | `__CHAT_HISTORY_FREQUENCY__` | capture granularity — a value that reduces what is captured erases the trail |
|
|
178
|
+
| `chat_history.max_size_kb` | C | `__CHAT_HISTORY_MAX_SIZE_KB__` | cap on how much conversation lands on disk |
|
|
179
|
+
| `chat_history.on_overflow` | C | `__CHAT_HISTORY_ON_OVERFLOW__` | decides what survives when the log fills; an audit-integrity choice, not a preference |
|
|
180
|
+
| `chat_history.text_limits.user` | C | `0` | cap on how much user text is written to disk |
|
|
181
|
+
| `chat_history.text_limits.agent` | C | `5000` | cap on how much agent text is written to disk |
|
|
182
|
+
| `chat_history.text_limits.tool` | C | `200` | cap on how much tool payload is written to disk |
|
|
183
|
+
| `chat_history.text_limits.phase` | C | `200` | cap on how much phase text is written to disk |
|
|
184
|
+
| `pipelines.skill_improvement` | A | `true` | proposes a capture; the user still decides |
|
|
185
|
+
| `reasoning.enabled` | C | `true` | master switch for the agent's own reasoning discipline |
|
|
186
|
+
| `reasoning.auto_gate` | C | `true` | decides when that discipline engages |
|
|
187
|
+
| `reasoning.components.orchestrator` | C | `true` | a component of the agent's own discipline |
|
|
188
|
+
| `reasoning.components.notes_first` | C | `true` | a component of the agent's own discipline |
|
|
189
|
+
| `reasoning.components.grounding` | C | `true` | a component of the agent's own discipline |
|
|
190
|
+
| `reasoning.components.intent` | C | `true` | a component of the agent's own discipline |
|
|
191
|
+
| `reasoning.components.complexity_first` | C | `true` | a component of the agent's own discipline |
|
|
192
|
+
| `reasoning.components.verifier_default` | C | `true` | disabling it removes a verification step |
|
|
193
|
+
| `reasoning.components.prediction_tracking` | C | `true` | a component of the agent's own discipline |
|
|
194
|
+
| `reasoning.components.decision_ledger` | C | `true` | a component of the agent's own discipline |
|
|
195
|
+
| `reasoning.components.uncertainty_budget` | C | `true` | a component of the agent's own discipline |
|
|
196
|
+
| `roadmap.skip_pre_run_gate` | C | `true` | disables a pre-run confirmation gate |
|
|
197
|
+
| `roadmap.quality_cadence` | C | `end_of_roadmap` | governs when verification runs |
|
|
198
|
+
| `roadmap.dashboard_regen_cadence` | A | `every_5_steps` | beat of a derived view |
|
|
199
|
+
| `roadmap.horizon_weeks` | C | `0` | a non-zero value relaxes a lint's plate-token ban |
|
|
200
|
+
| `planning.challenge_on_create` | C | `true` | disables the plan-confidence gate |
|
|
201
|
+
| `planning.risk_review` | C | `true` | disables the risk-register validator |
|
|
202
|
+
| `planning.completion_review` | C | `true` | disables the completion-review validator |
|
|
203
|
+
| `quality.local_auto_run` | C | `false` | governs whether local verification runs at all |
|
|
204
|
+
| `quality.wait_for_remote_ci` | C | `false` | governs whether the authoritative gate is waited on |
|
|
205
|
+
| `design.fidelity_mode` | C | `strict` | strict-mode selector, one of whose values is a Hard Floor |
|
|
206
|
+
| `consistency.cross_source` | C | `"on"` | disables the cross-source discrepancy gate |
|
|
207
|
+
| `screenshots.identity_allowlist` | C | `[]` | allowlist of identities that ship unredacted |
|
|
208
|
+
| `screenshots.forbid_terminal_capture` | C | `true` | kill-switch over the highest-leak capture path |
|
|
209
|
+
| `screenshots.data_bearing_gate` | C | `"on"` | the human-confirmation gate over a published egress |
|
|
210
|
+
| `code_style.docblocks` | A | `minimal` | code convention |
|
|
211
|
+
| `subagents.enabled` | C | `true` | kill-switch for the whole subagent layer |
|
|
212
|
+
| `subagents.auto` | C | `"on"` | dispatches work without asking |
|
|
213
|
+
| `subagents.downshift` | C | `true` | routes to another model tier, which is spend and quality |
|
|
214
|
+
| `subagents.budget_routing` | C | `ask` | the budget-aware routing gate |
|
|
215
|
+
| `subagents.quota_arbitrage` | C | `true` | spends from a separate quota pool |
|
|
216
|
+
| `subagents.model_map.lite` | C | `""` | names an external model endpoint |
|
|
217
|
+
| `subagents.model_map.medium` | C | `""` | names an external model endpoint |
|
|
218
|
+
| `subagents.model_map.high` | C | `""` | names an external model endpoint |
|
|
219
|
+
| `subagents.host_capabilities` | C | `{}` | overrides the host capability manifest |
|
|
220
|
+
| `subagents.implementer_model` | C | `""` | names an external model endpoint |
|
|
221
|
+
| `subagents.judge_model` | C | `""` | names an external model endpoint |
|
|
222
|
+
| `subagents.max_parallel` | C | `3` | parallelism cap, and therefore a spend rate |
|
|
223
|
+
| `subagents.adversarial_council` | C | `"off"` | governs a paid verification step |
|
|
224
|
+
| `worktrees.mode` | C | `ask` | standing permission for autonomous worktree creation |
|
|
225
|
+
| `ai_team.enabled` | C | `false` | authorises paid external CLI calls |
|
|
226
|
+
| `ai_team.model` | C | `auto` | names an external model |
|
|
227
|
+
| `ai_team.allow_delegate` | C | `false` | grants an external tool write access to the repository |
|
|
228
|
+
| `ai_team.max_calls_per_day` | C | `50` | quota cap on a shared budget |
|
|
229
|
+
| `ai_team.suppress_setup_hint` | A | `false` | hint cosmetics |
|
|
230
|
+
| `ai_team.review_gate.managed` | C | `false` | governs an upstream review gate |
|
|
231
|
+
| `ai_team.review_gate.max_consecutive_blocks` | C | `3` | circuit-breaker threshold |
|
|
232
|
+
| `onboarding.onboarded` | C | `false` | flipping it bypasses the onboarding gate |
|
|
233
|
+
| `commands.auto_detect` | C | `enabled` | kill-switch for orchestrator auto-detection |
|
|
234
|
+
| `commands.suggestion.enabled` | A | `true` | a convenience layer; governs no gate and no spend |
|
|
235
|
+
| `commands.suggestion.confidence_floor` | A | `0.6` | tuning of that convenience layer |
|
|
236
|
+
| `commands.suggestion.cooldown_seconds` | A | `600` | tuning of that convenience layer |
|
|
237
|
+
| `commands.suggestion.max_options` | A | `4` | tuning of that convenience layer |
|
|
238
|
+
| `commands.suggestion.blocklist` | C | `[]` | a deny-list |
|
|
239
|
+
| `commands.create_pr.preview_description` | C | `false` | removes pre-publish review of an outbound artefact |
|
|
240
|
+
| `commands.create_pr.detail_level` | A | `min` | verbosity of a generated body |
|
|
241
|
+
| `commands.create_pr.api_examples` | A | `true` | verbosity of a generated body |
|
|
242
|
+
| `commands.create_pr.screenshots` | C | `false` | puts captured screenshots into a published PR body |
|
|
243
|
+
| `commands.create_pr.ui_paths` | C | `[]` | glob allowlist |
|
|
244
|
+
| `commands.create_pr.api_paths` | C | `[]` | glob allowlist |
|
|
245
|
+
| `memory.cadence` | C | `always` | suppressing the visibility line hides what the agent learned from the user |
|
|
246
|
+
| `memory.review_threshold` | A | `10` | when a review preview surfaces; governs no gate |
|
|
247
|
+
| `memory.redact_patterns` | C | `[]` | deny-list of secret and PII regexes |
|
|
248
|
+
| `memory.session_index` | A | `"off"` | injects a compact index at session start |
|
|
249
|
+
| `memory.learn_on_session_end` | B | `false` | turns on automatic memory writes at session end |
|
|
250
|
+
| `knowledge.global_sharing.enabled` | C | `true` | kill-switch over cross-project egress |
|
|
251
|
+
| `knowledge.global_sharing.allowed_tiers` | C | `[public]` | allowlist for that egress |
|
|
252
|
+
| `knowledge.global_sharing.redaction.enabled` | C | `true` | disabling it removes the redaction floor |
|
|
253
|
+
| `knowledge.global_sharing.redaction.halt_on_trigger` | C | `true` | disabling it removes halt-and-prompt |
|
|
254
|
+
| `knowledge.global_sharing.auto_promote_threshold` | C | `2` | threshold governing that egress |
|
|
255
|
+
| `knowledge.global_sharing.freshness.hypothesis_after_days` | A | `90` | freshness heuristic on a card already shared |
|
|
256
|
+
| `knowledge.global_sharing.freshness.stale_after_days` | A | `180` | freshness heuristic on a card already shared |
|
|
257
|
+
| `hooks.concern_budget.max_per_event` | C | `8` | budget cap on hook concerns |
|
|
258
|
+
| `hooks.concern_budget.tier1_concerns` | C | `[]` | allowlist of concerns permitted to fail closed |
|
|
259
|
+
| `hooks.concern_budget.hard_fail` | C | `false` | weakens the budget gate to warn-only |
|
|
260
|
+
| `hooks.injection_scan.enabled` | C | `false` | the prompt-injection scanner |
|
|
261
|
+
| `hooks.rtk_wrap.enabled` | C | `false` | configures code that runs on every tool call |
|
|
262
|
+
| `hooks.design_slop.enabled` | C | `false` | configures code that runs on every tool call |
|
|
263
|
+
| `hooks.code_graph.enabled` | C | `false` | configures code that runs on every tool call |
|
|
264
|
+
| `decision_engine.surface_traces` | C | `false` | the decision engine’s own black box; the agent must not be able to close it |
|
|
265
|
+
| `decision_engine.min_confidence` | C | `"off"` | the confidence gate |
|
|
266
|
+
| `decision_engine.block_on_risk` | C | `"off"` | the risk-class gate |
|
|
267
|
+
| `decision_engine.require_memory_hits` | C | `false` | a phase gate |
|
|
268
|
+
| `decision_engine.on_block` | C | `stop` | `warn` advances past a gate that fired |
|
|
269
|
+
| `decision_engine.ask_timeout_seconds` | C | `30` | how long a fired gate waits before falling back |
|
|
270
|
+
| `decision_engine.on_block_fallback` | C | `stop` | fail-safe versus fail-open after that timeout |
|
|
271
|
+
| `update_check.enabled` | C | `true` | a background safety check; disabling it pins the user to known-vulnerable code |
|
|
272
|
+
| `explain.enable_last` | A | `true` | a read-only diagnostics surface |
|
|
273
|
+
| `legal_review_prep.acknowledged` | C | `false` | the consent gate; the safety floor requires the wizard checkbox |
|
|
274
|
+
| `legal_review_prep.consented_at` | C | `""` | the record of that consent |
|
|
275
|
+
| `legal_review_prep.require_council` | C | `true` | a fail-closed defence-in-depth gate |
|
|
276
|
+
|
|
277
|
+
## The council review of the C list (2026-08-05)
|
|
278
|
+
|
|
279
|
+
The C list was reviewed by a two-member AI council (`claude-sonnet-4-5`,
|
|
280
|
+
`gpt-4o`), asked one question in one direction only: **name every key
|
|
281
|
+
classified A or B that should be C.** Arguing a C key down to A was explicitly
|
|
282
|
+
out of scope, because over-fencing is not the failure this fence exists to
|
|
283
|
+
prevent. The review ran because completeness is the property a single author is
|
|
284
|
+
worst placed to certify about their own list.
|
|
285
|
+
|
|
286
|
+
**Converged, and applied — five keys moved A → C:**
|
|
287
|
+
|
|
288
|
+
| Key | The abuse both members named |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `update_check.enabled` | a background safety check; switching it off pins the user to known-vulnerable code |
|
|
291
|
+
| `chat_history.frequency` | reduces what is captured, so the trail thins |
|
|
292
|
+
| `chat_history.on_overflow` | decides what survives when the log fills |
|
|
293
|
+
| `memory.cadence` | suppresses the line that tells the user what the agent learned |
|
|
294
|
+
| `decision_engine.surface_traces` | the decision engine's own black box |
|
|
295
|
+
|
|
296
|
+
**Converged, and applied — a missing test.** All five share a shape the original
|
|
297
|
+
seven tests could not express: they do not let an attacker *do* more, they let
|
|
298
|
+
an attacker *hide* what was done. That is now test 8, and it is the one to reach
|
|
299
|
+
for first when classifying a new key, because it is the one the other seven
|
|
300
|
+
cannot cover by analogy.
|
|
301
|
+
|
|
302
|
+
**Converged, and applied — the B invariant had a hole.** Conservative-default
|
|
303
|
+
alone is satisfiable by an agent that picks a coercive moment to ask. Half two
|
|
304
|
+
of the invariant above is that finding.
|
|
305
|
+
|
|
306
|
+
**Split, and recorded rather than resolved:** `github.pr_reply_method`. One
|
|
307
|
+
member wanted C ("redirects a communication path"); the other kept A, on the
|
|
308
|
+
concrete ground that both enum values post a comment on the same PR through the
|
|
309
|
+
same API — the worst outcome is a comment of the wrong type, which is visible
|
|
310
|
+
and cosmetic. Kept **A** on the stronger-evidenced side. If that key ever gains
|
|
311
|
+
a value that targets a different destination, it becomes C under test 6 and this
|
|
312
|
+
paragraph is the reason to revisit it.
|
|
313
|
+
|
|
314
|
+
**Rejected, with the reason:** `pipelines.skill_improvement`, `personal.rtk_installed`,
|
|
315
|
+
`commands.create_pr.api_examples`, and `knowledge.global_sharing.freshness.*`
|
|
316
|
+
were each proposed for C by one member and argued back to A by the other.
|
|
317
|
+
The load-bearing distinctions: an auto-detected fact is not a control surface; a
|
|
318
|
+
tuning knob inside an already-guarded flow is not a second gate; and a boolean
|
|
319
|
+
that says "include examples" is bounded by the C-classed allow-list that decides
|
|
320
|
+
*which* examples. `pipelines.skill_improvement` carries a stated litmus — if its
|
|
321
|
+
approval ever stops being a separate mechanical step and becomes an in-turn
|
|
322
|
+
"ok", it moves to C.
|
|
323
|
+
|
|
324
|
+
## Adding a key
|
|
325
|
+
|
|
326
|
+
1. Add it to `src/config/agent-settings.template.yml` and to
|
|
327
|
+
`src/server/schemas/settings.ts` (the parity test enforces both directions).
|
|
328
|
+
2. Add a row here. The lint fails until you do — that is the point.
|
|
329
|
+
3. If you reach for **B**, check the invariant above first: the template default
|
|
330
|
+
must be the conservative value.
|
|
331
|
+
4. If you reach for **A**, check it against all eight C tests — test 8 first,
|
|
332
|
+
because it is the one the other seven cannot cover by analogy. If any of them
|
|
333
|
+
fires, it is C.
|
|
334
|
+
|
|
335
|
+
## See also
|
|
336
|
+
|
|
337
|
+
- [`settings-api.md`](settings-api.md) — the GUI's REST surface; its `PUT` route
|
|
338
|
+
is the one write path a C-class key may travel.
|
|
339
|
+
- `src/scripts/lint_settings_classes.ts` — the gate.
|
|
340
|
+
- `src/config/agent-settings.template.yml` — the defaults-and-schema source.
|
|
341
|
+
- `tests/server/schemas/parity.test.ts` — the template↔schema parity gate this
|
|
342
|
+
contract sits beside and never relaxes.
|
|
@@ -6,7 +6,22 @@ _Origin: migrated from `src/rules/artifact-drafting-protocol.md` per the P4 patt
|
|
|
6
6
|
|
|
7
7
|
## Phase A — Understand
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Question zero, asked first: should this artifact exist at all?**
|
|
10
|
+
|
|
11
|
+
Before the five below, put the extend-versus-create question to the user —
|
|
12
|
+
*is there an existing artifact this belongs in?* It used to live in Phase B,
|
|
13
|
+
after the research, and it was measured moving: one source found that the same
|
|
14
|
+
stop placed after work had begun was passed straight through, because mid-build
|
|
15
|
+
momentum beats restraint. A stop that only fires once you are invested is a stop
|
|
16
|
+
that does not fire.
|
|
17
|
+
|
|
18
|
+
Asking it first costs one question and is answerable without the research: the
|
|
19
|
+
user usually knows whether they are extending something. Phase B still runs the
|
|
20
|
+
overlap scan — that is what turns a guess into a verdict — but it now *confirms
|
|
21
|
+
or overturns* an answer already on the record rather than raising the question
|
|
22
|
+
for the first time at the point where a draft is half-formed.
|
|
23
|
+
|
|
24
|
+
Ask up to **5** further clarifying questions (numbered options, each with a
|
|
10
25
|
*"skip / I don't know yet"* escape):
|
|
11
26
|
|
|
12
27
|
1. **Problem** — what does this solve that no existing artifact solves?
|
|
@@ -26,13 +41,19 @@ Run the **search protocol** from
|
|
|
26
41
|
grep with **solution-words AND problem-words**, scan sub-directory
|
|
27
42
|
taxonomies, then **open and skim** the 3 nearest matches. A negative grep
|
|
28
43
|
alone is not proof of no overlap. Report the top 3-5 most-similar
|
|
29
|
-
artifacts and ask (numbered options)
|
|
44
|
+
artifacts and ask (numbered options) — this **confirms or overturns** the
|
|
45
|
+
question-zero answer from Phase A with evidence, rather than raising it for the
|
|
46
|
+
first time:
|
|
30
47
|
|
|
31
48
|
- Extend an existing one?
|
|
32
49
|
- Create a new one — gap is real?
|
|
33
50
|
- Show overlap first?
|
|
34
51
|
- Promote via `learning-to-rule-or-skill` instead?
|
|
35
52
|
|
|
53
|
+
A Phase-B verdict that contradicts the Phase-A answer is the useful outcome, not
|
|
54
|
+
an embarrassment: it is the research doing its job. Surface the contradiction
|
|
55
|
+
and let the user re-decide.
|
|
56
|
+
|
|
36
57
|
Carry the summary into the commit message (*"Reviewed before drafting:
|
|
37
58
|
X, Y"*).
|
|
38
59
|
|