@event4u/agent-config 14.4.0 → 14.6.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 +132 -291
- package/dist/agent-src/commands/analyze/decision.md +7 -0
- package/dist/agent-src/commands/optimize/project.md +10 -0
- package/dist/agent-src/commands/roadmap/process-full.md +45 -3
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +130 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +108 -7
- package/dist/agent-src/skills/adr-create/SKILL.md +25 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +18 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-ZPSXJTLI.js → chunk-P6LQDIWK.js} +15 -1
- package/dist/cli-delegate/cmd_conformance.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +1 -1
- 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 +419 -94
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/router.json +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +49 -0
- package/package.json +1 -1
- package/src/config/estate-count-budget.json +36 -1
- package/src/config/pack-size-budget.json +3 -2
- package/src/scripts/_lib/layer_overlap_notice.ts +85 -0
- package/src/scripts/_lib/scope_guard.sh +62 -3
- package/src/scripts/_lib/subagent_capsule.ts +81 -1
- package/src/scripts/adr/regenerate_index.ts +13 -2
- package/src/scripts/adr_cite_check.ts +424 -0
- package/src/scripts/bench_hook_injection.ts +18 -1
- package/src/scripts/check_adr_frontmatter.ts +187 -1
- package/src/scripts/check_single_delivery.ts +473 -0
- package/src/scripts/hooks/host_semantics.ts +28 -0
- package/src/scripts/hooks/injection_budget.ts +152 -16
- package/src/scripts/hooks/run_continuation_hook.ts +918 -46
- package/src/scripts/install.sh +12 -2
- package/src/scripts/memory_consolidation.ts +189 -0
- package/src/scripts/memory_eviction.ts +384 -0
- package/src/scripts/report_layer_overlap.ts +72 -0
- package/src/scripts/report_memory_pointers.ts +622 -0
- package/src/scripts/session_register_hook.ts +144 -21
|
@@ -17,10 +17,27 @@
|
|
|
17
17
|
* the fix is a registered decision, not a silent default bump
|
|
18
18
|
* - per-slot SUM over all bound concerns > the slot cap → RED
|
|
19
19
|
*
|
|
20
|
+
* NOT FULLY DETERMINISTIC — this paragraph replaces a claim that said it was.
|
|
21
|
+
* The fixture path was described as "deterministic, CI-reproducible". For most
|
|
22
|
+
* concerns it is; they read only the committed envelope. But `end-review-nudge`
|
|
23
|
+
* decides whether to fire from the LIVE working-tree diff plus a once-per-session
|
|
24
|
+
* latch, so one commit reads 1,140 B with uncommitted changes present and 922 B
|
|
25
|
+
* without them. Two values, observed three times in one session as the tree went
|
|
26
|
+
* dirty, clean and dirty again — the earlier wording here said "three different
|
|
27
|
+
* values", which counted the readings and not the values, and an R2 review was
|
|
28
|
+
* right to call that an unbacked number inside the paragraph that exists to
|
|
29
|
+
* replace an unbacked claim.
|
|
30
|
+
* Two consequences. A single run is a reading of THIS checkout right now, so its
|
|
31
|
+
* number must never be pinned as acceptance evidence (a roadmap note tried, and
|
|
32
|
+
* had to be corrected). And the BREACH verdict is unaffected regardless, because
|
|
33
|
+
* a state-dependent concern can only push the sum UP: a green run is still a
|
|
34
|
+
* green run. Documented rather than fixed, because making this hermetic means
|
|
35
|
+
* changing what the concern reads — a change to the concern, not to its bench.
|
|
36
|
+
*
|
|
20
37
|
* HONEST SCOPE, stated up front: concerns are conditional-silence by design —
|
|
21
38
|
* a generic fixture triggers few of them, so most measure 0 bytes here. The
|
|
22
39
|
* gate therefore enforces the cap on whatever DOES fire under the committed
|
|
23
|
-
* fixtures
|
|
40
|
+
* fixtures, while the census mode
|
|
24
41
|
* (`--record`) measures REAL sessions: it appends counts-only lines
|
|
25
42
|
* ({ts, slot, concern, bytes}) to agents/runtime/state/injection-census.jsonl
|
|
26
43
|
* — the repeat-injection-census blocker's instrument. Claiming the fixture
|
|
@@ -51,6 +51,92 @@ export const REVIEW_TRIGGER_SINCE = '2026-07-25';
|
|
|
51
51
|
/** Fields every ADR must carry — the shape that already exists in practice. */
|
|
52
52
|
const REQUIRED = ['adr', 'status', 'date', 'decision'] as const;
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Reopen-authority vocabulary (`adr-layout.md` § Reopen authority).
|
|
56
|
+
*
|
|
57
|
+
* Both fields are OPTIONAL and stay optional. An absent `reopen_policy`
|
|
58
|
+
* resolves to `unclassified` at the reader, never to `owner`: with 146 accepted
|
|
59
|
+
* ADRs a fail-closed default would encode the existing blockage into the new
|
|
60
|
+
* schema, which is the one thing both council seats (2026-08-19) rejected in
|
|
61
|
+
* the same words. This validator therefore checks the VALUE when present and
|
|
62
|
+
* never requires the KEY.
|
|
63
|
+
*/
|
|
64
|
+
const ALLOWED_REOPEN_POLICY = new Set(['directional', 'owner', 'unclassified']);
|
|
65
|
+
|
|
66
|
+
const ALLOWED_PROTECTED_DIMENSIONS = new Set([
|
|
67
|
+
'purpose',
|
|
68
|
+
'security_floor',
|
|
69
|
+
'privacy_floor',
|
|
70
|
+
'external_commitment',
|
|
71
|
+
'governance',
|
|
72
|
+
'none',
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
/** Split an inline `[a, b]` list or a folded multi-value string into members. */
|
|
76
|
+
export function split_dimensions(raw: string): string[] {
|
|
77
|
+
return raw
|
|
78
|
+
.replace(/^\[/, '')
|
|
79
|
+
.replace(/\]$/, '')
|
|
80
|
+
.split(/[,\s]+/)
|
|
81
|
+
.map((s) =>
|
|
82
|
+
s
|
|
83
|
+
.trim()
|
|
84
|
+
.replace(/^-\s*/, '')
|
|
85
|
+
.replace(/^["'](.*)["']$/, '$1'),
|
|
86
|
+
)
|
|
87
|
+
.filter((s) => s !== '');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate the two optional reopen-authority fields — value only, never
|
|
92
|
+
* presence. A wrong enum is a real error (it silently mis-routes authority);
|
|
93
|
+
* an absent field is the documented default and not a finding.
|
|
94
|
+
*/
|
|
95
|
+
export function check_reopen_authority(
|
|
96
|
+
rel: string,
|
|
97
|
+
fm: Record<string, string>,
|
|
98
|
+
findings: AdrFinding[],
|
|
99
|
+
): void {
|
|
100
|
+
const policy = fm['reopen_policy'];
|
|
101
|
+
if (policy !== undefined && policy !== '' && !ALLOWED_REOPEN_POLICY.has(policy)) {
|
|
102
|
+
findings.push({
|
|
103
|
+
file: rel,
|
|
104
|
+
level: 'error',
|
|
105
|
+
message:
|
|
106
|
+
`\`reopen_policy\` \`${policy}\` is not one of: ` +
|
|
107
|
+
`${[...ALLOWED_REOPEN_POLICY].sort().join(', ')} (absent = unclassified)`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const dims = fm['protected_dimensions'];
|
|
112
|
+
if (dims !== undefined && dims !== '') {
|
|
113
|
+
for (const d of split_dimensions(dims)) {
|
|
114
|
+
if (!ALLOWED_PROTECTED_DIMENSIONS.has(d)) {
|
|
115
|
+
findings.push({
|
|
116
|
+
file: rel,
|
|
117
|
+
level: 'error',
|
|
118
|
+
message:
|
|
119
|
+
`\`protected_dimensions\` member \`${d}\` is not one of: ` +
|
|
120
|
+
`${[...ALLOWED_PROTECTED_DIMENSIONS].sort().join(', ')}`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// `owner` without a named dimension is the frozen-over-classification shape
|
|
127
|
+
// the contract warns about: it reserves every future transition, including
|
|
128
|
+
// the ones that only strengthen the decision, and records no reason.
|
|
129
|
+
if (policy === 'owner' && (dims === undefined || dims === '')) {
|
|
130
|
+
findings.push({
|
|
131
|
+
file: rel,
|
|
132
|
+
level: 'warn',
|
|
133
|
+
message:
|
|
134
|
+
'`reopen_policy: owner` without `protected_dimensions` — name the reserved ' +
|
|
135
|
+
'interest, or the reservation cannot be checked against a proposed transition',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
54
140
|
const ALLOWED_STATUS = new Set([
|
|
55
141
|
'accepted',
|
|
56
142
|
'proposed',
|
|
@@ -141,6 +227,98 @@ export function check_one(rel: string, text: string): AdrFinding[] {
|
|
|
141
227
|
message: `\`review_trigger\` is a cadence, not a condition: "${trigger}". A calendar review is ignored; an event fires.`,
|
|
142
228
|
});
|
|
143
229
|
}
|
|
230
|
+
check_reopen_authority(rel, fm, findings);
|
|
231
|
+
check_amendment_shape(rel, fm, findings);
|
|
232
|
+
return findings;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** `ADR-035`, `035`, `35` → `35`. Returns null when nothing resolves. */
|
|
236
|
+
export function adr_number(raw: string): string | null {
|
|
237
|
+
const m = /(\d{1,4})/.exec(raw.trim());
|
|
238
|
+
return m?.[1] === undefined ? null : String(Number(m[1]));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Validate `amends:` / `amended_by:` shape. The reciprocal half is checked in
|
|
243
|
+
* `check_amendment_links` (it needs the whole corpus); this one only rejects a
|
|
244
|
+
* value that is neither the em-dash placeholder nor an ADR reference, so a
|
|
245
|
+
* typo'd link is caught before it becomes an invisible one-sided amendment.
|
|
246
|
+
*/
|
|
247
|
+
export function check_amendment_shape(
|
|
248
|
+
rel: string,
|
|
249
|
+
fm: Record<string, string>,
|
|
250
|
+
findings: AdrFinding[],
|
|
251
|
+
): void {
|
|
252
|
+
for (const key of ['amends', 'amended_by'] as const) {
|
|
253
|
+
const v = fm[key];
|
|
254
|
+
if (v === undefined || v === '' || v === '—') continue;
|
|
255
|
+
for (const part of v.split(',')) {
|
|
256
|
+
if (adr_number(part) === null) {
|
|
257
|
+
findings.push({
|
|
258
|
+
file: rel,
|
|
259
|
+
level: 'error',
|
|
260
|
+
message: `\`${key}\` value \`${part.trim()}\` names no ADR number`,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Corpus-level check: every `amends:` has its reciprocal `amended_by:` and vice
|
|
269
|
+
* versa. This is the half the `supersedes:` / `superseded_by:` pair never had —
|
|
270
|
+
* and its absence is exactly how ADR-035 kept asserting a rejection ADR-232 had
|
|
271
|
+
* reopened. A one-sided link is invisible from the side that is stale, which is
|
|
272
|
+
* the side a reader lands on first.
|
|
273
|
+
*/
|
|
274
|
+
export function check_amendment_links(
|
|
275
|
+
files: { rel: string; fm: Record<string, string> }[],
|
|
276
|
+
): AdrFinding[] {
|
|
277
|
+
const findings: AdrFinding[] = [];
|
|
278
|
+
const byNumber = new Map<string, { rel: string; fm: Record<string, string> }>();
|
|
279
|
+
for (const f of files) {
|
|
280
|
+
const n = f.fm['adr'] === undefined ? null : adr_number(f.fm['adr']);
|
|
281
|
+
if (n !== null) byNumber.set(n, f);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const reciprocal = { amends: 'amended_by', amended_by: 'amends' } as const;
|
|
285
|
+
for (const f of files) {
|
|
286
|
+
for (const key of ['amends', 'amended_by'] as const) {
|
|
287
|
+
const v = f.fm[key];
|
|
288
|
+
if (v === undefined || v === '' || v === '—') continue;
|
|
289
|
+
const self = f.fm['adr'] === undefined ? null : adr_number(f.fm['adr']);
|
|
290
|
+
for (const part of v.split(',')) {
|
|
291
|
+
const target = adr_number(part);
|
|
292
|
+
if (target === null) continue;
|
|
293
|
+
const other = byNumber.get(target);
|
|
294
|
+
if (other === undefined) {
|
|
295
|
+
findings.push({
|
|
296
|
+
file: f.rel,
|
|
297
|
+
level: 'error',
|
|
298
|
+
message: `\`${key}: ${part.trim()}\` points at an ADR that does not exist`,
|
|
299
|
+
});
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const back = other.fm[reciprocal[key]] ?? '';
|
|
303
|
+
const names_self =
|
|
304
|
+
self !== null &&
|
|
305
|
+
back
|
|
306
|
+
.split(',')
|
|
307
|
+
.map((s) => adr_number(s))
|
|
308
|
+
.includes(self);
|
|
309
|
+
if (!names_self) {
|
|
310
|
+
findings.push({
|
|
311
|
+
file: f.rel,
|
|
312
|
+
level: 'error',
|
|
313
|
+
message:
|
|
314
|
+
`\`${key}: ${part.trim()}\` is one-sided — ${other.rel} carries no ` +
|
|
315
|
+
`reciprocal \`${reciprocal[key]}\`. A one-sided amendment link is ` +
|
|
316
|
+
`invisible from the stale side.`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
144
322
|
return findings;
|
|
145
323
|
}
|
|
146
324
|
|
|
@@ -163,13 +341,20 @@ export function check(dir: string = ADR_DIR, ledger?: GateLedger): AdrFinding[]
|
|
|
163
341
|
const candidates = fs.readdirSync(dir).filter((n) => n.endsWith('.md')).sort();
|
|
164
342
|
ledger?.plan(candidates);
|
|
165
343
|
|
|
344
|
+
// Collected for the corpus-level reciprocal-amendment check below, which
|
|
345
|
+
// cannot be decided from one file.
|
|
346
|
+
const corpus: { rel: string; fm: Record<string, string> }[] = [];
|
|
347
|
+
|
|
166
348
|
for (const name of candidates) {
|
|
167
349
|
if (!/^ADR-.*\.md$/.test(name)) {
|
|
168
350
|
ledger?.outOfScope(name, 'not_applicable_kind');
|
|
169
351
|
continue;
|
|
170
352
|
}
|
|
171
353
|
const rel = path.relative(REPO_ROOT, path.join(dir, name));
|
|
172
|
-
const
|
|
354
|
+
const text = fs.readFileSync(path.join(dir, name), 'utf-8');
|
|
355
|
+
const parsed = parse_frontmatter(text);
|
|
356
|
+
if (parsed !== null) corpus.push({ rel, fm: parsed });
|
|
357
|
+
const own = check_one(rel, text);
|
|
173
358
|
findings.push(...own);
|
|
174
359
|
|
|
175
360
|
const errors = own.filter((f) => f.level === 'error').length;
|
|
@@ -179,6 +364,7 @@ export function check(dir: string = ADR_DIR, ledger?: GateLedger): AdrFinding[]
|
|
|
179
364
|
ledger?.complete(name);
|
|
180
365
|
}
|
|
181
366
|
}
|
|
367
|
+
findings.push(...check_amendment_links(corpus));
|
|
182
368
|
return findings;
|
|
183
369
|
}
|
|
184
370
|
|