@event4u/agent-config 1.35.0 → 1.36.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.
@@ -351,6 +351,12 @@
351
351
  "load_context": [],
352
352
  "load_context_eager": []
353
353
  },
354
+ ".agent-src.uncompressed/commands/memory/mine-session.md": {
355
+ "kind": "command",
356
+ "rule_type": null,
357
+ "load_context": [],
358
+ "load_context_eager": []
359
+ },
354
360
  ".agent-src.uncompressed/commands/memory/promote.md": {
355
361
  "kind": "command",
356
362
  "rule_type": null,
@@ -843,6 +849,12 @@
843
849
  "load_context": [],
844
850
  "load_context_eager": []
845
851
  },
852
+ ".agent-src.uncompressed/personas/discovery-lead.md": {
853
+ "kind": "persona",
854
+ "rule_type": null,
855
+ "load_context": [],
856
+ "load_context_eager": []
857
+ },
846
858
  ".agent-src.uncompressed/personas/eloquent-tamer.md": {
847
859
  "kind": "persona",
848
860
  "rule_type": null,
@@ -867,6 +879,12 @@
867
879
  "load_context": [],
868
880
  "load_context_eager": []
869
881
  },
882
+ ".agent-src.uncompressed/personas/revops-maintainer.md": {
883
+ "kind": "persona",
884
+ "rule_type": null,
885
+ "load_context": [],
886
+ "load_context_eager": []
887
+ },
870
888
  ".agent-src.uncompressed/personas/security-engineer.md": {
871
889
  "kind": "persona",
872
890
  "rule_type": null,
@@ -885,6 +903,12 @@
885
903
  "load_context": [],
886
904
  "load_context_eager": []
887
905
  },
906
+ ".agent-src.uncompressed/personas/tech-writer.md": {
907
+ "kind": "persona",
908
+ "rule_type": null,
909
+ "load_context": [],
910
+ "load_context_eager": []
911
+ },
888
912
  ".agent-src.uncompressed/rules/agent-authority.md": {
889
913
  "kind": "rule",
890
914
  "rule_type": "always",
@@ -1416,6 +1440,12 @@
1416
1440
  "load_context": [],
1417
1441
  "load_context_eager": []
1418
1442
  },
1443
+ ".agent-src.uncompressed/skills/competitive-positioning/SKILL.md": {
1444
+ "kind": "skill",
1445
+ "rule_type": null,
1446
+ "load_context": [],
1447
+ "load_context_eager": []
1448
+ },
1419
1449
  ".agent-src.uncompressed/skills/composer-packages/SKILL.md": {
1420
1450
  "kind": "skill",
1421
1451
  "rule_type": null,
@@ -1530,6 +1560,12 @@
1530
1560
  "load_context": [],
1531
1561
  "load_context_eager": []
1532
1562
  },
1563
+ ".agent-src.uncompressed/skills/discovery-interview/SKILL.md": {
1564
+ "kind": "skill",
1565
+ "rule_type": null,
1566
+ "load_context": [],
1567
+ "load_context_eager": []
1568
+ },
1533
1569
  ".agent-src.uncompressed/skills/docker/SKILL.md": {
1534
1570
  "kind": "skill",
1535
1571
  "rule_type": null,
@@ -1734,6 +1770,12 @@
1734
1770
  "load_context": [],
1735
1771
  "load_context_eager": []
1736
1772
  },
1773
+ ".agent-src.uncompressed/skills/launch-readiness/SKILL.md": {
1774
+ "kind": "skill",
1775
+ "rule_type": null,
1776
+ "load_context": [],
1777
+ "load_context_eager": []
1778
+ },
1737
1779
  ".agent-src.uncompressed/skills/learning-to-rule-or-skill/SKILL.md": {
1738
1780
  "kind": "skill",
1739
1781
  "rule_type": null,
@@ -1788,6 +1830,12 @@
1788
1830
  "load_context": [],
1789
1831
  "load_context_eager": []
1790
1832
  },
1833
+ ".agent-src.uncompressed/skills/memory-consolidation/SKILL.md": {
1834
+ "kind": "skill",
1835
+ "rule_type": null,
1836
+ "load_context": [],
1837
+ "load_context_eager": []
1838
+ },
1791
1839
  ".agent-src.uncompressed/skills/merge-conflicts/SKILL.md": {
1792
1840
  "kind": "skill",
1793
1841
  "rule_type": null,
@@ -2334,6 +2382,12 @@
2334
2382
  "load_context": [],
2335
2383
  "load_context_eager": []
2336
2384
  },
2385
+ ".agent-src.uncompressed/skills/voc-extract/SKILL.md": {
2386
+ "kind": "skill",
2387
+ "rule_type": null,
2388
+ "load_context": [],
2389
+ "load_context_eager": []
2390
+ },
2337
2391
  ".agent-src.uncompressed/skills/websocket/SKILL.md": {
2338
2392
  "kind": "skill",
2339
2393
  "rule_type": null,
@@ -3217,6 +3271,41 @@
3217
3271
  "via": "self",
3218
3272
  "depth": 0
3219
3273
  },
3274
+ {
3275
+ "source": ".agent-src.uncompressed/commands/memory/load.md",
3276
+ "target": ".agent-src.uncompressed/skills/memory-consolidation/SKILL.md",
3277
+ "type": "READ_ONLY",
3278
+ "via": "body_link",
3279
+ "depth": 1
3280
+ },
3281
+ {
3282
+ "source": ".agent-src.uncompressed/commands/memory/mine-session.md",
3283
+ "target": ".agent-src.uncompressed/commands/memory/mine-session.md",
3284
+ "type": "WRITE",
3285
+ "via": "self",
3286
+ "depth": 0
3287
+ },
3288
+ {
3289
+ "source": ".agent-src.uncompressed/commands/memory/mine-session.md",
3290
+ "target": ".agent-src.uncompressed/commands/memory/promote.md",
3291
+ "type": "READ_ONLY",
3292
+ "via": "body_link",
3293
+ "depth": 1
3294
+ },
3295
+ {
3296
+ "source": ".agent-src.uncompressed/commands/memory/mine-session.md",
3297
+ "target": ".agent-src.uncompressed/commands/memory/propose.md",
3298
+ "type": "READ_ONLY",
3299
+ "via": "body_link",
3300
+ "depth": 1
3301
+ },
3302
+ {
3303
+ "source": ".agent-src.uncompressed/commands/memory/mine-session.md",
3304
+ "target": ".agent-src.uncompressed/skills/memory-consolidation/SKILL.md",
3305
+ "type": "READ_ONLY",
3306
+ "via": "body_link",
3307
+ "depth": 1
3308
+ },
3220
3309
  {
3221
3310
  "source": ".agent-src.uncompressed/commands/memory/promote.md",
3222
3311
  "target": ".agent-src.uncompressed/commands/memory/promote.md",
@@ -3231,6 +3320,13 @@
3231
3320
  "via": "self",
3232
3321
  "depth": 0
3233
3322
  },
3323
+ {
3324
+ "source": ".agent-src.uncompressed/commands/memory/propose.md",
3325
+ "target": ".agent-src.uncompressed/skills/memory-consolidation/SKILL.md",
3326
+ "type": "READ_ONLY",
3327
+ "via": "body_link",
3328
+ "depth": 1
3329
+ },
3234
3330
  {
3235
3331
  "source": ".agent-src.uncompressed/commands/mode.md",
3236
3332
  "target": ".agent-src.uncompressed/commands/mode.md",
@@ -4204,6 +4300,13 @@
4204
4300
  "via": "self",
4205
4301
  "depth": 0
4206
4302
  },
4303
+ {
4304
+ "source": ".agent-src.uncompressed/contexts/authority/scope-mechanics.md",
4305
+ "target": ".agent-src.uncompressed/rules/autonomous-execution.md",
4306
+ "type": "READ_ONLY",
4307
+ "via": "body_link",
4308
+ "depth": 1
4309
+ },
4207
4310
  {
4208
4311
  "source": ".agent-src.uncompressed/contexts/authority/scope-mechanics.md",
4209
4312
  "target": ".agent-src.uncompressed/rules/non-destructive-by-default.md",
@@ -4813,6 +4916,13 @@
4813
4916
  "via": "self",
4814
4917
  "depth": 0
4815
4918
  },
4919
+ {
4920
+ "source": ".agent-src.uncompressed/personas/discovery-lead.md",
4921
+ "target": ".agent-src.uncompressed/personas/discovery-lead.md",
4922
+ "type": "WRITE",
4923
+ "via": "self",
4924
+ "depth": 0
4925
+ },
4816
4926
  {
4817
4927
  "source": ".agent-src.uncompressed/personas/eloquent-tamer.md",
4818
4928
  "target": ".agent-src.uncompressed/personas/eloquent-tamer.md",
@@ -4855,6 +4965,13 @@
4855
4965
  "via": "self",
4856
4966
  "depth": 0
4857
4967
  },
4968
+ {
4969
+ "source": ".agent-src.uncompressed/personas/revops-maintainer.md",
4970
+ "target": ".agent-src.uncompressed/personas/revops-maintainer.md",
4971
+ "type": "WRITE",
4972
+ "via": "self",
4973
+ "depth": 0
4974
+ },
4858
4975
  {
4859
4976
  "source": ".agent-src.uncompressed/personas/security-engineer.md",
4860
4977
  "target": ".agent-src.uncompressed/personas/security-engineer.md",
@@ -4876,6 +4993,13 @@
4876
4993
  "via": "self",
4877
4994
  "depth": 0
4878
4995
  },
4996
+ {
4997
+ "source": ".agent-src.uncompressed/personas/tech-writer.md",
4998
+ "target": ".agent-src.uncompressed/personas/tech-writer.md",
4999
+ "type": "WRITE",
5000
+ "via": "self",
5001
+ "depth": 0
5002
+ },
4879
5003
  {
4880
5004
  "source": ".agent-src.uncompressed/rules/agent-authority.md",
4881
5005
  "target": ".agent-src.uncompressed/rules/agent-authority.md",
@@ -6451,6 +6575,41 @@
6451
6575
  "via": "body_link",
6452
6576
  "depth": 1
6453
6577
  },
6578
+ {
6579
+ "source": ".agent-src.uncompressed/skills/competitive-positioning/SKILL.md",
6580
+ "target": ".agent-src.uncompressed/skills/competitive-positioning/SKILL.md",
6581
+ "type": "WRITE",
6582
+ "via": "self",
6583
+ "depth": 0
6584
+ },
6585
+ {
6586
+ "source": ".agent-src.uncompressed/skills/competitive-positioning/SKILL.md",
6587
+ "target": ".agent-src.uncompressed/skills/dcf-modeling/SKILL.md",
6588
+ "type": "READ_ONLY",
6589
+ "via": "body_link",
6590
+ "depth": 1
6591
+ },
6592
+ {
6593
+ "source": ".agent-src.uncompressed/skills/competitive-positioning/SKILL.md",
6594
+ "target": ".agent-src.uncompressed/skills/decision-record/SKILL.md",
6595
+ "type": "READ_ONLY",
6596
+ "via": "body_link",
6597
+ "depth": 1
6598
+ },
6599
+ {
6600
+ "source": ".agent-src.uncompressed/skills/competitive-positioning/SKILL.md",
6601
+ "target": ".agent-src.uncompressed/skills/stakeholder-tradeoff/SKILL.md",
6602
+ "type": "READ_ONLY",
6603
+ "via": "body_link",
6604
+ "depth": 1
6605
+ },
6606
+ {
6607
+ "source": ".agent-src.uncompressed/skills/competitive-positioning/SKILL.md",
6608
+ "target": ".agent-src.uncompressed/skills/upstream-contribute/SKILL.md",
6609
+ "type": "READ_ONLY",
6610
+ "via": "body_link",
6611
+ "depth": 1
6612
+ },
6454
6613
  {
6455
6614
  "source": ".agent-src.uncompressed/skills/composer-packages/SKILL.md",
6456
6615
  "target": ".agent-src.uncompressed/skills/composer-packages/SKILL.md",
@@ -6647,6 +6806,20 @@
6647
6806
  "via": "self",
6648
6807
  "depth": 0
6649
6808
  },
6809
+ {
6810
+ "source": ".agent-src.uncompressed/skills/decision-record/SKILL.md",
6811
+ "target": ".agent-src.uncompressed/skills/risk-officer/SKILL.md",
6812
+ "type": "READ_ONLY",
6813
+ "via": "body_link",
6814
+ "depth": 1
6815
+ },
6816
+ {
6817
+ "source": ".agent-src.uncompressed/skills/decision-record/SKILL.md",
6818
+ "target": ".agent-src.uncompressed/skills/stakeholder-tradeoff/SKILL.md",
6819
+ "type": "READ_ONLY",
6820
+ "via": "body_link",
6821
+ "depth": 1
6822
+ },
6650
6823
  {
6651
6824
  "source": ".agent-src.uncompressed/skills/deep-reading-analyst/SKILL.md",
6652
6825
  "target": ".agent-src.uncompressed/skills/deep-reading-analyst/SKILL.md",
@@ -6717,6 +6890,41 @@
6717
6890
  "via": "self",
6718
6891
  "depth": 0
6719
6892
  },
6893
+ {
6894
+ "source": ".agent-src.uncompressed/skills/discovery-interview/SKILL.md",
6895
+ "target": ".agent-src.uncompressed/skills/customer-research/SKILL.md",
6896
+ "type": "READ_ONLY",
6897
+ "via": "body_link",
6898
+ "depth": 1
6899
+ },
6900
+ {
6901
+ "source": ".agent-src.uncompressed/skills/discovery-interview/SKILL.md",
6902
+ "target": ".agent-src.uncompressed/skills/discovery-interview/SKILL.md",
6903
+ "type": "WRITE",
6904
+ "via": "self",
6905
+ "depth": 0
6906
+ },
6907
+ {
6908
+ "source": ".agent-src.uncompressed/skills/discovery-interview/SKILL.md",
6909
+ "target": ".agent-src.uncompressed/skills/funnel-analysis/SKILL.md",
6910
+ "type": "READ_ONLY",
6911
+ "via": "body_link",
6912
+ "depth": 1
6913
+ },
6914
+ {
6915
+ "source": ".agent-src.uncompressed/skills/discovery-interview/SKILL.md",
6916
+ "target": ".agent-src.uncompressed/skills/refine-ticket/SKILL.md",
6917
+ "type": "READ_ONLY",
6918
+ "via": "body_link",
6919
+ "depth": 1
6920
+ },
6921
+ {
6922
+ "source": ".agent-src.uncompressed/skills/discovery-interview/SKILL.md",
6923
+ "target": ".agent-src.uncompressed/skills/voc-extract/SKILL.md",
6924
+ "type": "READ_ONLY",
6925
+ "via": "body_link",
6926
+ "depth": 1
6927
+ },
6720
6928
  {
6721
6929
  "source": ".agent-src.uncompressed/skills/docker/SKILL.md",
6722
6930
  "target": ".agent-src.uncompressed/skills/docker/SKILL.md",
@@ -7424,6 +7632,34 @@
7424
7632
  "via": "self",
7425
7633
  "depth": 0
7426
7634
  },
7635
+ {
7636
+ "source": ".agent-src.uncompressed/skills/launch-readiness/SKILL.md",
7637
+ "target": ".agent-src.uncompressed/skills/finishing-a-development-branch/SKILL.md",
7638
+ "type": "READ_ONLY",
7639
+ "via": "body_link",
7640
+ "depth": 1
7641
+ },
7642
+ {
7643
+ "source": ".agent-src.uncompressed/skills/launch-readiness/SKILL.md",
7644
+ "target": ".agent-src.uncompressed/skills/launch-readiness/SKILL.md",
7645
+ "type": "WRITE",
7646
+ "via": "self",
7647
+ "depth": 0
7648
+ },
7649
+ {
7650
+ "source": ".agent-src.uncompressed/skills/launch-readiness/SKILL.md",
7651
+ "target": ".agent-src.uncompressed/skills/release-comms/SKILL.md",
7652
+ "type": "READ_ONLY",
7653
+ "via": "body_link",
7654
+ "depth": 1
7655
+ },
7656
+ {
7657
+ "source": ".agent-src.uncompressed/skills/launch-readiness/SKILL.md",
7658
+ "target": ".agent-src.uncompressed/skills/stakeholder-tradeoff/SKILL.md",
7659
+ "type": "READ_ONLY",
7660
+ "via": "body_link",
7661
+ "depth": 1
7662
+ },
7427
7663
  {
7428
7664
  "source": ".agent-src.uncompressed/skills/learning-to-rule-or-skill/SKILL.md",
7429
7665
  "target": ".agent-src.uncompressed/skills/learning-to-rule-or-skill/SKILL.md",
@@ -7578,6 +7814,20 @@
7578
7814
  "via": "self",
7579
7815
  "depth": 0
7580
7816
  },
7817
+ {
7818
+ "source": ".agent-src.uncompressed/skills/memory-consolidation/SKILL.md",
7819
+ "target": ".agent-src.uncompressed/commands/onboard.md",
7820
+ "type": "READ_ONLY",
7821
+ "via": "body_link",
7822
+ "depth": 1
7823
+ },
7824
+ {
7825
+ "source": ".agent-src.uncompressed/skills/memory-consolidation/SKILL.md",
7826
+ "target": ".agent-src.uncompressed/skills/memory-consolidation/SKILL.md",
7827
+ "type": "WRITE",
7828
+ "via": "self",
7829
+ "depth": 0
7830
+ },
7581
7831
  {
7582
7832
  "source": ".agent-src.uncompressed/skills/merge-conflicts/SKILL.md",
7583
7833
  "target": ".agent-src.uncompressed/skills/merge-conflicts/SKILL.md",
@@ -9426,6 +9676,34 @@
9426
9676
  "via": "self",
9427
9677
  "depth": 0
9428
9678
  },
9679
+ {
9680
+ "source": ".agent-src.uncompressed/skills/voc-extract/SKILL.md",
9681
+ "target": ".agent-src.uncompressed/skills/discovery-interview/SKILL.md",
9682
+ "type": "READ_ONLY",
9683
+ "via": "body_link",
9684
+ "depth": 1
9685
+ },
9686
+ {
9687
+ "source": ".agent-src.uncompressed/skills/voc-extract/SKILL.md",
9688
+ "target": ".agent-src.uncompressed/skills/funnel-analysis/SKILL.md",
9689
+ "type": "READ_ONLY",
9690
+ "via": "body_link",
9691
+ "depth": 1
9692
+ },
9693
+ {
9694
+ "source": ".agent-src.uncompressed/skills/voc-extract/SKILL.md",
9695
+ "target": ".agent-src.uncompressed/skills/refine-ticket/SKILL.md",
9696
+ "type": "READ_ONLY",
9697
+ "via": "body_link",
9698
+ "depth": 1
9699
+ },
9700
+ {
9701
+ "source": ".agent-src.uncompressed/skills/voc-extract/SKILL.md",
9702
+ "target": ".agent-src.uncompressed/skills/voc-extract/SKILL.md",
9703
+ "type": "WRITE",
9704
+ "via": "self",
9705
+ "depth": 0
9706
+ },
9429
9707
  {
9430
9708
  "source": ".agent-src.uncompressed/skills/websocket/SKILL.md",
9431
9709
  "target": ".agent-src.uncompressed/skills/websocket/SKILL.md",
@@ -153,7 +153,7 @@ Your agent now understands slash commands:
153
153
  | `/quality-fix` | Run and fix all quality checks |
154
154
  | `/chat-history` | Inspect the persistent chat-history log (read-only `show`) |
155
155
 
156
- → [Browse all 103 active commands](../.agent-src/commands/)
156
+ → [Browse all 104 active commands](../.agent-src/commands/)
157
157
 
158
158
  ---
159
159
 
@@ -43,6 +43,58 @@ rejects entries missing any required field.
43
43
  | `owner` | yes | team slug | who keeps this entry fresh |
44
44
  | `last_validated` | yes | ISO date | stale check per type |
45
45
  | `review_after_days` | yes | integer | triggers staleness warning |
46
+ | `priority` | no | `critical` \| `normal` \| `low` | tier-0 surfacing; defaults to `normal` |
47
+ | `ts_week` | no | ISO-week string `YYYY-Www` | promotion week stamp; convention, not enforced |
48
+
49
+ ### Priority semantics (`critical` / `normal` / `low`)
50
+
51
+ The `priority` field controls how aggressively `/memory:load` surfaces
52
+ an entry. The three-tier enum is intentional — see
53
+ `road-to-dream-skill-adoption.md` § B2 and the Phase 2 council brief
54
+ for why a fourth `high` tier was rejected.
55
+
56
+ | Value | Meaning | Reader behaviour |
57
+ |---|---|---|
58
+ | `critical` | Tier-0 — always surface regardless of query | `/memory:load` injects on every load, irrespective of key/query match |
59
+ | `normal` (default) | Standard query-matched retrieval | Surfaced when the lookup key/query matches the entry |
60
+ | `low` | Background — only surface on explicit full load | Skipped by query-matched retrieval; visible only via `/memory:load --type` full sweep |
61
+
62
+ **Tier-0 governance.**
63
+ `scripts/check_memory.py` enforces two soft guards on `critical` entries:
64
+
65
+ - **Critical-stale warning** — a `priority: critical` entry whose
66
+ `last_validated` is older than 90 days emits a `critical-stale` warning
67
+ during validation (still exit 0; the curator decides whether to
68
+ re-validate or downgrade).
69
+ - **Tier-0 inflation warning** — when a memory type accumulates more
70
+ than 10 active `critical` entries, the validator warns. The intent is
71
+ to keep the always-surface slice small enough to remain signal, not to
72
+ block writes; raise the threshold deliberately if the project's domain
73
+ genuinely needs more.
74
+
75
+ Both are warnings, never errors. The curator stays in charge.
76
+
77
+ ### Temporal jitter (`ts_week`)
78
+
79
+ `ts_week` stamps a curated entry with the **ISO week** it was promoted
80
+ (`YYYY-Www`, e.g. `2026-W17`). It is optional and **convention-only** —
81
+ the validator does not require it and does not reject entries without
82
+ it. Promotion tooling (`/memory:promote`) writes it; manual edits are
83
+ free to set or omit.
84
+
85
+ **Why ISO-week, not date-time.** Curated YAML lives in the repo and is
86
+ reviewable by anyone with access. A precise timestamp on every entry
87
+ leaks session timing — "this rule appeared Tuesday 3pm" correlates with
88
+ "the incident hit Tuesday 3pm". ISO-week granularity preserves long-
89
+ term ordering (useful for audit) while removing intra-week inference.
90
+
91
+ **When to use it.** Stamp on every promotion. Do not retroactively
92
+ backfill — empty `ts_week` for older entries is fine and a deliberate
93
+ non-signal.
94
+
95
+ **Privacy carve-outs.** Highly sensitive entries (incident-learnings
96
+ tied to active investigations) may omit `ts_week` entirely; the field
97
+ is not a forensic record.
46
98
 
47
99
  ## Type-specific required fields
48
100
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "1.35.0",
3
+ "version": "1.36.0",
4
4
  "description": "Shared agent configuration \u2014 skills, rules, commands, guidelines, and templates for AI coding tools",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -49,6 +49,19 @@ REQUIRED_KEYS = {
49
49
  }
50
50
  VALID_STATUS = {"active", "deprecated", "archived"}
51
51
  VALID_CONFIDENCE = {"low", "medium", "high"}
52
+ # `priority` is optional (default `normal`); enum is the smallest set that
53
+ # solves the tier-0 surfacing use case. See `road-to-dream-skill-adoption.md`
54
+ # § B2 and the Phase 2 council brief for why the `high` tier was rejected.
55
+ VALID_PRIORITY = {"critical", "normal", "low"}
56
+ # Soft-cap on `priority: critical` entries per memory type. Tier-0 inflation
57
+ # is the failure mode: when too many entries claim "always surface", the
58
+ # slice loses signal. Warn (not fail) when the cap is exceeded so curators
59
+ # notice without being blocked.
60
+ CRITICAL_WARN_THRESHOLD = 10
61
+ # Stale-critical guard: a `priority: critical` entry that hasn't been
62
+ # re-validated in this many days emits a warning. Surfaced separately
63
+ # from the generic `stale:` info so reviewers see it before merge.
64
+ CRITICAL_STALE_DAYS = 90
52
65
  KNOWN_TYPES = {
53
66
  "domain-invariants", "architecture-decisions",
54
67
  "incident-learnings", "product-rules",
@@ -69,6 +82,19 @@ REDACTION_PATTERNS = [
69
82
  (re.compile(r"\b192\.168\.\d{1,3}\.\d{1,3}\b"), "internal ipv4 range"),
70
83
  ]
71
84
 
85
+ # Date-discipline — relative-date phrases without an ISO YYYY-MM-DD anchor
86
+ # within ±20 chars are rejected. Memory entries that say "yesterday" or
87
+ # "last week" rot the moment the file is re-read on another day; the
88
+ # anchor pins meaning. See `road-to-dream-skill-adoption.md` § A5.
89
+ RELATIVE_DATE_PATTERN = re.compile(
90
+ r"(?i)\b(yesterday|today|tomorrow|"
91
+ r"last\s+(?:week|month|year)|"
92
+ r"next\s+(?:week|month|year)|"
93
+ r"this\s+(?:week|month|year))\b"
94
+ )
95
+ ISO_DATE_PATTERN = re.compile(r"\b\d{4}-\d{2}-\d{2}\b")
96
+ DATE_ANCHOR_WINDOW = 20
97
+
72
98
 
73
99
  @dataclass
74
100
  class Finding:
@@ -105,7 +131,13 @@ def _memory_type(path: Path) -> str:
105
131
  return stem[:-len(".example")] if stem.endswith(".example") else stem
106
132
 
107
133
 
108
- def _validate_entry(entry: dict, path: Path, seen_ids: set, findings: List[Finding]):
134
+ def _validate_entry(
135
+ entry: dict,
136
+ path: Path,
137
+ seen_ids: set,
138
+ findings: List[Finding],
139
+ critical_counts: Optional[dict] = None,
140
+ ):
109
141
  eid = entry.get("id", "")
110
142
  missing = REQUIRED_KEYS - set(entry.keys())
111
143
  for key in sorted(missing):
@@ -116,6 +148,14 @@ def _validate_entry(entry: dict, path: Path, seen_ids: set, findings: List[Findi
116
148
  if entry.get("confidence") and entry["confidence"] not in VALID_CONFIDENCE:
117
149
  findings.append(Finding(str(path), 0, "error",
118
150
  f"invalid confidence '{entry['confidence']}'", eid))
151
+ # Priority is optional (defaults to `normal` at read time). When present
152
+ # it MUST be one of the three-tier enum — see VALID_PRIORITY for the
153
+ # rationale on rejecting a fourth `high` tier.
154
+ priority = entry.get("priority")
155
+ if priority is not None and priority not in VALID_PRIORITY:
156
+ findings.append(Finding(str(path), 0, "error",
157
+ f"invalid priority '{priority}' "
158
+ f"(expected one of {sorted(VALID_PRIORITY)})", eid))
119
159
  sources = entry.get("source") or []
120
160
  if not isinstance(sources, list) or len(sources) < 1:
121
161
  findings.append(Finding(str(path), 0, "error",
@@ -131,6 +171,26 @@ def _validate_entry(entry: dict, path: Path, seen_ids: set, findings: List[Findi
131
171
  if age > days and entry.get("status") == "active":
132
172
  findings.append(Finding(str(path), 0, "info",
133
173
  f"stale: last_validated {age} days ago (limit {days})", eid))
174
+ # Critical-stale guard: a `priority: critical` entry that has not been
175
+ # re-validated within CRITICAL_STALE_DAYS surfaces as a warning, even
176
+ # when the entry's own `review_after_days` is more lenient. Critical
177
+ # entries surface on every /memory:load — they have a tighter SLA.
178
+ if (
179
+ priority == "critical"
180
+ and entry.get("status") == "active"
181
+ and isinstance(lv, _dt.date)
182
+ ):
183
+ crit_age = (_dt.date.today() - lv).days
184
+ if crit_age > CRITICAL_STALE_DAYS:
185
+ findings.append(Finding(
186
+ str(path), 0, "warning",
187
+ f"critical-stale: last_validated {crit_age} days ago "
188
+ f"(critical SLA is {CRITICAL_STALE_DAYS} days)", eid))
189
+ # Tier-0 inflation tracking — increment per memory type. The aggregate
190
+ # warning is emitted in main() after all files are validated.
191
+ if critical_counts is not None and priority == "critical" and entry.get("status") == "active":
192
+ mtype = _memory_type(path)
193
+ critical_counts[mtype] = critical_counts.get(mtype, 0) + 1
134
194
 
135
195
 
136
196
  def _check_redaction(path: Path, findings: List[Finding]):
@@ -144,12 +204,43 @@ def _check_redaction(path: Path, findings: List[Finding]):
144
204
  f"possible leak: {label}"))
145
205
 
146
206
 
147
- def _validate_file(path: Path, findings: List[Finding]):
207
+ def _check_date_discipline(path: Path, findings: List[Finding]):
208
+ """Reject relative-date phrases without an ISO YYYY-MM-DD anchor.
209
+
210
+ A curated memory entry that says "fixed yesterday" rots silently
211
+ the moment the file is re-read on a different day. We require an
212
+ ISO date within ±20 chars of every relative phrase so the meaning
213
+ survives the calendar.
214
+ """
215
+ for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
216
+ # Skip comments and the YAML key for `last_validated` itself.
217
+ stripped = line.lstrip()
218
+ if stripped.startswith("#") or stripped.startswith("last_validated"):
219
+ continue
220
+ for match in RELATIVE_DATE_PATTERN.finditer(line):
221
+ start = max(0, match.start() - DATE_ANCHOR_WINDOW)
222
+ end = min(len(line), match.end() + DATE_ANCHOR_WINDOW)
223
+ window = line[start:end]
224
+ if ISO_DATE_PATTERN.search(window):
225
+ continue
226
+ phrase = match.group(0)
227
+ findings.append(Finding(
228
+ str(path), line_no, "error",
229
+ f"relative date '{phrase}' without an ISO YYYY-MM-DD anchor "
230
+ f"within ±{DATE_ANCHOR_WINDOW} chars (re-anchor before commit)"))
231
+
232
+
233
+ def _validate_file(
234
+ path: Path,
235
+ findings: List[Finding],
236
+ critical_counts: Optional[dict] = None,
237
+ ):
148
238
  mtype = _memory_type(path)
149
239
  if mtype not in KNOWN_TYPES:
150
240
  findings.append(Finding(str(path), 0, "warning",
151
241
  f"unknown memory type '{mtype}'"))
152
242
  _check_redaction(path, findings)
243
+ _check_date_discipline(path, findings)
153
244
  try:
154
245
  data = _load_yaml(path) or {}
155
246
  except Exception as exc: # yaml.YAMLError or anything else
@@ -163,7 +254,7 @@ def _validate_file(path: Path, findings: List[Finding]):
163
254
  seen_ids: set = set()
164
255
  for entry in data.get("entries") or []:
165
256
  if isinstance(entry, dict):
166
- _validate_entry(entry, path, seen_ids, findings)
257
+ _validate_entry(entry, path, seen_ids, findings, critical_counts)
167
258
 
168
259
 
169
260
  INTAKE_GLOB = "agents/memory/intake/*.jsonl"
@@ -316,8 +407,19 @@ def main() -> int:
316
407
  else:
317
408
  print(f"ℹ️ {root} not found — nothing to validate")
318
409
  return 0
410
+ critical_counts: dict = {}
319
411
  for yml in sorted(root.rglob("*.yml")):
320
- _validate_file(yml, findings)
412
+ _validate_file(yml, findings, critical_counts)
413
+ # Tier-0 inflation warning — soft cap on `priority: critical` per type.
414
+ # Council convergence (Phase 2 B2): warn rather than block, because the
415
+ # right answer to "too many criticals" is curator review, not CI failure.
416
+ for mtype, count in sorted(critical_counts.items()):
417
+ if count > CRITICAL_WARN_THRESHOLD:
418
+ findings.append(Finding(
419
+ f"agents/memory/{mtype}", 0, "warning",
420
+ f"tier-0 inflation: {count} active 'priority: critical' "
421
+ f"entries (threshold {CRITICAL_WARN_THRESHOLD}) — review "
422
+ f"whether all still warrant always-surface treatment"))
321
423
  return _emit(findings, args.format)
322
424
 
323
425
 
@@ -35,6 +35,7 @@ SCAN_DIRS = [".agent-src", "agents"]
35
35
  SKIP_DIRS = [
36
36
  "agents/roadmaps/archive", # archived roadmaps have historical refs
37
37
  "agents/council-sessions", # per-user audit trail (gitignored), captured provider output
38
+ "agents/council-responses", # paired council output (gitignored), captured provider output
38
39
  "agents/council-questions", # design Q&A trail — forward-refs to planned artifacts
39
40
  "agents/analysis", # plate-comparison working docs — forward-refs to planned artifacts
40
41
  ]