@dzhechkov/keysarium 1.7.9 → 1.7.10
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
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "README.md",
|
|
12
|
-
"sha256": "
|
|
12
|
+
"sha256": "10e8e4f470823f26d20a8cb4b5680dde97fd9654f9545b81de099405bd2dba32"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "README.ru.md",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"path": "package.json",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "fec2ebcb8a572ee95f1ac5b41264933d853a01d96b85a826e208a7bf4b49da1b"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "src/cli.js",
|
|
@@ -493,7 +493,7 @@
|
|
|
493
493
|
},
|
|
494
494
|
{
|
|
495
495
|
"path": "templates/.claude/skills/feature-adr/scripts/check-review-convergence.mjs",
|
|
496
|
-
"sha256": "
|
|
496
|
+
"sha256": "09c74f1441d9c42603fbb84402e6826cf96535d209be9802e0e4b2fea01dac7d"
|
|
497
497
|
},
|
|
498
498
|
{
|
|
499
499
|
"path": "templates/.claude/skills/feature-adr/scripts/markdown-masker.mjs",
|
|
@@ -1001,5 +1001,5 @@
|
|
|
1001
1001
|
}
|
|
1002
1002
|
]
|
|
1003
1003
|
},
|
|
1004
|
-
"signature": "
|
|
1004
|
+
"signature": "wfYnIS4MAw7T3m282RmpXCeIUVVAi+TDfNI4DmRaHJEy3h/gpJdtlv1TMTRz6dVfMA0fOcxZrJKWBXO+JFEcCg=="
|
|
1005
1005
|
}
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dzhechkov/keysarium
|
|
2
2
|
|
|
3
|
-
Current package version: `1.7.
|
|
3
|
+
Current package version: `1.7.10`. <!-- dz:version -->
|
|
4
4
|
|
|
5
5
|
Site: https://aicoding.space · Source: https://github.com/djd1m/dz-harness/tree/main/packages/@dzhechkov/keysarium
|
|
6
6
|
|
|
@@ -526,3 +526,7 @@ Confirmation, scope, family and budget gates still apply. No automatic repair lo
|
|
|
526
526
|
Repeated prepare retains all pending paths until verified closure. Actual fallback families bind before dispatch while prior owners remain required. Same-slug QE repair uses current own-reviewer evidence bound to the complete historical checkpoint; original findings/grades remain visible, and failed Confirmation still blocks delivery.
|
|
527
527
|
|
|
528
528
|
Before fallback, host prepare retains independently written pending receipt findings under the original owner even if that reviewer returned null. Unreconciled pending evidence pauses dispatch for originating-reviewer repair; it cannot be replaced by a clean fallback to obtain closure.
|
|
529
|
+
|
|
530
|
+
After a reviewed artifact is renamed or removed, convergence keeps its previously host-bound condition scope in the measured manifest; an absent old path has a null digest. Current discovered paths remain measured. Both prepare and evaluate validate the retained snapshot and reject unbound or unsafe scope paths. The original condition and originating reviewer verification remain required; a rename alone does not close a finding.
|
|
531
|
+
|
|
532
|
+
QE also measures the exact `.dz/guard.json` policy configuration when it is in the current Git index or selected review base. This includes newly staged and intent-to-add files; base-tracked deletions retain a null digest. Both declared targets and discovered changes use this rule. Untracked files absent from that base do not qualify through older history. Other `.dz/` runtime files remain excluded, and existing path, symlink, freshness and reviewer-ownership checks still apply.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/keysarium",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.10",
|
|
4
4
|
"scripts": {},
|
|
5
5
|
"description": "AI Case Research Toolkit for Claude Code — full 7-phase pipeline with feature-adr, dream cycles, background workers, multi-platform support, reward learning, and Cloud.ru AI Factory mapping (--ai-factory flag)",
|
|
6
6
|
"main": "src/cli.js",
|
package/sbom.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"hashes": [
|
|
26
26
|
{
|
|
27
27
|
"alg": "SHA-256",
|
|
28
|
-
"content": "
|
|
28
|
+
"content": "10e8e4f470823f26d20a8cb4b5680dde97fd9654f9545b81de099405bd2dba32"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
},
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"name": "dz:canonical-json-sha256-v2",
|
|
62
|
-
"value": "
|
|
62
|
+
"value": "fec2ebcb8a572ee95f1ac5b41264933d853a01d96b85a826e208a7bf4b49da1b"
|
|
63
63
|
}
|
|
64
64
|
]
|
|
65
65
|
},
|
|
@@ -1239,7 +1239,7 @@
|
|
|
1239
1239
|
"hashes": [
|
|
1240
1240
|
{
|
|
1241
1241
|
"alg": "SHA-256",
|
|
1242
|
-
"content": "
|
|
1242
|
+
"content": "09c74f1441d9c42603fbb84402e6826cf96535d209be9802e0e4b2fea01dac7d"
|
|
1243
1243
|
}
|
|
1244
1244
|
]
|
|
1245
1245
|
},
|
|
@@ -34,13 +34,25 @@ function reviewerEntry(r, roster) {
|
|
|
34
34
|
}
|
|
35
35
|
function result(phase, verdict, revision, reasons, unresolved = []) { return { schema: SCHEMA, phase, verdict, revision, unresolved, reasons } }
|
|
36
36
|
|
|
37
|
+
function artifactPath(rel) {
|
|
38
|
+
return text(rel) && !rel.startsWith('/') && !rel.includes('\\') && !rel.split('/').some(p => !p || p === '.' || p === '..') && !/[\x00-\x1f]/.test(rel)
|
|
39
|
+
}
|
|
40
|
+
function hostProblem(host) {
|
|
41
|
+
if (!dictionary(host, ['schema', 'phase', 'reviewers', 'snapshot', 'conditions', 'reviewSeen', 'rework', 'changedPaths']) || host.schema !== SCHEMA || !phases.includes(host.phase) || !array(host.reviewers, 16) || host.reviewers.length === 0) return 'host-lineage-invalid'
|
|
42
|
+
const owners = host.reviewers.map(r => r?.id)
|
|
43
|
+
if (new Set(owners).size !== owners.length || !host.reviewers.every(r => dictionary(r, ['id', 'family']) && text(r.id) && ['codex', 'claude', 'owner-exception'].includes(r.family))) return 'host-reviewers-invalid'
|
|
44
|
+
if (!dictionary(host.snapshot, ['nonce', 'revision', 'manifest']) || !text(host.snapshot.nonce) || !hash(host.snapshot.revision) || !array(host.conditions) || !host.conditions.every(c => condition(c, owners)) || new Set(host.conditions.map(c => c.id)).size !== host.conditions.length || typeof host.reviewSeen !== 'boolean' || typeof host.rework !== 'boolean' || !strings(host.changedPaths) || !host.changedPaths.every(artifactPath)) return 'host-snapshot-invalid'
|
|
45
|
+
const manifest = host.snapshot.manifest
|
|
46
|
+
if (!Array.isArray(manifest) || !manifest.every(p => dictionary(p, ['path', 'digest']) && artifactPath(p.path) && (p.digest === null || hash(p.digest))) || new Set(manifest.map(p => p.path)).size !== manifest.length || digest(JSON.stringify(manifest)) !== host.snapshot.revision) return 'host-manifest-invalid'
|
|
47
|
+
if (host.conditions.some(c => c.scope.some(path => !manifest.some(p => p.path === path)))) return 'host-condition-scope-unbound'
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
37
50
|
export function evaluateReviewConvergence(host, receipt, currentManifest) {
|
|
38
51
|
const phase = host?.phase
|
|
39
|
-
const refuse = reason => result(phase || 'qe', 'not-established', host?.snapshot?.revision || null, [reason], (host?.conditions
|
|
40
|
-
|
|
52
|
+
const refuse = reason => result(phase || 'qe', 'not-established', host?.snapshot?.revision || null, [reason], (array(host?.conditions) ? host.conditions : []).map(c => c?.id))
|
|
53
|
+
const problem = hostProblem(host)
|
|
54
|
+
if (problem) return refuse(problem)
|
|
41
55
|
const owners = host.reviewers.map(r => r.id)
|
|
42
|
-
if (new Set(owners).size !== owners.length || !host.reviewers.every(r => dictionary(r, ['id', 'family']) && text(r.id) && ['codex', 'claude', 'owner-exception'].includes(r.family))) return refuse('host-reviewers-invalid')
|
|
43
|
-
if (!dictionary(host.snapshot, ['nonce', 'revision', 'manifest']) || !text(host.snapshot.nonce) || !hash(host.snapshot.revision) || !array(host.conditions) || !host.conditions.every(c => condition(c, owners)) || typeof host.reviewSeen !== 'boolean' || typeof host.rework !== 'boolean' || !strings(host.changedPaths)) return refuse('host-snapshot-invalid')
|
|
44
56
|
if (!same(host.snapshot.manifest, currentManifest) || digest(JSON.stringify(currentManifest)) !== host.snapshot.revision) return refuse('artifact-manifest-changed')
|
|
45
57
|
if (!dictionary(receipt, ['schema', 'phase', 'nonce', 'revision', 'reviews', 'author']) || receipt.schema !== SCHEMA || receipt.phase !== phase || receipt.nonce !== host.snapshot.nonce || receipt.revision !== host.snapshot.revision) return refuse('receipt-phase-or-revision-invalid')
|
|
46
58
|
if (!array(receipt.reviews, 16) || receipt.reviews.length !== owners.length || new Set(receipt.reviews.map(r => r?.reviewer)).size !== owners.length) return refuse('reviewer-set-invalid')
|
|
@@ -96,12 +108,17 @@ export function evaluateReviewConvergence(host, receipt, currentManifest) {
|
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
function safePath(root, rel, allowMissing = true) {
|
|
99
|
-
if (!
|
|
111
|
+
if (!artifactPath(rel)) throw Error('unsafe-artifact-path')
|
|
100
112
|
let path = root
|
|
101
113
|
for (const part of rel.split('/')) {
|
|
102
114
|
path = resolve(path, part)
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
let entry
|
|
116
|
+
try { entry = lstatSync(path) } catch (error) {
|
|
117
|
+
if (error.code !== 'ENOENT') throw error
|
|
118
|
+
if (allowMissing) return resolve(root, rel)
|
|
119
|
+
throw Error('artifact-missing:' + rel)
|
|
120
|
+
}
|
|
121
|
+
if (entry.isSymbolicLink()) throw Error('symlink-artifact:' + rel)
|
|
105
122
|
}
|
|
106
123
|
return path
|
|
107
124
|
}
|
|
@@ -134,21 +151,30 @@ function pathsFor(repo, feature, phase) {
|
|
|
134
151
|
return match[1]
|
|
135
152
|
})
|
|
136
153
|
if (!planned.length) throw Error('target-block-empty')
|
|
137
|
-
if (planned.some(p => /^(features|\.dz|\.agentic-qe|roam)\//.test(p))) throw Error('circular-review-target')
|
|
138
|
-
paths.push(...planned)
|
|
139
|
-
// Additions/deletions are discovered by the host, not the author delta request.
|
|
140
154
|
const baseFile = resolve(feature, '.fa-state/base-ref')
|
|
141
155
|
const base = existsSync(baseFile) ? readFileSync(baseFile, 'utf8').trim() : 'HEAD'
|
|
142
156
|
if (!/^[A-Za-z0-9_./:-]+$/.test(base) || base.startsWith('-')) throw Error('base-ref-invalid')
|
|
143
|
-
|
|
157
|
+
const guard = '.dz/guard.json'
|
|
158
|
+
// Exact index membership includes staged additions/intent-to-add. The selected
|
|
159
|
+
// base proves deletions; descendants and unrelated history confer no authority.
|
|
160
|
+
const proof = [['ls-files', '--cached', '-z', '--', guard], ['ls-tree', '-r', '--name-only', '-z', base, '--', guard]].map(command =>
|
|
161
|
+
execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 }).split('\0').includes(guard))
|
|
162
|
+
const allowedGuard = p => p === guard && proof.some(Boolean)
|
|
163
|
+
if (planned.some(p => /^(features|\.dz|\.agentic-qe|roam)\//.test(p) && !allowedGuard(p))) throw Error('circular-review-target')
|
|
164
|
+
paths.push(...planned)
|
|
165
|
+
// Additions/deletions are discovered by the host, not the author delta request.
|
|
166
|
+
for (const command of [['diff', '--no-renames', '--name-only', '-z', base, '--'], ['ls-files', '--others', '--exclude-standard', '-z']]) {
|
|
144
167
|
const names = execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 }).split('\0').filter(Boolean)
|
|
145
|
-
paths.push(...names.filter(p => !p.startsWith('features/') && !p.startsWith('.dz/') && !p.startsWith('.agentic-qe/') && !p.startsWith('roam/') && p !== 'architecture/map.json'))
|
|
168
|
+
paths.push(...names.filter(p => allowedGuard(p) || !p.startsWith('features/') && !p.startsWith('.dz/') && !p.startsWith('.agentic-qe/') && !p.startsWith('roam/') && p !== 'architecture/map.json'))
|
|
146
169
|
}
|
|
147
170
|
}
|
|
148
171
|
return [...new Set(paths)].sort()
|
|
149
172
|
}
|
|
150
173
|
export function measureManifest(repo, feature, phase) {
|
|
151
|
-
return pathsFor(repo, feature, phase)
|
|
174
|
+
return measurePaths(repo, pathsFor(repo, feature, phase))
|
|
175
|
+
}
|
|
176
|
+
function measurePaths(repo, paths) {
|
|
177
|
+
return [...new Set(paths)].sort().map(path => {
|
|
152
178
|
const absolute = safePath(repo, path)
|
|
153
179
|
if (!existsSync(absolute)) return { path, digest: null }
|
|
154
180
|
if (!lstatSync(absolute).isFile()) throw Error('non-file-artifact:' + path)
|
|
@@ -243,17 +269,27 @@ function bindReviewers(host, requested, measured) {
|
|
|
243
269
|
export function runGate({ action, repo, feature, phase, reviewers }) {
|
|
244
270
|
if (!phases.includes(phase) || !['prepare', 'evaluate'].includes(action)) throw Error('invalid-command')
|
|
245
271
|
repo = realpathSync(repo); feature = resolve(repo, feature)
|
|
246
|
-
const current = measureManifest(repo, feature, phase)
|
|
247
272
|
const paths = checkpointPaths(repo, feature, phase)
|
|
248
|
-
|
|
273
|
+
const hostExists = existsSync(paths.host)
|
|
274
|
+
let host = hostExists ? readJson(paths.host) : null
|
|
275
|
+
if (hostExists) {
|
|
276
|
+
const problem = hostProblem(host)
|
|
277
|
+
if (problem) throw Error(problem)
|
|
278
|
+
if (host.phase !== phase) throw Error('host-lineage-conflict')
|
|
279
|
+
}
|
|
280
|
+
let pending = null
|
|
281
|
+
if (host && existsSync(paths.review)) {
|
|
282
|
+
try { pending = retainPendingReceipt(host, paths.review) } catch (error) { if (action === 'prepare') throw error }
|
|
283
|
+
}
|
|
284
|
+
const retained = pending?.next || host
|
|
285
|
+
// Only scopes bound by the validated prior snapshot can extend current discovery.
|
|
286
|
+
const current = measurePaths(repo, pathsFor(repo, feature, phase).concat((retained?.conditions || []).flatMap(c => c.scope)))
|
|
249
287
|
const measuredCheckpoint = phase === 'qe' ? qeCheckpoint(repo, feature) : null
|
|
250
288
|
if (action === 'prepare') {
|
|
251
289
|
if (!array(reviewers, 16) || !reviewers.length || !reviewers.every(r => dictionary(r, ['id', 'family']) && text(r.id) && ['codex', 'claude', 'owner-exception'].includes(r.family)) || new Set(reviewers.map(r => r.id)).size !== reviewers.length) throw Error('reviewers-invalid')
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
host = pending.next; writeJson(paths.host, host)
|
|
256
|
-
if (pending.problem) throw Error('pending-reviewer-receipt-needs-origin-review')
|
|
290
|
+
if (pending) {
|
|
291
|
+
host = pending.next
|
|
292
|
+
if (pending.problem) { writeJson(paths.host, host); throw Error('pending-reviewer-receipt-needs-origin-review') }
|
|
257
293
|
}
|
|
258
294
|
reviewers = bindReviewers(host, reviewers, measuredCheckpoint)
|
|
259
295
|
const revision = digest(JSON.stringify(current))
|
|
@@ -274,21 +310,14 @@ export function runGate({ action, repo, feature, phase, reviewers }) {
|
|
|
274
310
|
closure = reconcileCheckpoint(host, receipt, measuredCheckpoint, repo, feature)
|
|
275
311
|
if (!closure) verdict = result(phase, 'not-established', host.snapshot.revision, ['historical-checkpoint-own-verification-missing'])
|
|
276
312
|
}
|
|
277
|
-
// Preserve independently owned findings
|
|
278
|
-
//
|
|
313
|
+
// Preserve only fresh, independently owned findings bound by the prior snapshot.
|
|
314
|
+
// Stale or malformed receipts cannot originate measurement authority on refusal.
|
|
315
|
+
const preserved = new Map((pending?.next.conditions || host.conditions).map(c => [c.id, c]))
|
|
279
316
|
const owners = host.reviewers.map(r => r.id)
|
|
280
|
-
const preserved = new Map(host.conditions.map(c => [c.id, c]))
|
|
281
|
-
if (receipt?.schema === SCHEMA && receipt.phase === phase && array(receipt.reviews)) {
|
|
282
|
-
for (const r of receipt.reviews) if (owners.includes(r?.reviewer) && host.reviewers.find(p => p.id === r.reviewer)?.family === r.family && r.independent === true) {
|
|
283
|
-
for (const c of [...(array(r.conditions) ? r.conditions : []), ...(array(r.newRisks?.conditions) ? r.newRisks.conditions : [])]) {
|
|
284
|
-
if (condition(c, owners) && c.owner === r.reviewer && !preserved.has(c.id)) preserved.set(c.id, c)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
317
|
if (receipt?.phase === phase && receipt.nonce === host.snapshot.nonce && receipt.revision === host.snapshot.revision && risks(receipt.author?.newRisks, owners)) {
|
|
289
|
-
for (const c of receipt.author.newRisks.conditions) if (!preserved.has(c.id)) preserved.set(c.id, c)
|
|
318
|
+
for (const c of receipt.author.newRisks.conditions) if (!preserved.has(c.id) && c.scope.every(path => host.snapshot.manifest.some(p => p.path === path))) preserved.set(c.id, c)
|
|
290
319
|
}
|
|
291
|
-
writeJson(paths.host, { ...host, conditions: [...preserved.values()], reviewSeen:
|
|
320
|
+
writeJson(paths.host, { ...host, conditions: [...preserved.values()], reviewSeen: pending?.next.reviewSeen || host.reviewSeen, changedPaths: verdict.verdict === 'closed' ? [] : host.changedPaths })
|
|
292
321
|
if (closure && verdict.verdict === 'closed') return { ...verdict, checkpointClosure: closure }
|
|
293
322
|
return verdict
|
|
294
323
|
}
|