@event4u/agent-config 9.21.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 (93) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +69 -221
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/optimize/deep.md +13 -0
  5. package/dist/agent-src/commands/worktree/cleanup.md +17 -0
  6. package/dist/agent-src/commands/worktree/status.md +6 -0
  7. package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +12 -2
  8. package/dist/agent-src/contexts/execution/mandated-lines.md +180 -0
  9. package/dist/agent-src/rules/context-hygiene.md +17 -0
  10. package/dist/agent-src/rules/downstream-changes.md +26 -0
  11. package/dist/agent-src/rules/think-before-action.md +25 -0
  12. package/dist/agent-src/rules/token-budget-discipline.md +46 -3
  13. package/dist/agent-src/rules/token-efficiency.md +13 -0
  14. package/dist/agent-src/rules/ui-audit-gate.md +33 -2
  15. package/dist/agent-src/skills/adversarial-review/SKILL.md +18 -4
  16. package/dist/agent-src/skills/decision-review/SKILL.md +49 -0
  17. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +5 -1
  18. package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +16 -0
  19. package/dist/agent-src/skills/judge-synthesis/SKILL.md +14 -1
  20. package/dist/agent-src/skills/rule-writing/SKILL.md +33 -0
  21. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +31 -0
  22. package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
  23. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +22 -0
  24. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  25. package/dist/cli/registry.js +1 -0
  26. package/dist/cli/registry.js.map +1 -1
  27. package/dist/cli-delegate/chunk-DVRS2KVR.js +4602 -0
  28. package/dist/cli-delegate/{chunk-ELMFOQPW.js → chunk-SJQMJI7M.js} +8 -4597
  29. package/dist/cli-delegate/cmd_export.js +2 -1
  30. package/dist/cli-delegate/cmd_prune.js +2 -1
  31. package/dist/cli-delegate/cmd_settings_migrate.js +2 -1
  32. package/dist/cli-delegate/cmd_settings_set.js +3370 -0
  33. package/dist/cli-delegate/cmd_sync.js +2 -1
  34. package/dist/cli-delegate/cmd_uninstall.js +2 -1
  35. package/dist/cli-delegate/cmd_validate.js +2 -1
  36. package/dist/discovery/deprecation-report.md +1 -1
  37. package/dist/discovery/discovery-manifest.json +20 -20
  38. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  39. package/dist/discovery/discovery-manifest.summary.md +1 -1
  40. package/dist/discovery/orphan-report.md +1 -1
  41. package/dist/discovery/packs.json +2 -2
  42. package/dist/discovery/trust-report.md +1 -1
  43. package/dist/discovery/workspaces.json +2 -2
  44. package/dist/hooks/dispatch.js +79 -1
  45. package/dist/mcp/registry-manifest.json +2 -2
  46. package/dist/mcp/server.json +2 -2
  47. package/dist/scripts/condense.js +47 -2
  48. package/dist/scripts/condense.js.map +1 -1
  49. package/dist/server/routes/settings.js +82 -0
  50. package/dist/server/routes/settings.js.map +1 -1
  51. package/dist/shared/settingsClasses.js +191 -0
  52. package/dist/shared/settingsClasses.js.map +1 -0
  53. package/docs/contracts/settings-classes.md +342 -0
  54. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +23 -2
  55. package/docs/guidelines/agent-infra/false-green.md +187 -0
  56. package/docs/guidelines/agent-infra/gate-authoring.md +177 -0
  57. package/package.json +3 -1
  58. package/src/config/ci-local-parity.yml +12 -0
  59. package/src/config/evaluator-budgets.json +4 -4
  60. package/src/config/gate-coverage.yml +54 -12
  61. package/src/config/gate-violation-baselines.json +6 -0
  62. package/src/config/pack-size-budget.json +4 -3
  63. package/src/scripts/_cli/cmd_settings_set.ts +509 -0
  64. package/src/scripts/_dispatch.bash +16 -0
  65. package/src/scripts/_lib/bench_ab_activation.ts +368 -0
  66. package/src/scripts/_lib/gate_ledger.ts +295 -0
  67. package/src/scripts/_lib/gate_result.ts +101 -0
  68. package/src/scripts/_lib/gate_self_test.ts +129 -0
  69. package/src/scripts/_lib/measured_render.ts +97 -0
  70. package/src/scripts/_lib/ratchet_base_ref.ts +373 -0
  71. package/src/scripts/bench_ab_task_runner.ts +18 -0
  72. package/src/scripts/bench_ab_v2_run.ts +147 -6
  73. package/src/scripts/bench_ab_v2_stats.ts +74 -2
  74. package/src/scripts/check_gate_completeness.ts +244 -0
  75. package/src/scripts/check_gate_coverage.ts +35 -6
  76. package/src/scripts/check_references.ts +125 -15
  77. package/src/scripts/check_review_dispositions.ts +14 -1
  78. package/src/scripts/check_suppression_hygiene.ts +503 -0
  79. package/src/scripts/condense.ts +47 -2
  80. package/src/scripts/hook_manifest.yaml +23 -0
  81. package/src/scripts/hooks/dispatch_hook.ts +65 -1
  82. package/src/scripts/hooks/host_semantics.ts +140 -0
  83. package/src/scripts/lint_framework_leakage.ts +147 -15
  84. package/src/scripts/lint_mandated_lines.ts +339 -0
  85. package/src/scripts/lint_roadmap_complexity.ts +102 -11
  86. package/src/scripts/lint_roadmap_family_cap.ts +191 -0
  87. package/src/scripts/lint_settings_classes.ts +397 -0
  88. package/src/scripts/lint_token_budget_discipline.ts +109 -1
  89. package/src/scripts/report_imperative_density.ts +229 -0
  90. package/src/scripts/verify_physical_move.ts +10 -1
  91. package/src/scripts/worktree_cleanup_check.ts +319 -4
  92. package/src/server/io/yamlIO.ts +173 -0
  93. package/src/shared/settingsClasses.ts +235 -0
@@ -52,6 +52,7 @@ import * as yaml from 'js-yaml';
52
52
  import { checkRatchet } from './_lib/gate_baseline.js';
53
53
  import { runCountedProbe } from './_lib/counted_probe.js';
54
54
  import { listGateScripts } from './_lib/gate_population.js';
55
+ import { describeOutcome, namesEstateInvalidatingError } from './_lib/gate_result.js';
55
56
  import { assertScanned } from './_lib/scan_scope.js';
56
57
 
57
58
  const _HERE = fileURLToPath(import.meta.url);
@@ -100,7 +101,14 @@ export interface GateSpec {
100
101
  note?: string;
101
102
  }
102
103
 
103
- export type Verdict = 'ok' | 'below_floor' | 'silent' | 'crashed' | 'pending' | 'unavailable';
104
+ export type Verdict =
105
+ | 'ok'
106
+ | 'below_floor'
107
+ | 'silent'
108
+ | 'crashed'
109
+ | 'estate_invalid'
110
+ | 'pending'
111
+ | 'unavailable';
104
112
 
105
113
  export interface GateResult {
106
114
  id: string;
@@ -186,6 +194,7 @@ export function classify(
186
194
  scanned: number | null,
187
195
  crashed: boolean,
188
196
  exit_code?: number | null,
197
+ output = '',
189
198
  ): GateResult {
190
199
  const base = { id: spec.id, status: spec.status, scanned, min_scanned: spec.min_scanned };
191
200
  if (spec.status === 'pending') {
@@ -201,6 +210,18 @@ export function classify(
201
210
  if (crashed) {
202
211
  return { ...base, verdict: 'crashed', message: 'gate could not be executed' };
203
212
  }
213
+ // A gate that could not MEASURE is not a gate that found violations, and it
214
+ // is not a gate that passed either. Reporting it as itself is the whole point:
215
+ // three of this repository's recorded traps are estate invalidation
216
+ // misreported as a per-gate red, sending a contributor hunting for a
217
+ // violation that does not exist.
218
+ if (namesEstateInvalidatingError(output)) {
219
+ return {
220
+ ...base,
221
+ verdict: 'estate_invalid',
222
+ message: describeOutcome(spec.id, 'estate_invalid'),
223
+ };
224
+ }
204
225
  if (
205
226
  spec.unavailable_exit !== undefined &&
206
227
  exit_code !== undefined &&
@@ -234,16 +255,23 @@ export function classify(
234
255
  return { ...base, verdict: 'ok', message: `scanned ${String(scanned)} ≥ ${String(spec.min_scanned)}` };
235
256
  }
236
257
 
237
- function run_gate(spec: GateSpec): { scanned: number | null; crashed: boolean; exit_code: number | null } {
258
+ function run_gate(spec: GateSpec): {
259
+ scanned: number | null;
260
+ crashed: boolean;
261
+ exit_code: number | null;
262
+ output: string;
263
+ } {
238
264
  const runner = path.join(REPO_ROOT, 'scripts-run');
239
265
  // Bounded read: a gate whose output overflowed would lose its `scanned:`
240
266
  // line and be classified `silent` — the scan-scope guard reporting a dead
241
267
  // scope that is merely truncated. `runCountedProbe` throws instead.
242
268
  const r = runCountedProbe(runner, [`src/scripts/${spec.id}`, ...spec.argv], { cwd: REPO_ROOT });
243
- if (r.failure !== null && r.status === null) return { scanned: null, crashed: true, exit_code: null };
269
+ const output = `${r.stdout}\n${r.stderr}`;
270
+ if (r.failure !== null && r.status === null)
271
+ return { scanned: null, crashed: true, exit_code: null, output };
244
272
  // A gate may legitimately exit non-zero (it found real violations) and still
245
273
  // have scanned plenty — coverage and verdict are different questions.
246
- return { scanned: parse_scanned(`${r.stdout}\n${r.stderr}`), crashed: false, exit_code: r.status };
274
+ return { scanned: parse_scanned(output), crashed: false, exit_code: r.status, output };
247
275
  }
248
276
 
249
277
  const ICON: Record<Verdict, string> = {
@@ -251,6 +279,7 @@ const ICON: Record<Verdict, string> = {
251
279
  below_floor: '❌',
252
280
  silent: '❌',
253
281
  crashed: '❌',
282
+ estate_invalid: '❌',
254
283
  pending: '⚠️',
255
284
  unavailable: '⚠️',
256
285
  };
@@ -299,8 +328,8 @@ export function main(argv: readonly string[]): number {
299
328
  const { scanned, crashed } = probe_pending(s);
300
329
  return classify(s, scanned, crashed);
301
330
  }
302
- const { scanned, crashed, exit_code } = run_gate(s);
303
- return classify(s, scanned, crashed, exit_code);
331
+ const { scanned, crashed, exit_code, output } = run_gate(s);
332
+ return classify(s, scanned, crashed, exit_code, output);
304
333
  });
305
334
 
306
335
  if (wantJson) {
@@ -21,10 +21,18 @@
21
21
  */
22
22
 
23
23
  import * as fs from 'node:fs';
24
+ import * as os from 'node:os';
24
25
  import * as path from 'node:path';
25
26
  import { fileURLToPath, pathToFileURL } from 'node:url';
26
27
  import type * as YamlModule from 'yaml';
27
28
 
29
+ import { GateLedger, type GateSkipReason } from './_lib/gate_ledger.js';
30
+ import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
31
+ import { assertScanned } from './_lib/scan_scope.js';
32
+
33
+ /** Where this script lives — used only to re-invoke its own CLI in `--self-test`. */
34
+ const REPO_ROOT_FOR_SELF_TEST = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
35
+
28
36
  type Severity = 'error' | 'warning';
29
37
 
30
38
  interface BrokenRef {
@@ -480,13 +488,26 @@ function _closest_match(name: string, candidates: Set<string>): string {
480
488
  return '';
481
489
  }
482
490
 
483
- function check_file(filepath: string, artifacts: Artifacts, root: string): BrokenRef[] {
491
+ function check_file(
492
+ filepath: string,
493
+ artifacts: Artifacts,
494
+ root: string,
495
+ /**
496
+ * Called instead of checking, when the file is not judged at all. Both
497
+ * early returns below used to return an empty `broken[]`, which is
498
+ * byte-identical to "checked and clean" for every caller — so an
499
+ * opted-out file counted as coverage. The sink lets the ledger tell the
500
+ * two apart without changing this function's return type.
501
+ */
502
+ onSkip?: (reason: GateSkipReason) => void,
503
+ ): BrokenRef[] {
484
504
  // Check a single .md file for broken references.
485
505
  const broken: BrokenRef[] = [];
486
506
  let text: string;
487
507
  try {
488
508
  text = fs.readFileSync(filepath, 'utf-8');
489
509
  } catch {
510
+ onSkip?.('binary_content');
490
511
  return broken;
491
512
  }
492
513
 
@@ -496,6 +517,7 @@ function check_file(filepath: string, artifacts: Artifacts, root: string): Broke
496
517
  // with the per-line `<!-- ref-ignore -->` below; either suffices.
497
518
  const headerLines = text.split('\n').slice(0, 10);
498
519
  if (headerLines.some((line) => line.includes(FILE_SKIP_MARKER))) {
520
+ onSkip?.('declared_exemption');
499
521
  return broken;
500
522
  }
501
523
 
@@ -783,37 +805,73 @@ function check_memory_yaml(filepath: string, artifacts: Artifacts, root: string)
783
805
  function scan_all(root: string): BrokenRef[] {
784
806
  const artifacts = collect_artifacts(root);
785
807
  const broken: BrokenRef[] = [];
786
- // gate-coverage contract (src/config/gate-coverage.yml): a machine-readable
787
- // count of files actually read, so `check_gate_coverage` can assert this
788
- // gate saw a real corpus rather than a moved root.
789
- let scannedFiles = 0;
808
+ // Completeness accounting. The two hidden outcomes this makes visible:
809
+ // a missing scan root (`continue` on a dead directory, previously silent
810
+ // and uncounted — the canonical false green), and a file that opted out of
811
+ // the check but was still counted as coverage.
812
+ const ledger = new GateLedger('check_references');
790
813
  for (const scanDir of SCAN_DIRS) {
791
814
  const d = path.join(root, scanDir);
792
815
  if (!_exists(d)) {
816
+ ledger.plan(scanDir);
817
+ ledger.skip(scanDir, 'dead_scan_root');
793
818
  continue;
794
819
  }
795
820
  const files = _rglob(d, '.md').sort();
821
+ ledger.plan(files.map((f) => _relPosix(root, f)));
796
822
  for (const f of files) {
823
+ const rel = _relPosix(root, f);
797
824
  // Skip archived directories
798
825
  if (SKIP_DIRS.some((skip) => f.startsWith(path.join(root, skip)))) {
826
+ ledger.outOfScope(rel, 'excluded_directory');
799
827
  continue;
800
828
  }
801
- scannedFiles += 1;
802
- broken.push(...check_file(f, artifacts, root));
829
+ let skipped = false;
830
+ const found = check_file(f, artifacts, root, (reason) => {
831
+ skipped = true;
832
+ ledger.skip(rel, reason);
833
+ });
834
+ if (!skipped) {
835
+ if (found.length > 0) {
836
+ ledger.fail(rel, `${String(found.length)} broken reference(s)`);
837
+ } else {
838
+ ledger.complete(rel);
839
+ }
840
+ }
841
+ broken.push(...found);
803
842
  }
804
843
  }
805
844
  const memoryDir = path.join(root, MEMORY_YAML_ROOT);
806
845
  if (_isDir(memoryDir)) {
807
- for (const f of _rglob(memoryDir, '.yml').sort()) {
808
- scannedFiles += 1;
809
- broken.push(...check_memory_yaml(f, artifacts, root));
810
- }
811
- for (const f of _rglob(memoryDir, '.yaml').sort()) {
812
- scannedFiles += 1;
813
- broken.push(...check_memory_yaml(f, artifacts, root));
846
+ const yamls = [..._rglob(memoryDir, '.yml').sort(), ..._rglob(memoryDir, '.yaml').sort()];
847
+ ledger.plan(yamls.map((f) => _relPosix(root, f)));
848
+ for (const f of yamls) {
849
+ const rel = _relPosix(root, f);
850
+ const found = check_memory_yaml(f, artifacts, root);
851
+ if (found.length > 0) {
852
+ ledger.fail(rel, `${String(found.length)} broken reference(s)`);
853
+ } else {
854
+ ledger.complete(rel);
855
+ }
856
+ broken.push(...found);
814
857
  }
815
858
  }
816
- process.stderr.write(`scanned: ${String(scannedFiles)}\n`);
859
+ // gate-coverage contract (src/config/gate-coverage.yml): a machine-readable
860
+ // count of files actually JUDGED, so `check_gate_coverage` can assert this
861
+ // gate saw a real corpus rather than a moved root. It is the ledger's own
862
+ // completed+failed, so the published number cannot drift from the number
863
+ // the accounting accepted.
864
+ const tally = ledger.report();
865
+ // Fail closed on a fully dead scope. Both scan roots absent used to print
866
+ // `scanned: 0` and then `✅ No broken references found.` — the canonical
867
+ // false green, and the one shape this gate had no guard against.
868
+ assertScanned({
869
+ gate: 'check_references',
870
+ scanned: tally.completed + tally.failed,
871
+ units: 'file(s)',
872
+ roots: SCAN_DIRS,
873
+ });
874
+ process.stderr.write(`scanned: ${String(tally.completed + tally.failed)}\n`);
817
875
  return broken;
818
876
  }
819
877
 
@@ -918,7 +976,59 @@ function _argparse_error(message: string): never {
918
976
  process.exit(2);
919
977
  }
920
978
 
979
+ /**
980
+ * Prove, against the real CLI, that this gate still rejects what it must.
981
+ *
982
+ * The dead-scope case is the one that matters: until this change, both scan
983
+ * roots absent produced `scanned: 0` followed by "No broken references found"
984
+ * and exit 0.
985
+ */
986
+ export function selfTest(): number {
987
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'refs-selftest-'));
988
+ const seed = (root: string, body: string | null): string => {
989
+ const dir = path.join(tmp, root, 'dist', 'agent-src', 'rules');
990
+ fs.mkdirSync(dir, { recursive: true });
991
+ if (body !== null) {
992
+ fs.writeFileSync(path.join(dir, 'demo.md'), body, 'utf-8');
993
+ }
994
+ return path.join(tmp, root);
995
+ };
996
+ const run = (root: string): number =>
997
+ runGateCli(REPO_ROOT_FOR_SELF_TEST, 'src/scripts/check_references.ts', ['--root', root], REPO_ROOT_FOR_SELF_TEST);
998
+
999
+ try {
1000
+ return runSelfTest({
1001
+ gate: 'check_references',
1002
+ minCases: 3,
1003
+ minRejectCases: 2,
1004
+ cases: [
1005
+ {
1006
+ name: 'a broken file reference is rejected',
1007
+ expect: 'reject',
1008
+ run: () =>
1009
+ run(seed('broken', '# Demo\n\nSee `docs/guidelines/definitely-not-here.md` for detail.\n')),
1010
+ },
1011
+ {
1012
+ name: 'a document with no references passes',
1013
+ expect: 'accept',
1014
+ run: () => run(seed('clean', '# Demo\n\nNothing to resolve here.\n')),
1015
+ },
1016
+ {
1017
+ name: 'an EMPTY corpus is rejected — a gate that read nothing has not passed',
1018
+ expect: 'reject',
1019
+ run: () => run(seed('empty', null)),
1020
+ },
1021
+ ],
1022
+ });
1023
+ } finally {
1024
+ fs.rmSync(tmp, { recursive: true, force: true });
1025
+ }
1026
+ }
1027
+
921
1028
  function main(): number {
1029
+ if (process.argv.slice(2).includes('--self-test')) {
1030
+ return selfTest();
1031
+ }
922
1032
  const args = parse_args(process.argv.slice(2));
923
1033
 
924
1034
  let broken: BrokenRef[];
@@ -45,6 +45,7 @@ import * as fs from 'node:fs';
45
45
  import * as path from 'node:path';
46
46
  import { fileURLToPath, pathToFileURL } from 'node:url';
47
47
 
48
+ import { GateLedger } from './_lib/gate_ledger.js';
48
49
  import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
49
50
  import { parseArtifact } from './check_completion_review.js';
50
51
 
@@ -150,12 +151,23 @@ export function main(argv?: readonly string[]): number {
150
151
  }
151
152
 
152
153
  const violations: Violation[] = [];
154
+ // Per-target completeness accounting: the read below can throw partway and
155
+ // the catch reports `scanned` as if the remaining records had been judged.
156
+ // The ledger names what was actually reached.
157
+ const ledger = new GateLedger('check_review_dispositions');
158
+ ledger.plan(files);
153
159
  let scanned = 0;
154
160
  try {
155
161
  for (const rel of files) {
156
162
  const text = fs.readFileSync(path.resolve(repo, rel), 'utf-8');
157
163
  scanned += 1;
158
- violations.push(...checkRecord(rel, text, { repo }));
164
+ const found = checkRecord(rel, text, { repo });
165
+ if (found.length > 0) {
166
+ ledger.fail(rel, `${String(found.length)} disposition violation(s)`);
167
+ } else {
168
+ ledger.complete(rel);
169
+ }
170
+ violations.push(...found);
159
171
  }
160
172
  } catch (exc) {
161
173
  process.stdout.write(`scanned: ${String(scanned)}\n`);
@@ -174,6 +186,7 @@ export function main(argv?: readonly string[]): number {
174
186
  } else if (!quiet) {
175
187
  process.stdout.write(`✅ Archived review records all terminal (${String(scanned)} record(s) scanned).\n`);
176
188
  }
189
+ ledger.report();
177
190
  process.stdout.write(`scanned: ${String(scanned)}\n`);
178
191
  return violations.length > 0 ? 1 : 0;
179
192
  }