@event4u/agent-config 14.22.0 → 14.23.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +159 -380
- package/README.md +1 -1
- package/dist/agent-src/commands/pr/merge.md +47 -38
- package/dist/agent-src/commands/roadmap/next.md +8 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -1
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +52 -4
- package/dist/cli-delegate/{chunk-TGIPZBCW.js → chunk-2KKXJW25.js} +22 -8
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
- package/dist/cli-delegate/{chunk-BIXEDQPZ.js → chunk-KCNTUSPI.js} +1 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +20 -12
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +3 -13
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +8 -8
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +880 -289
- package/dist/install/install.mjs +15 -3
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +5458 -2453
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +20 -20
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/schemas/settings.js +16 -3
- package/docs/MIGRATION.md +56 -0
- package/docs/contracts/settings-classes.md +39 -7
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/src/config/agent-settings.template.yml +47 -1
- package/src/config/continuity-surface.json +7 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-violation-baselines.json +2 -2
- package/src/config/hook-token-budget.json +7 -6
- package/src/config/packed-binary-manifest.json +1 -1
- package/src/config/routing-coverage-seed.json +9 -2
- package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- package/src/scripts/_lib/continuity_slot.ts +295 -0
- package/src/scripts/_lib/continuity_writer.ts +253 -0
- package/src/scripts/_lib/hook_settings.ts +85 -0
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/packed_binary_predicate.ts +37 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +346 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- package/src/scripts/audit_mcp_tools.ts +69 -25
- package/src/scripts/build_mcp_catalog.ts +24 -1
- package/src/scripts/check_host_tree_parity.ts +291 -0
- package/src/scripts/check_preamble_payload_budget.ts +136 -7
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +79 -0
- package/src/scripts/code_graph/cli.ts +258 -7
- package/src/scripts/code_graph/detect.ts +38 -0
- package/src/scripts/code_graph/query.ts +25 -2
- package/src/scripts/code_graph/sqlite_store.ts +16 -1
- package/src/scripts/code_graph/types.ts +52 -3
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +20 -20
- package/src/scripts/handoff_context_hook.ts +39 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +24 -1
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/code_graph_context_hook.ts +7 -18
- package/src/scripts/hooks/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +107 -20
- package/src/scripts/hooks/session_eol_hook.ts +120 -2
- package/src/scripts/hot_context_hook.ts +62 -7
- package/src/scripts/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +3 -13
- package/src/scripts/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- package/src/scripts/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +81 -8
- package/src/server/schemas/settings.ts +23 -4
- package/src/vendor/grammars/README.md +27 -2
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `.augment/` projections.
|
|
20
20
|
*
|
|
21
21
|
* NOTE (faithful literal): the thin-entry pointer keeps the verbatim
|
|
22
|
-
* `Body: [`{rule_id}`](
|
|
22
|
+
* `Body: [`{rule_id}`](../../dist/agent-src/rules/{rule_id}.md)`
|
|
23
23
|
* Markdown link from the retired Python implementation. The same-basename `.py` twin
|
|
24
24
|
* carries that literal, so the ADR-051 legacy-path guard exempts this `.ts`
|
|
25
25
|
* file by twin-parity.
|
|
@@ -34,6 +34,7 @@ import * as fs from 'node:fs';
|
|
|
34
34
|
import * as path from 'node:path';
|
|
35
35
|
import process from 'node:process';
|
|
36
36
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
37
|
+
import { pathOnlyRuleIds, triggerlessRuleIds, } from './_lib/rule_injection.js';
|
|
37
38
|
import * as token_count from './_lib/token_count.js';
|
|
38
39
|
const _HERE = fileURLToPath(import.meta.url);
|
|
39
40
|
// REPO_ROOT = Path(__file__).resolve().parent.parent.parent — three dirs up
|
|
@@ -72,21 +73,85 @@ export function kernel_ids() {
|
|
|
72
73
|
* visible instead of being absorbed into the pointer count
|
|
73
74
|
* (road-to-trigger-delivered-rule-bodies 1.3).
|
|
74
75
|
*/
|
|
76
|
+
/**
|
|
77
|
+
* Rules whose ONLY triggers are path-shaped — reachable from `pre_tool_use`
|
|
78
|
+
* and from no other slot (road-to-delivery-for-every-host 3.2).
|
|
79
|
+
*
|
|
80
|
+
* They must never be thinned while the delivery concern is NOT bound on
|
|
81
|
+
* `pre_tool_use`, because a stub plus a slot nobody listens on is a rule that
|
|
82
|
+
* reaches the model at no scope at all. This is the same failure the
|
|
83
|
+
* `no_trigger_ids` exemption prevents, one step out: there the rule has nothing
|
|
84
|
+
* to match on, here it has nothing to match ON.
|
|
85
|
+
*
|
|
86
|
+
* Stated as a PROPERTY rather than a list of ids. If the concern is ever bound
|
|
87
|
+
* on `pre_tool_use` again, this exemption is what should be reconsidered — not
|
|
88
|
+
* three names someone has to remember.
|
|
89
|
+
*
|
|
90
|
+
* WHAT THIS EXEMPTION DOES **NOT** COVER — recorded 2026-09-08, R2 finding 1,
|
|
91
|
+
* because the silence read as coverage. The predicate is `every` trigger
|
|
92
|
+
* path-shaped. A rule with BOTH path and non-path triggers is therefore THINNED,
|
|
93
|
+
* and its path-shaped half then has no carrier at all: the stub carries no
|
|
94
|
+
* frontmatter (`condense.ts` writes it with `_writeText`, bypassing
|
|
95
|
+
* `_emit_claude_rule`'s host-native `paths:` key), `pre_tool_use` lost the
|
|
96
|
+
* binding under owner ruling E2, and `user_prompt_submit` never populates
|
|
97
|
+
* `openFiles`. Measured over the current `dist/router.json`: **18 such rules**,
|
|
98
|
+
* all non-kernel and all thinned — augment-edit-discipline, design-fidelity,
|
|
99
|
+
* doc-screenshot-hygiene, domain-adoption-policy,
|
|
100
|
+
* framework-neutrality-in-generic-skills, image-likeness-and-rights,
|
|
101
|
+
* laravel-translations, lethal-trifecta-guard, linked-projects-onboarding-gate,
|
|
102
|
+
* low-impact-corpus-privacy-floor, markdown-safe-codeblocks, onboarding-gate,
|
|
103
|
+
* persona-governance, php-coding, provider-lifecycle-discipline,
|
|
104
|
+
* roadmap-ci-steps-policy, roadmap-progress-sync, settings-ask-protocol.
|
|
105
|
+
*
|
|
106
|
+
* They do NOT reach the model at no scope: every non-path route
|
|
107
|
+
* (keyword / phrase / command) still delivers the body through the concern, and
|
|
108
|
+
* on `claude-code` they never had a separate host-native path route to lose —
|
|
109
|
+
* `_claude_paths_plan` emits no `paths:` for a mixed-trigger rule on purpose, so
|
|
110
|
+
* under `eager-all` they loaded UNCONDITIONALLY. What is lost is the difference
|
|
111
|
+
* between unconditional and prompt-triggered: a session that touches a matching
|
|
112
|
+
* file and says nothing that matches gets nothing.
|
|
113
|
+
*
|
|
114
|
+
* The labelled corpus cannot see this and the endpoint says so rather than
|
|
115
|
+
* implying otherwise — `model_rule_injection --endpoints` (b) now publishes the
|
|
116
|
+
* shipped `user_prompt_submit` reach beside the pre-registered reading, and
|
|
117
|
+
* every one of these 18 has at least one prompt-matching positive, so none
|
|
118
|
+
* appears in its "reachable only via a path trigger" column.
|
|
119
|
+
*
|
|
120
|
+
* WHY THIS IS NOT WIDENED TO `some` HERE. Measured: exempting all 18 moves the
|
|
121
|
+
* thin rule layer 23,592 -> 39,921 GPT tok (+16,329, +69 %), which is a budget
|
|
122
|
+
* move of a magnitude no agent may take, and it would erase most of the saving
|
|
123
|
+
* the flip is licensed on. Restoring a path route needs either that exemption
|
|
124
|
+
* under a re-anchored baseline or a `pre_tool_use` binding under a raised slot
|
|
125
|
+
* cap — both owner-reserved, so the closure is tracked rather than taken here.
|
|
126
|
+
* ADR-267's Consequences section names the receiver.
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* ONE spelling of the router-derived sets, shared with the injector (R2
|
|
130
|
+
* finding 11).
|
|
131
|
+
*
|
|
132
|
+
* `path_only_ids` and `no_trigger_ids` were each a second, hand-rolled
|
|
133
|
+
* implementation of `_lib/rule_injection.ts::pathOnlyRuleIds` /
|
|
134
|
+
* `triggerlessRuleIds` — verified identical over the current router at the time
|
|
135
|
+
* (symmetric difference empty), so drift risk rather than a live defect. It is
|
|
136
|
+
* exactly the hazard `THIN_ENTRY_MARKER`'s own comment argues against one
|
|
137
|
+
* screen below ("a gate that re-spelled this string would drift from the writer
|
|
138
|
+
* silently"), and the two consumers here are the PROJECTOR's exemption set and
|
|
139
|
+
* the INJECTOR's reachability set: they must not be able to disagree about which
|
|
140
|
+
* rules the delivery path can reach.
|
|
141
|
+
*
|
|
142
|
+
* The finding named `path_only_ids`. The defect-pattern sweep found the SECOND
|
|
143
|
+
* instance one function down — `no_trigger_ids` against `triggerlessRuleIds` —
|
|
144
|
+
* and it is delegated in the same change. Count: 2 of 2 duplicated
|
|
145
|
+
* router-derived sets in this file, 0 remaining.
|
|
146
|
+
*/
|
|
147
|
+
function _router() {
|
|
148
|
+
return JSON.parse(fs.readFileSync(ROUTER, 'utf-8'));
|
|
149
|
+
}
|
|
150
|
+
export function path_only_ids() {
|
|
151
|
+
return pathOnlyRuleIds(_router());
|
|
152
|
+
}
|
|
75
153
|
export function no_trigger_ids() {
|
|
76
|
-
|
|
77
|
-
const out = new Set();
|
|
78
|
-
for (const tier of ['tier_1', 'tier_2']) {
|
|
79
|
-
const entries = data[tier];
|
|
80
|
-
if (!Array.isArray(entries))
|
|
81
|
-
continue;
|
|
82
|
-
for (const e of entries) {
|
|
83
|
-
const obj = e;
|
|
84
|
-
const t = obj.triggers;
|
|
85
|
-
if (!Array.isArray(t) || t.length === 0)
|
|
86
|
-
out.add(String(obj.id));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return out;
|
|
154
|
+
return new Set(triggerlessRuleIds(_router()));
|
|
90
155
|
}
|
|
91
156
|
/**
|
|
92
157
|
* id → workspaces for every non-kernel router entry (router.json schema v2,
|
|
@@ -179,6 +244,42 @@ function _trigger_hint(fm) {
|
|
|
179
244
|
function _title(s) {
|
|
180
245
|
return s.replace(/[A-Za-z]+/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase());
|
|
181
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* The marker that makes a projected entry recognisable as a stub.
|
|
249
|
+
*
|
|
250
|
+
* Exported so the WRITER and every DETECTOR share one definition. A gate that
|
|
251
|
+
* re-spelled this string would drift from the writer silently, and the failure
|
|
252
|
+
* would be invisible in exactly the direction that matters: a stub the gate
|
|
253
|
+
* fails to recognise reads as a complete rule body.
|
|
254
|
+
*/
|
|
255
|
+
export const THIN_ENTRY_MARKER = '> Routed rule — load the body on trigger-match.';
|
|
256
|
+
/** Is this projected entry a pointer stub rather than a rule body? */
|
|
257
|
+
export function is_thin_entry(text) {
|
|
258
|
+
return text.includes(THIN_ENTRY_MARKER);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Where a stub tells a reader the body actually is.
|
|
262
|
+
*
|
|
263
|
+
* FIXED 2026-09-07: this pointed into the uncondensed source tree ADR-051
|
|
264
|
+
* retired — the literal path is deliberately NOT reproduced here, because
|
|
265
|
+
* `check_no_uncondensed_refs` matches the string and cannot tell a repair
|
|
266
|
+
* record from a live reference; `git log -S` on this file finds the old value.
|
|
267
|
+
* It exists in no checkout, so EVERY stub's fallback pointer
|
|
268
|
+
* resolved to nothing — invisible under `delivery`, where the hook loads the
|
|
269
|
+
* body from `dist/agent-src/rules` and never follows this link, and total under
|
|
270
|
+
* `thin`, where the pointer is the only path to the body there is.
|
|
271
|
+
*
|
|
272
|
+
* KNOWN LIMIT, stated rather than discovered later: one relative prefix cannot
|
|
273
|
+
* be right for every tree, because they sit at different depths — `.claude/rules`
|
|
274
|
+
* and `.cursor/rules` are two levels below the repo root and `.clinerules` is
|
|
275
|
+
* one. `../../` is correct for the first two, which are the only trees the
|
|
276
|
+
* shipped `lean_projection.hosts` default can thin. A stub in `.clinerules`
|
|
277
|
+
* needs an operator to have added `cline` to that list, and its pointer is off
|
|
278
|
+
* by one level. That is strictly better than the previous state, where the
|
|
279
|
+
* pointer was dead in all three, and it is recorded here so the remaining case
|
|
280
|
+
* is a known limit rather than a surprise.
|
|
281
|
+
*/
|
|
282
|
+
const BODY_LINK_PREFIX = '../../dist/agent-src/rules/';
|
|
182
283
|
/** Build the minimal progressive-disclosure pointer for a non-kernel rule. */
|
|
183
284
|
export function thin_entry(rule_id, text) {
|
|
184
285
|
const [fm] = split_frontmatter(text);
|
|
@@ -187,13 +288,25 @@ export function thin_entry(rule_id, text) {
|
|
|
187
288
|
const title = _title(rule_id.replace(/-/g, ' '));
|
|
188
289
|
const fires = hint ? ` Fires on: ${hint}.` : '';
|
|
189
290
|
return (`## ${title}\n` +
|
|
190
|
-
|
|
191
|
-
`Body: [\`${rule_id}\`](
|
|
291
|
+
`${THIN_ENTRY_MARKER}${fires} ${desc} ` +
|
|
292
|
+
`Body: [\`${rule_id}\`](${BODY_LINK_PREFIX}${rule_id}.md)\n`);
|
|
192
293
|
}
|
|
193
294
|
/** Map {filename: thin_or_full_text} for every rule. Kernel stays full. */
|
|
194
|
-
export function build_thin(rules_dir = RULES_SOURCE, scope = null
|
|
295
|
+
export function build_thin(rules_dir = RULES_SOURCE, scope = null,
|
|
296
|
+
/**
|
|
297
|
+
* Optional sink for the D3 diagnostic
|
|
298
|
+
* (road-to-delivery-for-every-host 2.2).
|
|
299
|
+
*
|
|
300
|
+
* An `auto` rule the router gives NO trigger is already kept full-bodied by
|
|
301
|
+
* `noTrigger` below, which is the substantive protection and predates this
|
|
302
|
+
* roadmap. What did not exist is any way to SEE it happen: the rule is
|
|
303
|
+
* silently exempted, so an author who removes a rule's last trigger gets a
|
|
304
|
+
* silently eager rule and no signal. The line makes the exemption audible.
|
|
305
|
+
*/
|
|
306
|
+
announce = null) {
|
|
195
307
|
const kernel = kernel_ids();
|
|
196
308
|
const noTrigger = no_trigger_ids();
|
|
309
|
+
const pathOnly = path_only_ids();
|
|
197
310
|
const wsMap = scope !== null ? rule_workspaces_map() : new Map();
|
|
198
311
|
const out = new Map();
|
|
199
312
|
for (const p of _globSortedMd(rules_dir)) {
|
|
@@ -202,7 +315,14 @@ export function build_thin(rules_dir = RULES_SOURCE, scope = null) {
|
|
|
202
315
|
if (!id_in_scope(stem, scope, kernel, wsMap, fm_workspaces(text))) {
|
|
203
316
|
continue; // out of workspace scope — no body, no pointer line
|
|
204
317
|
}
|
|
205
|
-
const full = kernel.has(stem) || noTrigger.has(stem);
|
|
318
|
+
const full = kernel.has(stem) || noTrigger.has(stem) || pathOnly.has(stem);
|
|
319
|
+
if (announce !== null && noTrigger.has(stem) && !kernel.has(stem)) {
|
|
320
|
+
announce(`D3: trigger-less auto rule ${path.basename(p)} — kept full-bodied, never thinned`);
|
|
321
|
+
}
|
|
322
|
+
if (announce !== null && pathOnly.has(stem) && !kernel.has(stem) && !noTrigger.has(stem)) {
|
|
323
|
+
announce(`E2: path-only auto rule ${path.basename(p)} — kept full-bodied; its only triggers ` +
|
|
324
|
+
`are path-shaped and the delivery concern is not bound on pre_tool_use`);
|
|
325
|
+
}
|
|
206
326
|
out.set(path.basename(p), full ? text : thin_entry(stem, text));
|
|
207
327
|
}
|
|
208
328
|
return out;
|
|
@@ -29,7 +29,8 @@ const confidenceBand = z.enum(['off', 'low', 'medium', 'high']);
|
|
|
29
29
|
const onBlock = z.enum(['stop', 'ask', 'warn']);
|
|
30
30
|
const onBlockFallback = z.enum(['stop', 'warn']);
|
|
31
31
|
const modelAutoSwitch = z.enum(['auto', 'suggest', 'off']);
|
|
32
|
-
const leanProjectionMode = z.enum(['eager-all', 'thin']);
|
|
32
|
+
const leanProjectionMode = z.enum(['eager-all', 'thin', 'delivery']);
|
|
33
|
+
const leanProjectionHost = z.enum(['claude-code', 'cursor', 'cline']);
|
|
33
34
|
const projectionMode = z.enum(['legacy-all', 'scoped']);
|
|
34
35
|
const memoryCadence = z.enum(['auto', 'always', 'never']);
|
|
35
36
|
const projectAudience = z.enum(['self', 'internal', 'client', 'public']);
|
|
@@ -46,8 +47,16 @@ export const settingsSchema = z.object({
|
|
|
46
47
|
rule_loading_tier: ruleLoadingTier.default('balanced').describe('Master switch for which rule tiers load and how cautiously the agent spends tokens. minimal = only the 9 kernel rules (cheapest, fewest guardrails). balanced = kernel + tier-1 (recommended default). full = kernel + tier-1 + tier-2 (most guardrails, highest token cost). custom = roll your own in agents/overrides/. LEGACY: superseded by discipline_profile — when that key is set it wins (mapping: minimal→off, balanced→essential, full→full).'),
|
|
47
48
|
discipline_profile: disciplineProfile.optional().describe('The ONE runtime knob for the discipline-rule tier (successor of rule_loading_tier; council 2026-07-07). off = kernel only (~1x tokens). essential = kernel + the measured lift-carrying rules (~3.3x, keeps the weak-host discipline lift). full = everything (~11.7x, EXPERIMENTAL — residual lift over essential not established). auto = resolve per session against the evidence-gated NULL-lift disable-list in src/config/host-capabilities.yml (measured-null host → off, otherwise → essential). Optional and opt-in until the P1/P2 evidence gates pass (agents/roadmaps/road-to-discipline-profile-tiering.md); absent = legacy rule_loading_tier applies.'),
|
|
48
49
|
lean_projection: z.object({
|
|
49
|
-
mode: leanProjectionMode.default('eager-all').describe('How the per-tool projector emits the rule layer. eager-all = every rule body inlined into every projection (
|
|
50
|
-
|
|
50
|
+
mode: leanProjectionMode.default('eager-all').describe('How the per-tool projector emits the rule layer. eager-all = every rule body inlined into every projection (safe; what an ABSENT key still resolves to, per ADR-267 decision point 4). thin = kernel rules full-bodied + non-kernel rules as router-resolved pointers (~45k GPT-tok lighter per session), with no delivery mechanism. delivery = thin stubs PLUS the rule-inject hook concern, which delivers a body back on a trigger match; this is the value the SHIPPED TEMPLATE carries for Claude Code since ADR-267, and the activation charge for it is paid in src/config/hook-token-budget.json (user_prompt_submit slot sum 4,096 -> 16,384).'),
|
|
51
|
+
hosts: z.array(leanProjectionHost).default(['claude-code']).describe('Which hosts a thinning mode may thin (ADR-267). Absent resolves to [claude-code]. '
|
|
52
|
+
+ 'Only the three hosts with a per-rule rule tree are accepted, and this enum is the '
|
|
53
|
+
+ 'AUTHORING layer: an id outside it is a hard validation FAILURE here and under '
|
|
54
|
+
+ 'validate_agent_settings, never a warning. resolveLeanProjectionHosts is the '
|
|
55
|
+
+ 'READ-TIME backstop for a hand-edited .agent-settings.yml that passed neither, and '
|
|
56
|
+
+ 'THERE the id is dropped with a warning (R2 finding 6 — the two layers answer '
|
|
57
|
+
+ 'differently and neither said so). Every host outside this list receives exactly '
|
|
58
|
+
+ 'what eager-all writes, asserted byte-for-byte by check_host_tree_parity.'),
|
|
59
|
+
}).default({ mode: 'eager-all', hosts: ['claude-code'] }),
|
|
51
60
|
cost: z.object({
|
|
52
61
|
budgets: z.object({
|
|
53
62
|
daily: z.number().min(0).default(0).describe('Daily USD ceiling across all model calls. The agent warns at 50% / 75% / 90% and either stops or warns at 100% depending on cost.enforcement. Set 0 to disable the daily budget entirely.'),
|
|
@@ -229,6 +238,10 @@ export const settingsSchema = z.object({
|
|
|
229
238
|
api_paths: z.array(z.string()).default([]).describe('Optional glob list that makes API-endpoint detection explicit instead of heuristic (e.g. ["app/Http/Controllers/Api/**", "src/pages/api/**"]). Empty (default) = a light path/extension heuristic that fails open.'),
|
|
230
239
|
}),
|
|
231
240
|
}),
|
|
241
|
+
continuity: z.object({
|
|
242
|
+
auto_record: z.enum(['on', 'off']).default('off').describe('Deterministic continuity-record writer at session end (road-to-continuity-writer-activation Phase 1). on = the session-eol concern writes the continuity_record capsule variant on Stop for a substantive session that has claimed a roadmap; every field is computed from on-disk state, with no model spend and no subprocess. off (default) = no automatic record — while session:recycle is still the normal path, a second producer on it before the parity evidence is in would be unverified.'),
|
|
243
|
+
run_checkpoints: z.enum(['on', 'off']).default('on').describe('Deterministic run-checkpoint production at session end (road-to-continuity-writer-activation Phase 1.4). on (default) = a session above the recycle threshold and inside a running roadmap contract leaves agents/runtime/state/checkpoints/<run>.json, so a killed run resumes from a derived checkpoint rather than from bookkeeping. off = no checkpoint; continuity writing, the recycle advisory and the context-fill surface are unaffected. Default ON because it is the behaviour the tree already had — the switch exists to make the three session-end handlers independently disableable, not to change what ships.'),
|
|
244
|
+
}),
|
|
232
245
|
memory: z.object({
|
|
233
246
|
cadence: memoryCadence.default('always').describe('Cadence of the 🧠 memory-visibility line after a memory-consulting step. always (default) = show whenever a memory type was asked; auto = show only when 3+ types were consulted (less noise); never = suppress. Distinct from rule_loading_tier — owns its own key since the 2026-06-01 untangle.'),
|
|
234
247
|
review_threshold: z.number().int().min(0).default(10).describe('Maximum number of memory entries /memory:load surfaces inline before falling back to a summary view. Default 10 keeps the chat readable. Raise to see more candidates, lower to keep the context tight.'),
|
package/docs/MIGRATION.md
CHANGED
|
@@ -42,6 +42,62 @@ reason, which no arithmetic can.
|
|
|
42
42
|
or that promises a future breaking removal, gets a row here in the same commit
|
|
43
43
|
that makes the promise. A promise with no row is not tracked and will be missed.
|
|
44
44
|
|
|
45
|
+
## 14.22.x — the code-graph engine gets readers, and the MCP install hint changes
|
|
46
|
+
|
|
47
|
+
💡 advisory · 🔄 automatic — **one exception, below.**
|
|
48
|
+
|
|
49
|
+
The 14.21.x entry above shipped the engine to consumers. This one ships the
|
|
50
|
+
things that read it (`road-to-a-graph-that-is-shipped`, Phases 3 and 4).
|
|
51
|
+
|
|
52
|
+
### Additive — nothing to do
|
|
53
|
+
|
|
54
|
+
- **Four new `code-graph` subcommands**, all answering questions about a DIFF
|
|
55
|
+
rather than a symbol: `impact --diff <rev>`, `tests-for <symbol>`,
|
|
56
|
+
`untested --diff <rev>`, `dead`. Each prints the resolution mechanisms it
|
|
57
|
+
accepted, the mechanisms it refused, and the graph's three-state staleness.
|
|
58
|
+
No existing subcommand changed, and no new top-level verb was added.
|
|
59
|
+
- **Five new MCP tools** on the existing stdio server — `graph_impact`,
|
|
60
|
+
`graph_tests_for`, `graph_dead`, `graph_query`, `graph_path` — taking the
|
|
61
|
+
catalogue from 31 tools to 36. Four are read-only; `graph_impact` runs
|
|
62
|
+
`git diff --name-only` to resolve its rev and declares `side_effect: shell`
|
|
63
|
+
for that reason.
|
|
64
|
+
- **A new `tests` edge relation**, derived in the build pass from a test file's
|
|
65
|
+
imports. `SCHEMA_VERSION` 3 → 4 and `GRAPH_STORE_VERSION` 3 → 4, so an
|
|
66
|
+
existing cache and its SQLite twin are refused and rebuilt on the next
|
|
67
|
+
`code-graph build`. The cache is derived and disposable
|
|
68
|
+
(ADR-129) — there is nothing to migrate, and a rebuild takes seconds.
|
|
69
|
+
|
|
70
|
+
### The one thing to check — `dead` refuses by default
|
|
71
|
+
|
|
72
|
+
`agent-config code-graph dead` exits **1** with an empty list unless you either
|
|
73
|
+
supply an entry-point list (`--entry-points <file>`) or accept the gap
|
|
74
|
+
(`--accept-missing-exports`). That is deliberate: this engine records no
|
|
75
|
+
exportedness, so without a supplied list an exported-but-not-yet-imported public
|
|
76
|
+
symbol is indistinguishable from a dead one, and a confident false "dead" invites
|
|
77
|
+
a deletion the graph cannot justify. If you wire `dead` into a script, handle the
|
|
78
|
+
refusal — an exit 1 here is not a crash, and treating it as an empty dead list
|
|
79
|
+
is the false negative the refusal exists to prevent.
|
|
80
|
+
|
|
81
|
+
### One consumer-visible string changed
|
|
82
|
+
|
|
83
|
+
`install_hint_stdio` in the published MCP tool catalogue was
|
|
84
|
+
`npx -y @event4u/agent-config mcp-server` and is now **`agent-config mcp-server`**
|
|
85
|
+
— the installed-binary form the setup docs already lead with. It resolves no
|
|
86
|
+
dist-tag, so the server you run is the version you installed rather than
|
|
87
|
+
whatever the registry served most recently. **It assumes `agent-config` is on
|
|
88
|
+
PATH.** If you read that field programmatically and rely on `npx`, keep using
|
|
89
|
+
the pinned form from [`mcp-server.md`](mcp-server.md) —
|
|
90
|
+
`npx -y @event4u/agent-config@<version> mcp-server` — which is unchanged and
|
|
91
|
+
still the documented client configuration.
|
|
92
|
+
|
|
93
|
+
### Standing context cost
|
|
94
|
+
|
|
95
|
+
Registering the stdio MCP server now costs **4,876 tokens** of standing context
|
|
96
|
+
per session, up from 3,886 — the five graph tools measure 993 between them. It
|
|
97
|
+
remains roughly 4x under the ~20,000-token Tool Search deferral threshold at a
|
|
98
|
+
200k window, so the surface still loads upfront. Measured, with the prior reading
|
|
99
|
+
kept: `agents/evidence/metrics/mcp-tool-standing-cost.jsonl`.
|
|
100
|
+
|
|
45
101
|
## 14.21.x — the code-graph engine ships to consumers
|
|
46
102
|
|
|
47
103
|
💡 advisory · 🔄 automatic — no action required.
|
|
@@ -247,8 +247,33 @@ dispatch runs, not WHETHER the layer exists, so they keep their own C rows.
|
|
|
247
247
|
|---|---|
|
|
248
248
|
| A — preference | 26 |
|
|
249
249
|
| B — consent | 3 |
|
|
250
|
-
| C — guarded |
|
|
251
|
-
| **Total** | **
|
|
250
|
+
| C — guarded | 113 |
|
|
251
|
+
| **Total** | **142** |
|
|
252
|
+
|
|
253
|
+
It rose to 142 on 2026-09-08 from two independent additions that landed in the same
|
|
254
|
+
day and are counted together here: `lean_projection.hosts` (one C, `policy`, from
|
|
255
|
+
`road-to-delivery-for-every-host`) and the two continuity keys below. The merge that
|
|
256
|
+
combined them re-derived the total from the class table rather than adding either
|
|
257
|
+
side's figure to the other, which is the arithmetic `lint_settings_classes` checks.
|
|
258
|
+
|
|
259
|
+
It rose on 2026-09-08 when `road-to-continuity-writer-activation`
|
|
260
|
+
step 1.2 added `continuity.auto_record` — one C, `consent`: it arms an automatic
|
|
261
|
+
producer on the normal session-end path, and the roadmap step requires the
|
|
262
|
+
default to stay `off` until parity is measured, which is a decision no agent may
|
|
263
|
+
take by writing the key. Step 1.4 added
|
|
264
|
+
`continuity.run_checkpoints` the same day — a second C, shipping `on` because
|
|
265
|
+
that is the behavior the tree already had. It exists so the three session-end
|
|
266
|
+
handlers are independently disableable, which is what the 2026-09-07 council's
|
|
267
|
+
D2 asked for; it changes nothing that ships.
|
|
268
|
+
|
|
269
|
+
Its disposition is `consent`, and `derivable` was considered first and rejected:
|
|
270
|
+
`derivable` is the **deletion queue**, a claim that the key disappears once the
|
|
271
|
+
mechanism it names exists. This one is an operator kill switch over a recovery
|
|
272
|
+
artifact, so it is meant to survive — and nothing in the tree can derive whether
|
|
273
|
+
an operator wants resumability traded for quiet. It sits in the same shape as the
|
|
274
|
+
kill switches already classified `consent` (`knowledge.global_sharing.enabled`
|
|
275
|
+
and its allowlist): the mechanism's own preconditions are computable, the
|
|
276
|
+
operator's decision to suppress it is not.
|
|
252
277
|
|
|
253
278
|
The total was 140 until 2026-08-12, when five of the six keys no code path read were
|
|
254
279
|
deleted, minus the one held open (§ The six unread keys, below): one A
|
|
@@ -338,9 +363,9 @@ the template, which is the drift this contract exists to prevent.
|
|
|
338
363
|
|---|---|
|
|
339
364
|
| derivable | 83 |
|
|
340
365
|
| un-inferrable | 9 |
|
|
341
|
-
| consent |
|
|
342
|
-
| policy |
|
|
343
|
-
| **Total** | **
|
|
366
|
+
| consent | 43 |
|
|
367
|
+
| policy | 7 |
|
|
368
|
+
| **Total** | **142** |
|
|
344
369
|
|
|
345
370
|
First measured 2026-08-12 at 140 leaves (derivable 88 · consent 38 ·
|
|
346
371
|
un-inferrable 9 · policy 5), from the table below rather than predicted — the
|
|
@@ -355,8 +380,12 @@ mechanism it names actually exists, so 83 measures work outstanding, not keys
|
|
|
355
380
|
about to disappear. The count is expected to fall while `un-inferrable` does not
|
|
356
381
|
— those 9 plus whatever survives re-examination in `consent` are the floor this
|
|
357
382
|
surface has, and stating it is the point. `policy` is the smallest class and the
|
|
358
|
-
only one whose action is a *move* rather than a keep or a delete:
|
|
359
|
-
a project fact the tree could hold instead.
|
|
383
|
+
only one whose action is a *move* rather than a keep or a delete: seven keys carry
|
|
384
|
+
a project fact the tree could hold instead. (This sentence read "five" while the
|
|
385
|
+
counts table beside it read 6, and 2026-09-08 added `lean_projection.hosts` as the
|
|
386
|
+
seventh — corrected in both directions rather than only incremented, because a
|
|
387
|
+
prose number that disagrees with a computed one beside it is the exact defect
|
|
388
|
+
`lint_settings_classes` fails the two Total rows for.)
|
|
360
389
|
|
|
361
390
|
## The floor — the nine keys no mechanism can derive
|
|
362
391
|
|
|
@@ -440,6 +469,7 @@ Rows follow template order, so a diff against the template reads straight down.
|
|
|
440
469
|
| `discipline_profile` | C | `__DISCIPLINE_PROFILE__` | master switch for the discipline rule tier | derivable — its own `auto` resolution against `src/config/host-capabilities.yml` |
|
|
441
470
|
| `rule_loading_tier` | C | `__RULE_LOADING_TIER__` | legacy master switch for rule loading | derivable — `discipline_profile` supersedes it with a documented mapping (minimal→off, balanced→essential, full→full) |
|
|
442
471
|
| `lean_projection.mode` | C | `eager-all` | `thin` removes rule bodies from the agent's context | derivable — `probe_host_compliance.ts` already computes the per-host thin/eager recommendation |
|
|
472
|
+
| `lean_projection.hosts` | C | `[claude-code]` | decides WHICH hosts a thinning mode may thin, so an added id removes rule bodies from that host's context | policy — the value is a package-level decision the tree already holds: ADR-267 scopes the shipped `delivery` flip to `claude-code` alone. NOT `derivable`, and the distinction is the whole reason for this row: the mechanism that would compute an admissible host set, `admissibleUnderE3` in `src/scripts/_lib/injection_effect.ts`, admits **nothing** today — `src/config/host-injection-effect.json` carries 1 `observed-false` and 8 `unobserved` rows and no `observed-true` — so a derivation would yield `[]` rather than the shipped `[claude-code]`. Naming a mechanism that returns empty as the replacement would put a key in the deletion queue whose deletion would silently change the default. Move target: the ADR plus that census, once a host earns an `observed-true` row |
|
|
443
473
|
| `telegraph.speak` | C | `false` | ships a rule body; a token-cost lever in both directions | derivable — the telegraph kill-criterion bench verdict is a package-level decision, not a per-install one |
|
|
444
474
|
| `tokens.rich_skills` | C | `"on"` | token-spend lever | derivable — the skill's own `token_budget_class: rich` declaration plus the CI ceiling in `lint_token_budget_discipline.ts` |
|
|
445
475
|
| `cost.budgets.daily` | C | `0` | rolling spend ceiling | consent |
|
|
@@ -537,6 +567,8 @@ Rows follow template order, so a diff against the template reads straight down.
|
|
|
537
567
|
| `commands.create_pr.screenshots` | C | `false` | puts captured screenshots into a published PR body | consent |
|
|
538
568
|
| `commands.create_pr.ui_paths` | C | `[]` | glob allowlist | derivable — the frontend-surface heuristic the PR-description flow already applies when the glob list is empty |
|
|
539
569
|
| `commands.create_pr.api_paths` | C | `[]` | glob allowlist | derivable — the API-endpoint heuristic the same flow already applies as its documented empty-list fallback |
|
|
570
|
+
| `continuity.auto_record` | C | `"off"` | arms an automatic producer on the normal session-end path | consent |
|
|
571
|
+
| `continuity.run_checkpoints` | C | `"on"` | disabling it removes a recovery artifact a killed run resumes from | consent |
|
|
540
572
|
| `memory.cadence` | C | `always` | suppressing the visibility line hides what the agent learned from the user | derivable — the hits/asks count the memory-visibility summary already computes; the line only exists when memory was consulted |
|
|
541
573
|
| `memory.review_threshold` | A | `10` | when a review preview surfaces; governs no gate | derivable — the unreviewed-intake count `/memory load` already computes before rendering its preview |
|
|
542
574
|
| `memory.redact_patterns` | C | `[]` | deny-list of secret and PII regexes | policy |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Notes-first reasoning — horizon and reopening mechanics
|
|
2
|
+
|
|
3
|
+
Migrated out of [`notes-first-reasoning`](../../../src/rules/notes-first-reasoning.md)
|
|
4
|
+
on 2026-09-08. That rule is delivered on **every subagent spawn**, so its body is
|
|
5
|
+
paid per spawn; this material is reference read on demand. Nothing was dropped —
|
|
6
|
+
the four passages below are the rule's own text, verbatim, and the rule keeps
|
|
7
|
+
both Iron Laws and points here.
|
|
8
|
+
|
|
9
|
+
Same reason, and the same remedy, as the 2026-08-29 drain run recorded for
|
|
10
|
+
`source-confidentiality`: the addition had no room under the per-spawn ceiling,
|
|
11
|
+
and raising that ceiling is the config-weakening move this repository refuses.
|
|
12
|
+
|
|
13
|
+
### Why reasoning stays out of the reply, and what grounds the notes file
|
|
14
|
+
|
|
15
|
+
Reasoning dumped into the user-facing answer is both noise and a
|
|
16
|
+
`reasoning_extraction` refusal risk (see `rdp-gate`). Keep it in the notes file.
|
|
17
|
+
|
|
18
|
+
The notes file is grounded in the documented cross-run lessons memory
|
|
19
|
+
(consolidated via [`memory-consolidation`](../../../src/skills/memory-consolidation/SKILL.md));
|
|
20
|
+
the rule's in-task sections are a local derivation for within-task scope. The
|
|
21
|
+
gate itself reads settings + task-signal + host self-assessment.
|
|
22
|
+
|
|
23
|
+
### The two section fields, in full
|
|
24
|
+
|
|
25
|
+
The rule lists the sections and no count. That is deliberate: the structure
|
|
26
|
+
carries the enumeration, so there is no "write N hypotheses" instruction to
|
|
27
|
+
follow — a run records what the work actually surfaced, and an empty section is
|
|
28
|
+
a fact about the work rather than a gap in the notes.
|
|
29
|
+
|
|
30
|
+
`## Predictions` carries **chosen form** · prediction · confidence · result ·
|
|
31
|
+
lesson — the calibration loop: hypothesis → prediction → reality →
|
|
32
|
+
calibration. The form is named first because a prediction with no subject
|
|
33
|
+
cannot be contradicted: when the observation comes back, "the prediction
|
|
34
|
+
missed" has to point at *what* it was a prediction about, or nothing reopens.
|
|
35
|
+
|
|
36
|
+
`## Decisions` escalates to
|
|
37
|
+
[`decision-record`](../../../src/skills/decision-record/SKILL.md)/ADR when the
|
|
38
|
+
decision is cross-task or architectural — litmus: would a dev on a different
|
|
39
|
+
component next month need this context? And `next-commitment` is the boundary
|
|
40
|
+
this choice authorises work up to, not the plan for it: choosing a form is not
|
|
41
|
+
permission to execute a twelve-step plan on it without re-reading the position.
|
|
42
|
+
|
|
43
|
+
### The horizon — which boundaries count
|
|
44
|
+
|
|
45
|
+
The boundary is the nearest of these that the plan actually reaches: a **test
|
|
46
|
+
result** · a **type or schema inspection** · a **compile** · a **runtime probe**
|
|
47
|
+
· a **call-site inventory** · a **dry run** · a **browser observation** · a
|
|
48
|
+
**dependency-contract read**. The set is enumerable, which is the whole point —
|
|
49
|
+
"the work up to the nearest of those" is decidable from the plan, and `next
|
|
50
|
+
justified commitment` as prose degrades to *"implement solution"*, which is the
|
|
51
|
+
starting point wearing a label.
|
|
52
|
+
|
|
53
|
+
### Width scales with reversibility, never with task size
|
|
54
|
+
|
|
55
|
+
| The action is… | The horizon runs to… |
|
|
56
|
+
|---|---|
|
|
57
|
+
| a reversible local edit | a whole implementation slice — stopping at the first boundary costs more than it saves |
|
|
58
|
+
| stateful or cross-layer | the next boundary from the list above |
|
|
59
|
+
| irreversible or a public contract | **before** the irreversible step, unless direct evidence already covers it |
|
|
60
|
+
|
|
61
|
+
### The look-ahead ladder is a CEILING, never a quota
|
|
62
|
+
|
|
63
|
+
Three to five moves is the *maximum*, and only for a forcing, irreversible or
|
|
64
|
+
externally observable line. Stateful or cross-layer gets two to three. A quiet
|
|
65
|
+
reversible line gets **one, deliberately** — and no artifact anywhere requires a
|
|
66
|
+
minimum depth. Always-three-to-five across all variants is 1024 leaves at four
|
|
67
|
+
branches and depth five, which buys nothing and costs the run its budget.
|
|
68
|
+
|
|
69
|
+
### Reopening — the two consequences
|
|
70
|
+
|
|
71
|
+
A reopen driven by ritual rather than by contradicting evidence is ceremony, and
|
|
72
|
+
an uncapped reopen is a loop with better manners. Two consequences:
|
|
73
|
+
|
|
74
|
+
- **A rejection whose premise is falsified is not a rejection.** A candidate
|
|
75
|
+
killed by an assumption that later turns out false is *un-evaluated*, not
|
|
76
|
+
dead — it returns to the set. That is what makes a stated `killed-if`
|
|
77
|
+
condition worth carrying: without it, "we ruled that out" is unfalsifiable.
|
|
78
|
+
- **The first failure reads the candidate list before the retry**, where one
|
|
79
|
+
exists — see [`autonomy-mechanics`](../../../src/agent-src/contexts/execution/autonomy-mechanics.md)
|
|
80
|
+
§ Retry-budget escalation ladder. Where none exists the ladder is unchanged.
|
|
81
|
+
This adds a branch; it does not move the N=3 budget.
|
|
82
|
+
|
|
83
|
+
## See also
|
|
84
|
+
|
|
85
|
+
- [`notes-first-reasoning`](../../../src/rules/notes-first-reasoning.md) — the
|
|
86
|
+
rule this material was paid for in, and which keeps both Iron Laws.
|
|
87
|
+
- [`rdp-gate`](../../../src/agent-src/contexts/execution/rdp-gate.md) — the
|
|
88
|
+
engagement gate that decides whether the notes discipline runs at all.
|
|
89
|
+
- [`decision-record`](../../../src/skills/decision-record/SKILL.md) — where a
|
|
90
|
+
cross-task or architectural decision escalates to.
|
package/llms.txt
CHANGED
|
@@ -41,7 +41,7 @@ canvas-design: Creating static visual art — posters, marketing visuals, brand
|
|
|
41
41
|
character-consistency: Use when a character must stay visually identical across AI video scenes — locks identity tokens (silhouette, palette, wardrobe, prop) in JSON. Triggers 'character lock', 'same character'.
|
|
42
42
|
check-refs: Use when verifying cross-references between skills, rules, commands, guidelines, and context documents are not broken after edits, renames, or deletions.
|
|
43
43
|
churn-prevention: Use when designing churn defence — health-score signals, churn-cause split (involuntary / value / relationship / fit), early-warning loop. Triggers on 'why are accounts leaving'.
|
|
44
|
-
code-intelligence: Route codebase-structure questions (who calls X, where
|
|
44
|
+
code-intelligence: Route codebase-structure questions (who calls X, where used, change-impact) to an existing code-graph first: impact, tests-for, dead cheaper, never more precise; grep routine. Also 'call graph'.
|
|
45
45
|
code-refactoring: When the user says 'refactor this', 'rename class', or 'move method'. Safely refactors code in any language — finds all callers, updates downstream dependencies, verifies via quality tools.
|
|
46
46
|
code-review: Use when the user says \"review this\", \"check my code\", or wants feedback on changes. Reviews for correctness, quality, security, and coding standards.
|
|
47
47
|
command-routing: Use when the user invokes a slash command like /create-pr, /commit, /fix-ci, or pastes command file content — routes to the right command with context inference and GitHub API patterns.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.23.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including the counts in these badges.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -58,7 +58,11 @@ import * as path from 'node:path';
|
|
|
58
58
|
import process from 'node:process';
|
|
59
59
|
import { fileURLToPath } from 'node:url';
|
|
60
60
|
|
|
61
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
blocker_is_resolved,
|
|
63
|
+
collect,
|
|
64
|
+
parse_blockers,
|
|
65
|
+
} from './update_roadmap_progress.js';
|
|
62
66
|
import { guardedBaselineProblems, parseGuardedBaselines } from './guarded_baseline.js';
|
|
63
67
|
|
|
64
68
|
import { isCliEntry } from './_cli_entry.js';
|
|
@@ -265,6 +269,80 @@ function _git_mv(root: string, src_rel: string, dst_rel: string, dry_run: boolea
|
|
|
265
269
|
}
|
|
266
270
|
}
|
|
267
271
|
|
|
272
|
+
// ---------------------------------------------------------------------
|
|
273
|
+
// The index/working-tree blocker divergence check (1b) + staging (1a)
|
|
274
|
+
// ---------------------------------------------------------------------
|
|
275
|
+
//
|
|
276
|
+
// The open-blocker refusal below reads the file with `fs.readFileSync`, i.e.
|
|
277
|
+
// the WORKING TREE. `git mv` then moves the INDEX ENTRY, which still carries
|
|
278
|
+
// whatever was staged. When the two disagree the commit ships the index
|
|
279
|
+
// content — content this gate never read.
|
|
280
|
+
//
|
|
281
|
+
// Measured, once, at full cost (2026-09-08): an edit removing a roadmap's
|
|
282
|
+
// blocker was made and never staged; `git mv` moved the stale index entry; the
|
|
283
|
+
// archived file shipped a blocker declared `Status: open` that had already been
|
|
284
|
+
// relocated to an active roadmap. The same blocker was open in two files at
|
|
285
|
+
// once, one of them archived, and it was caught by a human reading
|
|
286
|
+
// `git status` rather than by any gate.
|
|
287
|
+
//
|
|
288
|
+
// The check is scoped to blocker id → status and deliberately NOT to whole-file
|
|
289
|
+
// equality. An unstaged typo fix in a roadmap title has nothing to do with the
|
|
290
|
+
// defect, and refusing archival for it trains `git add -A`, which destroys the
|
|
291
|
+
// staging discipline this check depends on. AI council 2026-09-08, 2/2 present
|
|
292
|
+
// (anthropic/claude-sonnet-4-5 + openai/gpt-4o), on that scoping specifically:
|
|
293
|
+
// broad scoping is "user-hostile, trains developers to `git add -A`
|
|
294
|
+
// reflexively, defeating staging discipline".
|
|
295
|
+
|
|
296
|
+
/** Blocker id → status, as the archive-transition check compares them. */
|
|
297
|
+
function _blockerStatuses(text: string): Map<string, string> {
|
|
298
|
+
const out = new Map<string, string>();
|
|
299
|
+
for (const b of parse_blockers(text)) {
|
|
300
|
+
out.set(b.id, blocker_is_resolved(b) ? 'resolved' : 'open');
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* `git show :<rel>` — the STAGED content of a path, or null when the path has
|
|
307
|
+
* no index entry.
|
|
308
|
+
*
|
|
309
|
+
* Null is the untracked / pre-first-commit case, which `_git_mv` already
|
|
310
|
+
* handles by falling back to a plain rename. There is no index entry to
|
|
311
|
+
* disagree with, so the divergence check has nothing to assert and says so by
|
|
312
|
+
* returning null rather than by guessing at an empty string — an empty staged
|
|
313
|
+
* file and an absent one differ, and only the second is legitimate here.
|
|
314
|
+
*/
|
|
315
|
+
function _indexContent(root: string, rel: string): string | null {
|
|
316
|
+
const cp = _run(['git', 'show', `:${rel}`], root);
|
|
317
|
+
return cp.returncode === 0 ? cp.stdout : null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Human-readable disagreements between the staged and working-tree blocker
|
|
322
|
+
* maps of one path. Empty array = the two agree, or there is no index entry.
|
|
323
|
+
*/
|
|
324
|
+
function _blockerDivergence(root: string, rel: string, workTreeText: string): string[] {
|
|
325
|
+
const staged = _indexContent(root, rel);
|
|
326
|
+
if (staged === null) {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
const wt = _blockerStatuses(workTreeText);
|
|
330
|
+
const idx = _blockerStatuses(staged);
|
|
331
|
+
const ids = [...new Set([...wt.keys(), ...idx.keys()])].sort();
|
|
332
|
+
const out: string[] = [];
|
|
333
|
+
for (const id of ids) {
|
|
334
|
+
const a = idx.get(id);
|
|
335
|
+
const b = wt.get(id);
|
|
336
|
+
if (a === b) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
out.push(
|
|
340
|
+
`blocker '${id}': staged=${a ?? 'absent'} vs working tree=${b ?? 'absent'}`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
|
|
268
346
|
/**
|
|
269
347
|
* Repo-relative paths of files containing `needle`, by walking the filesystem.
|
|
270
348
|
*
|
|
@@ -547,9 +625,8 @@ function archive_completed(
|
|
|
547
625
|
// and invisible in a sweep that only reports what it moved; a MALFORMED
|
|
548
626
|
// one on `[x]` would otherwise archive with an annotation claiming the
|
|
549
627
|
// opposite. Both are named here instead.
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
);
|
|
628
|
+
const roadmapText = fs.readFileSync(path.join(roadmap_root, stats.rel), 'utf-8');
|
|
629
|
+
const guarded = parseGuardedBaselines(roadmapText);
|
|
553
630
|
if (guarded.length > 0) {
|
|
554
631
|
process.stderr.write(
|
|
555
632
|
` ⚠️ ${stats.rel}: ${guarded.length} guarded-baseline step(s) — ` +
|
|
@@ -603,12 +680,37 @@ function archive_completed(
|
|
|
603
680
|
if (changed_only && !(touched as Set<string>).has(old_rel)) {
|
|
604
681
|
continue; // complete, but not this branch's work
|
|
605
682
|
}
|
|
683
|
+
// The gate above validated the WORKING TREE; `git mv` ships the INDEX.
|
|
684
|
+
// When they disagree about a blocker, the validated content is not the
|
|
685
|
+
// content that gets committed — see § the index/working-tree blocker
|
|
686
|
+
// divergence check above for the measured failure.
|
|
687
|
+
const divergence = _blockerDivergence(root, old_rel, roadmapText);
|
|
688
|
+
if (divergence.length > 0) {
|
|
689
|
+
process.stderr.write(
|
|
690
|
+
` ⚠️ ${stats.rel}: staged and working-tree blockers disagree ` +
|
|
691
|
+
`(${divergence.length}) — not archived. \`git mv\` would commit ` +
|
|
692
|
+
'the staged content, which this sweep never validated. Stage or ' +
|
|
693
|
+
'discard the change, then re-run.\n',
|
|
694
|
+
);
|
|
695
|
+
for (const d of divergence) {
|
|
696
|
+
process.stderr.write(` · ${d}\n`);
|
|
697
|
+
}
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
606
700
|
const new_rel = `agents/roadmaps/archive/${stats.rel}`;
|
|
607
701
|
const mv = _git_mv(root, old_rel, new_rel, dry_run);
|
|
608
702
|
if (!mv.ok) {
|
|
609
703
|
process.stderr.write(` ⚠️ could not archive ${old_rel} (mv failed)\n`);
|
|
610
704
|
continue;
|
|
611
705
|
}
|
|
706
|
+
// `git mv` moved the index ENTRY, not the working-tree content. Staging
|
|
707
|
+
// the destination makes what gets committed identical to what the checks
|
|
708
|
+
// above read — the divergence check refuses a disagreement that exists
|
|
709
|
+
// BEFORE the move, and this closes one created by an edit between the
|
|
710
|
+
// move and the commit.
|
|
711
|
+
if (!dry_run && mv.tracked) {
|
|
712
|
+
_run(['git', 'add', '--', new_rel], root);
|
|
713
|
+
}
|
|
612
714
|
const refs = _inbound_ref_rewrite(root, old_rel, new_rel, dry_run, mv.tracked);
|
|
613
715
|
// Stage rewritten refs only in a tracked repo; an untracked consumer's
|
|
614
716
|
// files are not in the index, so the filesystem rewrite is the whole job.
|