@event4u/agent-config 9.22.0 → 9.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.
Files changed (85) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +69 -256
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/optimize/deep.md +13 -0
  5. package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +12 -2
  6. package/dist/agent-src/contexts/execution/mandated-lines.md +180 -0
  7. package/dist/agent-src/rules/context-hygiene.md +17 -0
  8. package/dist/agent-src/rules/downstream-changes.md +26 -0
  9. package/dist/agent-src/rules/think-before-action.md +25 -0
  10. package/dist/agent-src/rules/token-budget-discipline.md +46 -3
  11. package/dist/agent-src/rules/token-efficiency.md +13 -0
  12. package/dist/agent-src/rules/ui-audit-gate.md +33 -2
  13. package/dist/agent-src/skills/adversarial-review/SKILL.md +18 -4
  14. package/dist/agent-src/skills/decision-review/SKILL.md +49 -0
  15. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +5 -1
  16. package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +16 -0
  17. package/dist/agent-src/skills/judge-synthesis/SKILL.md +14 -1
  18. package/dist/agent-src/skills/rule-writing/SKILL.md +33 -0
  19. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +31 -0
  20. package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
  21. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  22. package/dist/cli/registry.js +1 -0
  23. package/dist/cli/registry.js.map +1 -1
  24. package/dist/cli-delegate/chunk-DVRS2KVR.js +4602 -0
  25. package/dist/cli-delegate/{chunk-ELMFOQPW.js → chunk-SJQMJI7M.js} +8 -4597
  26. package/dist/cli-delegate/cmd_export.js +2 -1
  27. package/dist/cli-delegate/cmd_prune.js +2 -1
  28. package/dist/cli-delegate/cmd_settings_migrate.js +2 -1
  29. package/dist/cli-delegate/cmd_settings_set.js +3370 -0
  30. package/dist/cli-delegate/cmd_sync.js +2 -1
  31. package/dist/cli-delegate/cmd_uninstall.js +2 -1
  32. package/dist/cli-delegate/cmd_validate.js +2 -1
  33. package/dist/discovery/deprecation-report.md +1 -1
  34. package/dist/discovery/discovery-manifest.json +17 -17
  35. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  36. package/dist/discovery/discovery-manifest.summary.md +1 -1
  37. package/dist/discovery/orphan-report.md +1 -1
  38. package/dist/discovery/packs.json +2 -2
  39. package/dist/discovery/trust-report.md +1 -1
  40. package/dist/discovery/workspaces.json +2 -2
  41. package/dist/hooks/dispatch.js +79 -1
  42. package/dist/mcp/registry-manifest.json +2 -2
  43. package/dist/mcp/server.json +2 -2
  44. package/dist/scripts/condense.js +47 -2
  45. package/dist/scripts/condense.js.map +1 -1
  46. package/dist/server/routes/settings.js +82 -0
  47. package/dist/server/routes/settings.js.map +1 -1
  48. package/dist/shared/settingsClasses.js +191 -0
  49. package/dist/shared/settingsClasses.js.map +1 -0
  50. package/docs/contracts/settings-classes.md +342 -0
  51. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +23 -2
  52. package/docs/guidelines/agent-infra/false-green.md +187 -0
  53. package/docs/guidelines/agent-infra/gate-authoring.md +177 -0
  54. package/package.json +3 -1
  55. package/src/config/ci-local-parity.yml +12 -0
  56. package/src/config/evaluator-budgets.json +4 -4
  57. package/src/config/gate-coverage.yml +54 -12
  58. package/src/config/gate-violation-baselines.json +6 -0
  59. package/src/config/pack-size-budget.json +4 -3
  60. package/src/scripts/_cli/cmd_settings_set.ts +509 -0
  61. package/src/scripts/_dispatch.bash +16 -0
  62. package/src/scripts/_lib/gate_ledger.ts +295 -0
  63. package/src/scripts/_lib/gate_result.ts +101 -0
  64. package/src/scripts/_lib/gate_self_test.ts +129 -0
  65. package/src/scripts/_lib/measured_render.ts +97 -0
  66. package/src/scripts/_lib/ratchet_base_ref.ts +373 -0
  67. package/src/scripts/check_gate_completeness.ts +244 -0
  68. package/src/scripts/check_gate_coverage.ts +35 -6
  69. package/src/scripts/check_references.ts +125 -15
  70. package/src/scripts/check_review_dispositions.ts +14 -1
  71. package/src/scripts/check_suppression_hygiene.ts +503 -0
  72. package/src/scripts/condense.ts +47 -2
  73. package/src/scripts/hook_manifest.yaml +23 -0
  74. package/src/scripts/hooks/dispatch_hook.ts +65 -1
  75. package/src/scripts/hooks/host_semantics.ts +140 -0
  76. package/src/scripts/lint_framework_leakage.ts +147 -15
  77. package/src/scripts/lint_mandated_lines.ts +339 -0
  78. package/src/scripts/lint_roadmap_complexity.ts +102 -11
  79. package/src/scripts/lint_roadmap_family_cap.ts +191 -0
  80. package/src/scripts/lint_settings_classes.ts +397 -0
  81. package/src/scripts/lint_token_budget_discipline.ts +109 -1
  82. package/src/scripts/report_imperative_density.ts +229 -0
  83. package/src/scripts/verify_physical_move.ts +10 -1
  84. package/src/server/io/yamlIO.ts +173 -0
  85. package/src/shared/settingsClasses.ts +235 -0
@@ -0,0 +1,235 @@
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
+
18
+ /** The closed class vocabulary. A row outside it is a defect, not a new class. */
19
+ export const SETTINGS_CLASSES = ['A', 'B', 'C'] as const;
20
+
21
+ export type SettingsClass = (typeof SETTINGS_CLASSES)[number];
22
+
23
+ export function isSettingsClass(value: string): value is SettingsClass {
24
+ return (SETTINGS_CLASSES as readonly string[]).includes(value);
25
+ }
26
+
27
+ /** One parsed row of the contract's key table. */
28
+ export interface SettingsClassRow {
29
+ /** Dotted leaf path, e.g. `personal.autonomy`. */
30
+ key: string;
31
+ /** Raw class cell — NOT yet validated, so the lint can report a bad one. */
32
+ cls: string;
33
+ /** 1-indexed line in the contract, so a finding points at something. */
34
+ line: number;
35
+ }
36
+
37
+ /** What the contract's own `## Counts` table claims about itself. */
38
+ export interface DeclaredClassCounts {
39
+ A: number | null;
40
+ B: number | null;
41
+ C: number | null;
42
+ total: number | null;
43
+ }
44
+
45
+ /**
46
+ * `| \`some.key\` | C | \`false\` | why |` → one row.
47
+ *
48
+ * The class column is what separates the key table from the prose tables above
49
+ * it, several of which also open with a backticked key. A second cell that is
50
+ * not a single bare letter is not a key row.
51
+ */
52
+ const ROW_RE = /^\|\s*`([^`]+)`\s*\|\s*([^|]*?)\s*\|/;
53
+
54
+ export function parseSettingsClassRows(markdown: string): SettingsClassRow[] {
55
+ const rows: SettingsClassRow[] = [];
56
+ const lines = markdown.split('\n');
57
+ for (let i = 0; i < lines.length; i++) {
58
+ const m = ROW_RE.exec(lines[i] ?? '');
59
+ if (m === null) {
60
+ continue;
61
+ }
62
+ const key = m[1] ?? '';
63
+ const cls = m[2] ?? '';
64
+ if (!/^[A-Za-z]$/.test(cls)) {
65
+ continue;
66
+ }
67
+ rows.push({ key, cls, line: i + 1 });
68
+ }
69
+ return rows;
70
+ }
71
+
72
+ /** `| A — preference | 27 |` and `| **Total** | **140** |` → the declared tallies. */
73
+ export function parseDeclaredClassCounts(markdown: string): DeclaredClassCounts {
74
+ const read = (re: RegExp): number | null => {
75
+ const m = re.exec(markdown);
76
+ const raw = m?.[1];
77
+ return raw === undefined ? null : Number.parseInt(raw, 10);
78
+ };
79
+ return {
80
+ A: read(/^\|\s*A\s+—\s+preference\s*\|\s*(\d+)\s*\|/m),
81
+ B: read(/^\|\s*B\s+—\s+consent\s*\|\s*(\d+)\s*\|/m),
82
+ C: read(/^\|\s*C\s+—\s+guarded\s*\|\s*(\d+)\s*\|/m),
83
+ total: read(/^\|\s*\*\*Total\*\*\s*\|\s*\*\*(\d+)\*\*\s*\|/m),
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Dotted key → class, for the rows whose class is valid.
89
+ *
90
+ * A row with an unparseable class is DROPPED rather than defaulted, so a
91
+ * caller asking "may I write this?" gets `undefined` — which every caller
92
+ * treats as refuse. A typo in the contract must never widen the fence.
93
+ */
94
+ export function buildSettingsClassIndex(rows: readonly SettingsClassRow[]): Map<string, SettingsClass> {
95
+ const index = new Map<string, SettingsClass>();
96
+ for (const row of rows) {
97
+ if (!isSettingsClass(row.cls) || index.has(row.key)) {
98
+ continue;
99
+ }
100
+ index.set(row.key, row.cls);
101
+ }
102
+ return index;
103
+ }
104
+
105
+ /**
106
+ * Conservative-default test for class B (half one of the B invariant).
107
+ *
108
+ * A sparse settings file means absent = default. A B key whose default is the
109
+ * permissive value makes "never asked" indistinguishable from "answered yes",
110
+ * which turns the ask into decoration on a decision already taken in the
111
+ * user's name. These are the values that cannot carry a permission.
112
+ *
113
+ * Half two of the invariant — that the ask fires on the user's own request and
114
+ * never at a moment the agent chose — is prose in the contract, not code. No
115
+ * gate can read the agent's reason for picking a moment.
116
+ */
117
+ export function isConservativeDefault(value: unknown): boolean {
118
+ if (value === null || value === false || value === '' || value === 0) {
119
+ return true;
120
+ }
121
+ if (Array.isArray(value)) {
122
+ return value.length === 0;
123
+ }
124
+ if (typeof value === 'object' && value !== null) {
125
+ return Object.keys(value).length === 0;
126
+ }
127
+ return false;
128
+ }
129
+
130
+ /**
131
+ * Dotted leaf paths of a parsed settings tree.
132
+ *
133
+ * A leaf is anything that is not a NON-EMPTY map. An empty map is a real
134
+ * configurable value with a real default (`subagents.host_capabilities: {}`),
135
+ * so it is a leaf — one more than the template↔schema parity walk produces.
136
+ * That divergence is deliberate: a key the parity walk skips is exactly the
137
+ * key that would otherwise reach the writer with no class.
138
+ */
139
+ export function settingsLeafPaths(value: unknown, prefix = ''): string[] {
140
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
141
+ return [prefix];
142
+ }
143
+ const entries = Object.entries(value as Record<string, unknown>);
144
+ if (entries.length === 0) {
145
+ return [prefix];
146
+ }
147
+ return entries.flatMap(([k, v]) => settingsLeafPaths(v, prefix === '' ? k : `${prefix}.${k}`));
148
+ }
149
+
150
+ /** Read one dotted path out of a parsed settings tree; `undefined` when absent. */
151
+ export function getSettingsLeaf(root: unknown, dotted: string): unknown {
152
+ let node: unknown = root;
153
+ for (const part of dotted.split('.')) {
154
+ if (typeof node !== 'object' || node === null || Array.isArray(node)) {
155
+ return undefined;
156
+ }
157
+ node = (node as Record<string, unknown>)[part];
158
+ }
159
+ return node;
160
+ }
161
+
162
+ /**
163
+ * The class governing a dotted path, walking to the nearest classified ancestor.
164
+ *
165
+ * An exact row wins. Failing that, the longest classified prefix does: a
166
+ * class-C key whose value is a MAP has children that never appear in a flattened
167
+ * diff under the key's own name, so `subagents.host_capabilities.subagent_spawn`
168
+ * must inherit the C on `subagents.host_capabilities` rather than read as
169
+ * unclassified. Returns `undefined` when nothing on the path is classified —
170
+ * and every caller treats that as guarded, not as free.
171
+ */
172
+ export function classOfPath(
173
+ index: ReadonlyMap<string, SettingsClass>,
174
+ dotted: string,
175
+ ): SettingsClass | undefined {
176
+ const exact = index.get(dotted);
177
+ if (exact !== undefined) {
178
+ return exact;
179
+ }
180
+ const parts = dotted.split('.');
181
+ for (let end = parts.length - 1; end > 0; end--) {
182
+ const ancestor = index.get(parts.slice(0, end).join('.'));
183
+ if (ancestor !== undefined) {
184
+ return ancestor;
185
+ }
186
+ }
187
+ return undefined;
188
+ }
189
+
190
+ /** Absent / null / empty-scalar — the three spellings of "not set" in a settings file. */
191
+ export function isUnsetSettingValue(value: unknown): boolean {
192
+ return value === undefined || value === null || value === '';
193
+ }
194
+
195
+ /** One entry of a settings diff, as `diffValues` produces them. */
196
+ export interface SettingsChange {
197
+ path: string;
198
+ from: unknown;
199
+ to: unknown;
200
+ }
201
+
202
+ /**
203
+ * The changed paths a human must confirm before the write lands.
204
+ *
205
+ * Extracted from the Fastify write route so the fail-closed branch is testable
206
+ * without booting a server around it. That branch is the one a single inverted
207
+ * operator turns into a fail-OPEN, and a gate whose most dangerous line has no
208
+ * test is a gate that passes review once and rots after.
209
+ *
210
+ * `classes === null` means the contract could not be read. Every changed key is
211
+ * then guarded: unverifiable is not the same as unguarded. With a contract in
212
+ * hand, a key is guarded when its nearest classified ancestor is C, and ALSO
213
+ * when nothing on its path is classified at all — the same per-key refusal the
214
+ * CLI writer applies.
215
+ *
216
+ * Pairs where both sides are unset are dropped first: a key written as `null`
217
+ * reads back as `''` through the comment-preserving merge, and a confirmation
218
+ * that appears on every save is one nobody reads.
219
+ */
220
+ export function guardedChangedKeys(
221
+ classes: ReadonlyMap<string, SettingsClass> | null,
222
+ changed: readonly SettingsChange[],
223
+ ): string[] {
224
+ return changed
225
+ .filter((c) => !(isUnsetSettingValue(c.from) && isUnsetSettingValue(c.to)))
226
+ .map((c) => c.path)
227
+ .filter((key) => {
228
+ if (classes === null) {
229
+ return true;
230
+ }
231
+ const cls = classOfPath(classes, key);
232
+ return cls === 'C' || cls === undefined;
233
+ })
234
+ .sort();
235
+ }