@dzhechkov/harness-core 0.8.34 → 0.8.35
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/.dz-manifest.json +41 -41
- package/README.md +157 -12
- package/dist/agentdb-index.d.ts +22 -1
- package/dist/agentdb-index.d.ts.map +1 -1
- package/dist/agentdb-index.js +156 -6
- package/dist/agentdb-index.js.map +1 -1
- package/dist/apply-leg.d.ts +142 -2
- package/dist/apply-leg.d.ts.map +1 -1
- package/dist/apply-leg.js +534 -38
- package/dist/apply-leg.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/mutation-gate.d.ts +19 -0
- package/dist/mutation-gate.d.ts.map +1 -1
- package/dist/mutation-gate.js +37 -1
- package/dist/mutation-gate.js.map +1 -1
- package/dist/operations.d.ts +16 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +72 -9
- package/dist/operations.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +90 -14
- package/dist/setup.js.map +1 -1
- package/dist/skills.d.ts +87 -3
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +266 -15
- package/dist/skills.js.map +1 -1
- package/dist/vector-tier.d.ts.map +1 -1
- package/dist/vector-tier.js +12 -8
- package/dist/vector-tier.js.map +1 -1
- package/package.json +2 -2
- package/sbom.json +40 -40
- package/src/agentdb-index.ts +158 -7
- package/src/apply-leg.ts +556 -38
- package/src/index.ts +9 -1
- package/src/mutation-gate.ts +58 -2
- package/src/operations.ts +76 -10
- package/src/setup.ts +81 -16
- package/src/skills.ts +303 -14
- package/src/vector-tier.ts +20 -10
package/sbom.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"hashes": [
|
|
26
26
|
{
|
|
27
27
|
"alg": "SHA-256",
|
|
28
|
-
"content": "
|
|
28
|
+
"content": "37a3aab588b4b12b15d2d67a014abec49ae38615a1d28816310d5f2e6ad29f79"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
},
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"hashes": [
|
|
76
76
|
{
|
|
77
77
|
"alg": "SHA-256",
|
|
78
|
-
"content": "
|
|
78
|
+
"content": "179cd2dee305664b3c8365b4a8dcd64015b4e226b2753c6dd1e42769b4d0c96f"
|
|
79
79
|
}
|
|
80
80
|
]
|
|
81
81
|
},
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"hashes": [
|
|
86
86
|
{
|
|
87
87
|
"alg": "SHA-256",
|
|
88
|
-
"content": "
|
|
88
|
+
"content": "1e366365fadabe345c7d128da0250a4a21dc7e4924d1cc04f457fd38b40d3c72"
|
|
89
89
|
}
|
|
90
90
|
]
|
|
91
91
|
},
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"hashes": [
|
|
96
96
|
{
|
|
97
97
|
"alg": "SHA-256",
|
|
98
|
-
"content": "
|
|
98
|
+
"content": "bbcee70ee0aa52d95960956a5692cff81d296e49083487c0bbfd3c3d93c94f9c"
|
|
99
99
|
}
|
|
100
100
|
]
|
|
101
101
|
},
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"hashes": [
|
|
106
106
|
{
|
|
107
107
|
"alg": "SHA-256",
|
|
108
|
-
"content": "
|
|
108
|
+
"content": "02208be0504446c12852720a17f99df8d64e69ba759a55f2ea4095ea19fa2f37"
|
|
109
109
|
}
|
|
110
110
|
]
|
|
111
111
|
},
|
|
@@ -315,7 +315,7 @@
|
|
|
315
315
|
"hashes": [
|
|
316
316
|
{
|
|
317
317
|
"alg": "SHA-256",
|
|
318
|
-
"content": "
|
|
318
|
+
"content": "9cc4cf39b4ef8479eba5745c767c60a8a331f777e19251ec9faf42530f16b7c7"
|
|
319
319
|
}
|
|
320
320
|
]
|
|
321
321
|
},
|
|
@@ -325,7 +325,7 @@
|
|
|
325
325
|
"hashes": [
|
|
326
326
|
{
|
|
327
327
|
"alg": "SHA-256",
|
|
328
|
-
"content": "
|
|
328
|
+
"content": "5204913b80c8748926f20a1632658720b75336fcd7bdf15114ceb1707fde804c"
|
|
329
329
|
}
|
|
330
330
|
]
|
|
331
331
|
},
|
|
@@ -335,7 +335,7 @@
|
|
|
335
335
|
"hashes": [
|
|
336
336
|
{
|
|
337
337
|
"alg": "SHA-256",
|
|
338
|
-
"content": "
|
|
338
|
+
"content": "f3e74e03e325569237340aace076e4e0507d12b48d47dcced7a836242ae13ac9"
|
|
339
339
|
}
|
|
340
340
|
]
|
|
341
341
|
},
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
"hashes": [
|
|
346
346
|
{
|
|
347
347
|
"alg": "SHA-256",
|
|
348
|
-
"content": "
|
|
348
|
+
"content": "300050992853dba6e5cdd4dc54e66d77a506ac3750d81ed2d3da560086830e99"
|
|
349
349
|
}
|
|
350
350
|
]
|
|
351
351
|
},
|
|
@@ -2595,7 +2595,7 @@
|
|
|
2595
2595
|
"hashes": [
|
|
2596
2596
|
{
|
|
2597
2597
|
"alg": "SHA-256",
|
|
2598
|
-
"content": "
|
|
2598
|
+
"content": "5476546b291c2df0a0a4e3afd7b64900ada0b7ab26d400ae6fb6985853466c72"
|
|
2599
2599
|
}
|
|
2600
2600
|
]
|
|
2601
2601
|
},
|
|
@@ -2605,7 +2605,7 @@
|
|
|
2605
2605
|
"hashes": [
|
|
2606
2606
|
{
|
|
2607
2607
|
"alg": "SHA-256",
|
|
2608
|
-
"content": "
|
|
2608
|
+
"content": "593859b38d79431517940bccf25f5e8ae079e142ac6cb66396b38421555a1d17"
|
|
2609
2609
|
}
|
|
2610
2610
|
]
|
|
2611
2611
|
},
|
|
@@ -2615,7 +2615,7 @@
|
|
|
2615
2615
|
"hashes": [
|
|
2616
2616
|
{
|
|
2617
2617
|
"alg": "SHA-256",
|
|
2618
|
-
"content": "
|
|
2618
|
+
"content": "a7264b451ffa98a04e54d23e83312abe3afe1f380b088e386841ea4325938596"
|
|
2619
2619
|
}
|
|
2620
2620
|
]
|
|
2621
2621
|
},
|
|
@@ -2625,7 +2625,7 @@
|
|
|
2625
2625
|
"hashes": [
|
|
2626
2626
|
{
|
|
2627
2627
|
"alg": "SHA-256",
|
|
2628
|
-
"content": "
|
|
2628
|
+
"content": "67861fde05a1927927ab1951738fbed427a2d016b22433603136041df21b3f0d"
|
|
2629
2629
|
}
|
|
2630
2630
|
]
|
|
2631
2631
|
},
|
|
@@ -3515,7 +3515,7 @@
|
|
|
3515
3515
|
"hashes": [
|
|
3516
3516
|
{
|
|
3517
3517
|
"alg": "SHA-256",
|
|
3518
|
-
"content": "
|
|
3518
|
+
"content": "a77bd4063914f6dc09487990423e64c8186c5bc48b5285867852c905653605f4"
|
|
3519
3519
|
}
|
|
3520
3520
|
]
|
|
3521
3521
|
},
|
|
@@ -3525,7 +3525,7 @@
|
|
|
3525
3525
|
"hashes": [
|
|
3526
3526
|
{
|
|
3527
3527
|
"alg": "SHA-256",
|
|
3528
|
-
"content": "
|
|
3528
|
+
"content": "6e414fbaee4a6ed940620fb44cb8c39a2fd945e32a966f8141f1923ec242bed3"
|
|
3529
3529
|
}
|
|
3530
3530
|
]
|
|
3531
3531
|
},
|
|
@@ -3535,7 +3535,7 @@
|
|
|
3535
3535
|
"hashes": [
|
|
3536
3536
|
{
|
|
3537
3537
|
"alg": "SHA-256",
|
|
3538
|
-
"content": "
|
|
3538
|
+
"content": "36dc04b58621a090dadaeefbd8d40e73196875f28a84f130f08bcad9b3ebcb53"
|
|
3539
3539
|
}
|
|
3540
3540
|
]
|
|
3541
3541
|
},
|
|
@@ -3545,7 +3545,7 @@
|
|
|
3545
3545
|
"hashes": [
|
|
3546
3546
|
{
|
|
3547
3547
|
"alg": "SHA-256",
|
|
3548
|
-
"content": "
|
|
3548
|
+
"content": "64c71e44dd9635b4480f0e64c27cf510c2859890b03f1a00ce555b438e365090"
|
|
3549
3549
|
}
|
|
3550
3550
|
]
|
|
3551
3551
|
},
|
|
@@ -3715,7 +3715,7 @@
|
|
|
3715
3715
|
"hashes": [
|
|
3716
3716
|
{
|
|
3717
3717
|
"alg": "SHA-256",
|
|
3718
|
-
"content": "
|
|
3718
|
+
"content": "60bd3d2d8ddc18535260e2b790ee25e6ddb77eab597248f05948987d7d5a39eb"
|
|
3719
3719
|
}
|
|
3720
3720
|
]
|
|
3721
3721
|
},
|
|
@@ -3725,7 +3725,7 @@
|
|
|
3725
3725
|
"hashes": [
|
|
3726
3726
|
{
|
|
3727
3727
|
"alg": "SHA-256",
|
|
3728
|
-
"content": "
|
|
3728
|
+
"content": "f8021977e02820666664922176bca714047fc544c8b466f3956aae854e53dd53"
|
|
3729
3729
|
}
|
|
3730
3730
|
]
|
|
3731
3731
|
},
|
|
@@ -3735,7 +3735,7 @@
|
|
|
3735
3735
|
"hashes": [
|
|
3736
3736
|
{
|
|
3737
3737
|
"alg": "SHA-256",
|
|
3738
|
-
"content": "
|
|
3738
|
+
"content": "76105d806137ef947694e1cb10b2e8360411bd873e74386e87d933c99f53bbb3"
|
|
3739
3739
|
}
|
|
3740
3740
|
]
|
|
3741
3741
|
},
|
|
@@ -3745,7 +3745,7 @@
|
|
|
3745
3745
|
"hashes": [
|
|
3746
3746
|
{
|
|
3747
3747
|
"alg": "SHA-256",
|
|
3748
|
-
"content": "
|
|
3748
|
+
"content": "acbb9920848e738a286eff1f45ebdd3a9f5e3b09fec879567072573df15d171b"
|
|
3749
3749
|
}
|
|
3750
3750
|
]
|
|
3751
3751
|
},
|
|
@@ -5445,7 +5445,7 @@
|
|
|
5445
5445
|
"hashes": [
|
|
5446
5446
|
{
|
|
5447
5447
|
"alg": "SHA-256",
|
|
5448
|
-
"content": "
|
|
5448
|
+
"content": "3b8f5dc584a26b53be9f1e68cb8fffaaf68decbe1cf4b634f78c8ec0f3725b3d"
|
|
5449
5449
|
}
|
|
5450
5450
|
]
|
|
5451
5451
|
},
|
|
@@ -5455,7 +5455,7 @@
|
|
|
5455
5455
|
"hashes": [
|
|
5456
5456
|
{
|
|
5457
5457
|
"alg": "SHA-256",
|
|
5458
|
-
"content": "
|
|
5458
|
+
"content": "8644f836c6236867d8f238cf48465506361647f11bc58b50134a7a862b7e711a"
|
|
5459
5459
|
}
|
|
5460
5460
|
]
|
|
5461
5461
|
},
|
|
@@ -5465,7 +5465,7 @@
|
|
|
5465
5465
|
"hashes": [
|
|
5466
5466
|
{
|
|
5467
5467
|
"alg": "SHA-256",
|
|
5468
|
-
"content": "
|
|
5468
|
+
"content": "3bafd2f8eb1c3305be28c42d59365dc58751d199b5e702b69c581c5df7b97560"
|
|
5469
5469
|
}
|
|
5470
5470
|
]
|
|
5471
5471
|
},
|
|
@@ -5755,7 +5755,7 @@
|
|
|
5755
5755
|
"hashes": [
|
|
5756
5756
|
{
|
|
5757
5757
|
"alg": "SHA-256",
|
|
5758
|
-
"content": "
|
|
5758
|
+
"content": "e2ee030cc763d5e909855239129aad5275aa42c9128458320759d321174fd697"
|
|
5759
5759
|
}
|
|
5760
5760
|
]
|
|
5761
5761
|
},
|
|
@@ -5765,7 +5765,7 @@
|
|
|
5765
5765
|
"hashes": [
|
|
5766
5766
|
{
|
|
5767
5767
|
"alg": "SHA-256",
|
|
5768
|
-
"content": "
|
|
5768
|
+
"content": "857485e66830cc7a31e80a9a1b86614c5a02ceb074e7b209dbe62f12059f3198"
|
|
5769
5769
|
}
|
|
5770
5770
|
]
|
|
5771
5771
|
},
|
|
@@ -5775,7 +5775,7 @@
|
|
|
5775
5775
|
"hashes": [
|
|
5776
5776
|
{
|
|
5777
5777
|
"alg": "SHA-256",
|
|
5778
|
-
"content": "
|
|
5778
|
+
"content": "9659576b23f1c2f47cf5f6f78857495e44c89fffa4fc2010f6ded703e126738d"
|
|
5779
5779
|
}
|
|
5780
5780
|
]
|
|
5781
5781
|
},
|
|
@@ -5785,7 +5785,7 @@
|
|
|
5785
5785
|
"hashes": [
|
|
5786
5786
|
{
|
|
5787
5787
|
"alg": "SHA-256",
|
|
5788
|
-
"content": "
|
|
5788
|
+
"content": "f51408fe56f037d19219a828a5adba28f2bbe05337c3042d4fa25888ab31101b"
|
|
5789
5789
|
}
|
|
5790
5790
|
]
|
|
5791
5791
|
},
|
|
@@ -6645,7 +6645,7 @@
|
|
|
6645
6645
|
"hashes": [
|
|
6646
6646
|
{
|
|
6647
6647
|
"alg": "SHA-256",
|
|
6648
|
-
"content": "
|
|
6648
|
+
"content": "8c504e223d4d78a29a24993078f4a90e771f56d6a62b310ab90b7ba44f1a6bd5"
|
|
6649
6649
|
}
|
|
6650
6650
|
]
|
|
6651
6651
|
},
|
|
@@ -6655,7 +6655,7 @@
|
|
|
6655
6655
|
"hashes": [
|
|
6656
6656
|
{
|
|
6657
6657
|
"alg": "SHA-256",
|
|
6658
|
-
"content": "
|
|
6658
|
+
"content": "6554f0bd2566ac10e6c841732210daa2412028e520ce49acf197451024161382"
|
|
6659
6659
|
}
|
|
6660
6660
|
]
|
|
6661
6661
|
},
|
|
@@ -6665,7 +6665,7 @@
|
|
|
6665
6665
|
"hashes": [
|
|
6666
6666
|
{
|
|
6667
6667
|
"alg": "SHA-256",
|
|
6668
|
-
"content": "
|
|
6668
|
+
"content": "23de503688790bbe679dae6c72b3c77faa43a14d4d0fbc54946c2e35cfd2d543"
|
|
6669
6669
|
}
|
|
6670
6670
|
]
|
|
6671
6671
|
},
|
|
@@ -6839,7 +6839,7 @@
|
|
|
6839
6839
|
},
|
|
6840
6840
|
{
|
|
6841
6841
|
"name": "dz:canonical-json-sha256-v2",
|
|
6842
|
-
"value": "
|
|
6842
|
+
"value": "273067dfe330063f7f449dea7480208e147a20eb7b554f921d1eb9ee33cb8ca4"
|
|
6843
6843
|
}
|
|
6844
6844
|
]
|
|
6845
6845
|
},
|
|
@@ -6859,7 +6859,7 @@
|
|
|
6859
6859
|
"hashes": [
|
|
6860
6860
|
{
|
|
6861
6861
|
"alg": "SHA-256",
|
|
6862
|
-
"content": "
|
|
6862
|
+
"content": "388fce3f17382373d5864dccc0c8685c1779d1101a490399ccbacb3bd6c1a2f5"
|
|
6863
6863
|
}
|
|
6864
6864
|
]
|
|
6865
6865
|
},
|
|
@@ -6919,7 +6919,7 @@
|
|
|
6919
6919
|
"hashes": [
|
|
6920
6920
|
{
|
|
6921
6921
|
"alg": "SHA-256",
|
|
6922
|
-
"content": "
|
|
6922
|
+
"content": "9391b6e0d9f6ef3f39662d4f3c89dbc173881584c6956ad5add2ebd54cdc85cf"
|
|
6923
6923
|
}
|
|
6924
6924
|
]
|
|
6925
6925
|
},
|
|
@@ -7499,7 +7499,7 @@
|
|
|
7499
7499
|
"hashes": [
|
|
7500
7500
|
{
|
|
7501
7501
|
"alg": "SHA-256",
|
|
7502
|
-
"content": "
|
|
7502
|
+
"content": "f248dbc9f0fc735dcfe11ae399c8f7c52b4053bb10e29234dc27c6c9581b7945"
|
|
7503
7503
|
}
|
|
7504
7504
|
]
|
|
7505
7505
|
},
|
|
@@ -7739,7 +7739,7 @@
|
|
|
7739
7739
|
"hashes": [
|
|
7740
7740
|
{
|
|
7741
7741
|
"alg": "SHA-256",
|
|
7742
|
-
"content": "
|
|
7742
|
+
"content": "0e777534524e5042bcdb8eef9c5f3d23c5fd0f94e2fe45a52623f3122a5dedef"
|
|
7743
7743
|
}
|
|
7744
7744
|
]
|
|
7745
7745
|
},
|
|
@@ -7789,7 +7789,7 @@
|
|
|
7789
7789
|
"hashes": [
|
|
7790
7790
|
{
|
|
7791
7791
|
"alg": "SHA-256",
|
|
7792
|
-
"content": "
|
|
7792
|
+
"content": "8143fae14dbfcf0e87bf148add74e16e0ee8c2f8064fd2bfb0f90fbf1a21889b"
|
|
7793
7793
|
}
|
|
7794
7794
|
]
|
|
7795
7795
|
},
|
|
@@ -8209,7 +8209,7 @@
|
|
|
8209
8209
|
"hashes": [
|
|
8210
8210
|
{
|
|
8211
8211
|
"alg": "SHA-256",
|
|
8212
|
-
"content": "
|
|
8212
|
+
"content": "0f44ef0d05c0ffabffe5e0f4fb4539b67fd72f62b42206d68bc98199ea718dcd"
|
|
8213
8213
|
}
|
|
8214
8214
|
]
|
|
8215
8215
|
},
|
|
@@ -8289,7 +8289,7 @@
|
|
|
8289
8289
|
"hashes": [
|
|
8290
8290
|
{
|
|
8291
8291
|
"alg": "SHA-256",
|
|
8292
|
-
"content": "
|
|
8292
|
+
"content": "1dac38f95d956aa315554f6d7559e141a7acbebdab8c39d7f9880c3eae0f1f28"
|
|
8293
8293
|
}
|
|
8294
8294
|
]
|
|
8295
8295
|
},
|
|
@@ -8519,7 +8519,7 @@
|
|
|
8519
8519
|
"hashes": [
|
|
8520
8520
|
{
|
|
8521
8521
|
"alg": "SHA-256",
|
|
8522
|
-
"content": "
|
|
8522
|
+
"content": "6607b712f768be25a140f224dc6c3c80d25e0e5144898fb819d488da7f0cd600"
|
|
8523
8523
|
}
|
|
8524
8524
|
]
|
|
8525
8525
|
},
|
package/src/agentdb-index.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @packageDocumentation
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { existsSync, mkdirSync, copyFileSync, realpathSync } from 'node:fs';
|
|
15
|
+
import { existsSync, mkdirSync, copyFileSync, realpathSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
|
|
16
16
|
import { join, dirname, resolve, relative, isAbsolute, basename } from 'node:path';
|
|
17
17
|
import { pathToFileURL } from 'node:url';
|
|
18
18
|
import { createRequire } from 'node:module';
|
|
@@ -23,7 +23,7 @@ import { applyReadonlyPragmas } from './sqlite-read-helpers.js';
|
|
|
23
23
|
import { rotatePreReindexSnapshotsUnlocked, type SnapshotRotationReport } from './agentdb-snapshot-rotation.js';
|
|
24
24
|
import { snapshotSqliteDatabase, restoreSqliteSnapshot, type SnapshotMethod, type SnapshotDbCtor } from './agentdb-snapshot.js';
|
|
25
25
|
import { withAgentdbSnapshotLock, writeReindexMarker, clearReindexMarker, markReindexMarkerRecoveryRequired, reindexMarkerPath, msFromBackupPath } from './agentdb-reindex-marker.js';
|
|
26
|
-
import { NamedLockTimeoutError } from './named-lock.js';
|
|
26
|
+
import { NamedLockTimeoutError, withNamedLockSync } from './named-lock.js';
|
|
27
27
|
import type { StoreLockOptions } from './store-lock.js';
|
|
28
28
|
// The backlog dedup embed form (PURE, zero-dep — no cycle): dz-backlog rows must be embedded in the
|
|
29
29
|
// SAME bounded form the dedup query uses, including through the reindex path.
|
|
@@ -48,10 +48,14 @@ export interface AgentdbRow {
|
|
|
48
48
|
readonly avgReward?: number;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/** Outcome of {@link indexPatternsToAgentdb}.
|
|
51
|
+
/** Outcome of {@link indexPatternsToAgentdb}. `generationBumped`/`generationReason` are present only
|
|
52
|
+
* when a store write actually happened (`indexed > 0`) — FR-4: a failed counter write NEVER fails
|
|
53
|
+
* the indexing call itself, it is only reported so a caller (`dz doctor`, telemetry) can see it. */
|
|
52
54
|
export interface AgentdbIndexResult {
|
|
53
55
|
readonly indexed: number;
|
|
54
56
|
readonly error?: string | undefined;
|
|
57
|
+
readonly generationBumped?: boolean;
|
|
58
|
+
readonly generationReason?: string;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
interface NativeDb {
|
|
@@ -135,6 +139,112 @@ export function ensureAgentdbSchema(projectRoot: string, dbPath?: string): { rea
|
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
|
|
142
|
+
/** `<dbFile>.generation` — a sidecar counter next to the store itself, so it travels with any copy
|
|
143
|
+
* of `.dz/agentdb.db` (backup, mirror sync) without a separate path to keep in sync. */
|
|
144
|
+
function generationFilePath(dbFile: string): string {
|
|
145
|
+
return `${dbFile}.generation`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** AM-5 (fix-round): the ONLY shape {@link readStoreGeneration} trusts — one or more ASCII digits,
|
|
149
|
+
* nothing else. `Number.parseInt` alone accepts a leading-numeric-with-trailing-junk string like
|
|
150
|
+
* `"12junk"` as `12`; that reads a corrupt sidecar as a plausible generation instead of degrading to
|
|
151
|
+
* the documented `0` compatibility floor. */
|
|
152
|
+
const STRICT_GENERATION = /^\d+$/;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* FR-2/FR-3 (`store-generation-counter`): the store's write-generation counter, read back. A
|
|
156
|
+
* missing file (a store that predates this feature, or one that has never been written through
|
|
157
|
+
* {@link bumpStoreGeneration}) reads as `0` — the compatibility floor {@link getOrOpenEngine}'s
|
|
158
|
+
* caller compares against, never an error. A corrupt/non-numeric file degrades the same way (best
|
|
159
|
+
* effort — a bad counter must never crash a read path), never a throw. AM-5: the content must match
|
|
160
|
+
* {@link STRICT_GENERATION} exactly — `Number.parseInt`'s leading-digits-only tolerance is NOT used
|
|
161
|
+
* to decide validity, only to convert an already-validated string.
|
|
162
|
+
*/
|
|
163
|
+
export function readStoreGeneration(projectRoot: string, dbPath?: string): number {
|
|
164
|
+
try {
|
|
165
|
+
const raw = readFileSync(generationFilePath(resolveAgentdbPath(projectRoot, dbPath)), 'utf8').trim();
|
|
166
|
+
if (!STRICT_GENERATION.test(raw)) return 0;
|
|
167
|
+
const n = Number.parseInt(raw, 10);
|
|
168
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
169
|
+
} catch {
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* FR-1 (`store-generation-counter`): monotonically advance the store's write-generation counter by
|
|
176
|
+
* one (absent file ⇒ starts at 1), atomically (tmp + rename — the same durability discipline every
|
|
177
|
+
* other sidecar file in this module uses; a reader can never observe a half-written counter) AND
|
|
178
|
+
* under mutual exclusion (AM-2, fix-round after Codex review). A bare read→compute→rename with no
|
|
179
|
+
* lock lets two concurrent writers both read the same current value and both publish `current+1` —
|
|
180
|
+
* one bump is lost — or lets a DELAYED writer overwrite a later value with an earlier one (the
|
|
181
|
+
* counter briefly goes backwards on disk). `withNamedLockSync` (`named-lock.ts`, the repo's
|
|
182
|
+
* advisory lock for a read-modify-write file store — `.claude/rules/cross-runtime-concurrency.md`)
|
|
183
|
+
* serializes the critical section; the counter is RE-READ from disk *inside* the lock (never trusted
|
|
184
|
+
* from before acquisition), so the sequence every process observes is strictly monotonic
|
|
185
|
+
* (`agentdb-reindex-marker.ts`'s `withAgentdbSnapshotLock` is the precedent this mirrors — a lock
|
|
186
|
+
* addressed by `dirname(dbFile)`, a pure function of the store's own directory, never of
|
|
187
|
+
* `process.cwd()`).
|
|
188
|
+
*
|
|
189
|
+
* NEVER throws (FR-4): a write failure (read-only `.dz`, a full disk, a permissions error, an
|
|
190
|
+
* unresolvable path, OR a lock that could not be acquired by its deadline) is reported honestly as
|
|
191
|
+
* `{ok:false, error}` so the caller can log it — the store write it accompanies must stay successful
|
|
192
|
+
* regardless, telemetry is never a gate. AM-4: {@link resolveAgentdbPath} itself now runs INSIDE this
|
|
193
|
+
* function's outer `try` — an unresolvable path can no longer throw OUT of `bumpStoreGeneration`
|
|
194
|
+
* either; "never throws" now covers the whole function, not just the file-write tail.
|
|
195
|
+
*/
|
|
196
|
+
/** Codex round-2 (NEW HIGH): most mutators discard the bump result, so a failed bump must be
|
|
197
|
+
* VISIBLE on its own — one stderr line, written by the helper itself. Telemetry never throws. */
|
|
198
|
+
function reportBumpFailure(error: string): { readonly ok: false; readonly error: string } {
|
|
199
|
+
try { process.stderr.write(`dz: store generation not bumped — ${error}\n`); } catch { /* telemetry never throws */ }
|
|
200
|
+
return { ok: false, error };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function bumpStoreGeneration(
|
|
204
|
+
projectRoot: string,
|
|
205
|
+
dbPath?: string,
|
|
206
|
+
): { readonly ok: true; readonly generation: number } | { readonly ok: false; readonly error: string } {
|
|
207
|
+
try {
|
|
208
|
+
const dbFile = resolveAgentdbPath(projectRoot, dbPath);
|
|
209
|
+
const genFile = generationFilePath(dbFile);
|
|
210
|
+
return withNamedLockSync(
|
|
211
|
+
dirname(dbFile),
|
|
212
|
+
'store-generation',
|
|
213
|
+
(): { readonly ok: true; readonly generation: number } | { readonly ok: false; readonly error: string } => {
|
|
214
|
+
// AM-2: re-read the CURRENT value from disk while holding the lock — a value observed before
|
|
215
|
+
// acquisition may already be stale, another holder may have advanced it in the meantime.
|
|
216
|
+
let current = readStoreGeneration(projectRoot, dbPath);
|
|
217
|
+
const tmp = `${genFile}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
218
|
+
try {
|
|
219
|
+
// Codex round-2 (AM-2 residual): a sidecar that EXISTS but is corrupt reads as 0 and would
|
|
220
|
+
// reset the counter to 1 — a value an engine-cache entry may already be keyed on. Floor a
|
|
221
|
+
// corrupt value at a wall-clock stamp instead: still monotonic (ms since epoch exceeds any
|
|
222
|
+
// count reached by bumping) and never colliding with an earlier generation. Absent file ⇒ 1.
|
|
223
|
+
if (current === 0 && existsSync(genFile)) {
|
|
224
|
+
const raw = readFileSync(genFile, 'utf8').trim();
|
|
225
|
+
if (raw !== '0' && !STRICT_GENERATION.test(raw)) current = Date.now();
|
|
226
|
+
}
|
|
227
|
+
const next = current + 1;
|
|
228
|
+
mkdirSync(dirname(genFile), { recursive: true });
|
|
229
|
+
writeFileSync(tmp, String(next), { encoding: 'utf8', flag: 'wx' });
|
|
230
|
+
renameSync(tmp, genFile);
|
|
231
|
+
return { ok: true, generation: next };
|
|
232
|
+
} catch (err) {
|
|
233
|
+
// Best-effort cleanup of a half-written temp file (e.g. rename failed after a successful
|
|
234
|
+
// write) so it never lingers as clutter — never lets a cleanup failure mask the real error.
|
|
235
|
+
try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best-effort only */ }
|
|
236
|
+
return reportBumpFailure(`store generation bump failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
// AM-2/FR-4: a lock that could not be acquired by its deadline (`NamedLockTimeoutError`) — and
|
|
242
|
+
// any other failure reaching this point (an unresolvable path, AM-4) — degrades to the same
|
|
243
|
+
// honest `{ok:false, error}` shape; it never throws into the store write it accompanies.
|
|
244
|
+
return reportBumpFailure(`store generation bump failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
138
248
|
/**
|
|
139
249
|
* Index `rows` into the shared AgentDB vector store. Returns `{indexed:0}` for an empty input and
|
|
140
250
|
* `{indexed:0, error}` when `agentdb`/`better-sqlite3` cannot be resolved from the project.
|
|
@@ -218,8 +328,19 @@ export async function indexPatternsToAgentdb(
|
|
|
218
328
|
return prepared.length;
|
|
219
329
|
});
|
|
220
330
|
const indexed = commit() as number;
|
|
331
|
+
// AM-3 (fix-round): bump the generation IMMEDIATELY after the commit — BEFORE
|
|
332
|
+
// `writeEmbedManifest` — not after it. The store already changed on disk the instant `commit()`
|
|
333
|
+
// returned; if the manifest write throws (a jammed manifest path, a full disk), the OLD order
|
|
334
|
+
// left the DB changed with no generation ever published — a stale-cache read would then serve
|
|
335
|
+
// an engine that never saw this write, with no signal anywhere that anything went wrong. Moving
|
|
336
|
+
// the bump here makes "the store changed" and "the generation reflects it" atomic in effect:
|
|
337
|
+
// whichever of the two calls below throws, the generation is already correct for the rows that
|
|
338
|
+
// are already on disk.
|
|
339
|
+
const bump = bumpStoreGeneration(projectRoot, opts.dbPath);
|
|
221
340
|
writeEmbedManifest(dbFile, currentEmbedManifest(model, guard.manifest.version, 'agentdb'));
|
|
222
|
-
return
|
|
341
|
+
return bump.ok
|
|
342
|
+
? { indexed, generationBumped: true }
|
|
343
|
+
: { indexed, generationBumped: false, generationReason: bump.error };
|
|
223
344
|
} finally {
|
|
224
345
|
db.close();
|
|
225
346
|
}
|
|
@@ -726,6 +847,12 @@ export async function importVectorsToAgentdb(
|
|
|
726
847
|
return imported;
|
|
727
848
|
});
|
|
728
849
|
const imported = commit();
|
|
850
|
+
// AM-1 (fix-round): `importVectorsToAgentdb` is the write-half of `dz vector import` — it
|
|
851
|
+
// changes `pattern_embeddings`/`reasoning_patterns` exactly like `indexPatternsToAgentdb`, so
|
|
852
|
+
// it must bump the SAME counter (the whole point of a single "did the store change" signal is
|
|
853
|
+
// that every writer feeds it, not just one). Ordered before `writeEmbedManifest`, same AM-3
|
|
854
|
+
// rationale: the rows are already on disk by the time `commit()` returns.
|
|
855
|
+
bumpStoreGeneration(projectRoot, opts.dbPath);
|
|
729
856
|
writeEmbedManifest(dbFile, currentEmbedManifest(model, guard.manifest.version, 'agentdb'));
|
|
730
857
|
return { imported };
|
|
731
858
|
} finally {
|
|
@@ -773,7 +900,14 @@ export function clearAgentdbQuarantine(
|
|
|
773
900
|
}
|
|
774
901
|
return cleared;
|
|
775
902
|
});
|
|
776
|
-
|
|
903
|
+
const cleared = tx();
|
|
904
|
+
// AM-1 (fix-round): a quarantine clear mutates `metadata` on rows the hook daemon reads
|
|
905
|
+
// straight from this mirror (see the doc comment above) — it changes what a query returns, so
|
|
906
|
+
// it must bump too, exactly like every other mutator. Only when something actually changed
|
|
907
|
+
// (`cleared > 0`) — same "no write, no bump" discipline as `indexPatternsToAgentdb`'s
|
|
908
|
+
// empty-rows case (AC-2).
|
|
909
|
+
if (cleared > 0) bumpStoreGeneration(projectRoot, opts.dbPath);
|
|
910
|
+
return { cleared };
|
|
777
911
|
} finally {
|
|
778
912
|
db.close();
|
|
779
913
|
}
|
|
@@ -829,7 +963,11 @@ export function deleteAgentdbByDzIds(
|
|
|
829
963
|
}
|
|
830
964
|
return deleted;
|
|
831
965
|
});
|
|
832
|
-
|
|
966
|
+
const deleted = tx();
|
|
967
|
+
// AM-1 (fix-round): a DELETE removes rows from a query's result set exactly as surely as an
|
|
968
|
+
// INSERT adds them — it must bump too. Only when rows actually left the store (`deleted > 0`).
|
|
969
|
+
if (deleted > 0) bumpStoreGeneration(projectRoot, opts.dbPath);
|
|
970
|
+
return { deleted };
|
|
833
971
|
} finally {
|
|
834
972
|
db.close();
|
|
835
973
|
}
|
|
@@ -874,7 +1012,13 @@ export function bumpAgentdbUses(
|
|
|
874
1012
|
}
|
|
875
1013
|
return bumped;
|
|
876
1014
|
});
|
|
877
|
-
|
|
1015
|
+
const bumped = tx();
|
|
1016
|
+
// AM-1 (fix-round): `uses`/`avg_reward` feed reward-weighted ranking — a change here is a real
|
|
1017
|
+
// store mutation, so it bumps the SAME counter (deliberately NOT named `bump` — that identifier
|
|
1018
|
+
// is this function's own return value; the counter helper is called by its full name below to
|
|
1019
|
+
// avoid the collision). Only when a row actually changed (`bumped > 0`).
|
|
1020
|
+
if (bumped > 0) bumpStoreGeneration(projectRoot, opts.dbPath);
|
|
1021
|
+
return { bumped };
|
|
878
1022
|
} finally {
|
|
879
1023
|
db.close();
|
|
880
1024
|
}
|
|
@@ -1157,6 +1301,13 @@ export async function reindexAgentdbRows(
|
|
|
1157
1301
|
if (!(err instanceof NamedLockTimeoutError)) throw err;
|
|
1158
1302
|
snapshots = { kept: [], removed: [], removedBytes: 0, keep: keepSnapshots, errors: [`lock busy: ${err.message}`] };
|
|
1159
1303
|
}
|
|
1304
|
+
// AM-1 (fix-round): `reindexAgentdbRows` is itself a mutator (the DELETE above rebuilds the
|
|
1305
|
+
// owned task types) — it must not rely SOLELY on `indexPatternsToAgentdb`'s own internal bump,
|
|
1306
|
+
// because that call is a no-op (and bumps nothing) when `rows` is empty, yet the DELETE it ran
|
|
1307
|
+
// just above unconditionally changed the store. Bumping again here when `rows` was non-empty
|
|
1308
|
+
// (the common case, already bumped once inside `indexPatternsToAgentdb`) is harmless — the
|
|
1309
|
+
// counter is a monotonic "did anything change" signal, not a per-operation tally.
|
|
1310
|
+
bumpStoreGeneration(projectRoot, opts.dbPath);
|
|
1160
1311
|
return {
|
|
1161
1312
|
reembedded: indexed.indexed,
|
|
1162
1313
|
model: model.model,
|