@dzhechkov/keysarium 1.7.11 → 1.7.13
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 +6 -6
- package/README.md +13 -1
- package/package.json +1 -1
- package/sbom.json +5 -5
- package/templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md +12 -3
- package/templates/.claude/skills/feature-adr/modules/08-qe.md +31 -3
- package/templates/.claude/skills/feature-adr/scripts/check-review-convergence.mjs +172 -11
package/.dz-manifest.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "README.md",
|
|
12
|
-
"sha256": "
|
|
12
|
+
"sha256": "399f8032d0723d371c63c4d94d351614f4a2e11865a6475ab3f1db5c02e45058"
|
|
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": "3ab5dc27c4025613b6d9d9c5932eff6ef31927190723f63a8fd32b68e462050b"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "src/cli.js",
|
|
@@ -373,7 +373,7 @@
|
|
|
373
373
|
},
|
|
374
374
|
{
|
|
375
375
|
"path": "templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md",
|
|
376
|
-
"sha256": "
|
|
376
|
+
"sha256": "798c04595c5b0f8c5688334413c744b6d1dda6f9ad9d4fab263c96dcc2c4187e"
|
|
377
377
|
},
|
|
378
378
|
{
|
|
379
379
|
"path": "templates/.claude/skills/feature-adr/modules/04-ddd.md",
|
|
@@ -393,7 +393,7 @@
|
|
|
393
393
|
},
|
|
394
394
|
{
|
|
395
395
|
"path": "templates/.claude/skills/feature-adr/modules/08-qe.md",
|
|
396
|
-
"sha256": "
|
|
396
|
+
"sha256": "e2fb5fb869005b05d13fcc52da7ded281bb6758250a1cdf695dace286029d791"
|
|
397
397
|
},
|
|
398
398
|
{
|
|
399
399
|
"path": "templates/.claude/skills/feature-adr/modules/09-fleet-qe.md",
|
|
@@ -497,7 +497,7 @@
|
|
|
497
497
|
},
|
|
498
498
|
{
|
|
499
499
|
"path": "templates/.claude/skills/feature-adr/scripts/check-review-convergence.mjs",
|
|
500
|
-
"sha256": "
|
|
500
|
+
"sha256": "6fdacc0ee6c1d2b00029da8d300a574c051895ee98a9c88c9509a39b9bdccc71"
|
|
501
501
|
},
|
|
502
502
|
{
|
|
503
503
|
"path": "templates/.claude/skills/feature-adr/scripts/markdown-masker.mjs",
|
|
@@ -1005,5 +1005,5 @@
|
|
|
1005
1005
|
}
|
|
1006
1006
|
]
|
|
1007
1007
|
},
|
|
1008
|
-
"signature": "
|
|
1008
|
+
"signature": "IuyuSO5ojzp0YmewWW4Kmtn854zfx7p7vZmpVXBpzSnV7G24mTKKMJk2QAv0GjtLp9kfiiWwy0WkecuW7nPdCQ=="
|
|
1009
1009
|
}
|
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# @dzhechkov/keysarium
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The bundled Feature ADR projection supports installed-CLI native QE history opt-in with a fixed three-cycle ceiling and explicit repair. Its canonical helper/guidance remain synchronized; compatible core/CLI capabilities are required, and historical bridge behavior remains available.
|
|
4
|
+
|
|
5
|
+
If bytes change before first evaluation, native `evaluate` retains valid old-snapshot receipt origins and conditions while reporting `not-established/native-repair-required`. Follow with explicit `begin-repair` and fresh independent verification of the new snapshot. Recovery preserves authority and the three-cycle ceiling; old evidence never closes changed bytes.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Current package version: `1.7.13`. <!-- dz:version -->
|
|
4
9
|
|
|
5
10
|
Site: https://aicoding.space · Source: https://github.com/djd1m/dz-harness/tree/main/packages/@dzhechkov/keysarium
|
|
6
11
|
|
|
@@ -34,6 +39,13 @@ Required missing or incomplete context refuses coding. This helper is separate f
|
|
|
34
39
|
> whether a lesson describes a method or a person, and says so: that judgement is the agent's, per
|
|
35
40
|
> the teach protocol. See `skills/goap-research-ed25519/SKILL.md`.
|
|
36
41
|
|
|
42
|
+
Feature-adr structural `implementationVerified:true` has phase-specific meaning: ideation verifies
|
|
43
|
+
corrected ADR, architecture and phase artifacts at the current revision/nonce, without claiming
|
|
44
|
+
production code or implementation tests exist; QE requires actual implementation and relevant tests.
|
|
45
|
+
False or missing structural verification still blocks. Receipt fields, boolean strength, originating
|
|
46
|
+
reviewer ownership, phase/nonce/manifest binding and legacy receipt compatibility remain unchanged.
|
|
47
|
+
The gate checks receipt consistency and freshness; independent reviewers judge the evidence.
|
|
48
|
+
|
|
37
49
|
## Growth engine (reverse-engineering-unicorn M5)
|
|
38
50
|
### What M5 produces, since 0.2.0 / 1.7.5 / 1.6.0
|
|
39
51
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/keysarium",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.13",
|
|
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": "399f8032d0723d371c63c4d94d351614f4a2e11865a6475ab3f1db5c02e45058"
|
|
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": "3ab5dc27c4025613b6d9d9c5932eff6ef31927190723f63a8fd32b68e462050b"
|
|
63
63
|
}
|
|
64
64
|
]
|
|
65
65
|
},
|
|
@@ -939,7 +939,7 @@
|
|
|
939
939
|
"hashes": [
|
|
940
940
|
{
|
|
941
941
|
"alg": "SHA-256",
|
|
942
|
-
"content": "
|
|
942
|
+
"content": "798c04595c5b0f8c5688334413c744b6d1dda6f9ad9d4fab263c96dcc2c4187e"
|
|
943
943
|
}
|
|
944
944
|
]
|
|
945
945
|
},
|
|
@@ -989,7 +989,7 @@
|
|
|
989
989
|
"hashes": [
|
|
990
990
|
{
|
|
991
991
|
"alg": "SHA-256",
|
|
992
|
-
"content": "
|
|
992
|
+
"content": "e2fb5fb869005b05d13fcc52da7ded281bb6758250a1cdf695dace286029d791"
|
|
993
993
|
}
|
|
994
994
|
]
|
|
995
995
|
},
|
|
@@ -1249,7 +1249,7 @@
|
|
|
1249
1249
|
"hashes": [
|
|
1250
1250
|
{
|
|
1251
1251
|
"alg": "SHA-256",
|
|
1252
|
-
"content": "
|
|
1252
|
+
"content": "6fdacc0ee6c1d2b00029da8d300a574c051895ee98a9c88c9509a39b9bdccc71"
|
|
1253
1253
|
}
|
|
1254
1254
|
]
|
|
1255
1255
|
},
|
|
@@ -91,6 +91,15 @@ on every pass. Reviewer verifications have closed fields
|
|
|
91
91
|
`{id, revision, classification, evidence, implementationVerified}`. Only the originating independent
|
|
92
92
|
role can verify its own condition at the current revision; author/lead/scribe assertions cannot.
|
|
93
93
|
|
|
94
|
+
`implementationVerified` is phase-specific for both condition verifications and
|
|
95
|
+
`deltaVerification`. In phase `ideation`, structural `implementationVerified:true` means the
|
|
96
|
+
originating independent reviewer verified the corrected ADR, architecture and phase artifacts at
|
|
97
|
+
the current revision/nonce; it does not claim production code or implementation tests exist. In
|
|
98
|
+
phase `qe`, structural `implementationVerified:true` requires independent verification of the
|
|
99
|
+
actual implementation and relevant tests at the current revision/nonce. False or missing structural
|
|
100
|
+
verification remains blocking in both phases. Field names, boolean strength, originating ownership,
|
|
101
|
+
phase/nonce/manifest binding and legacy receipt compatibility are unchanged.
|
|
102
|
+
|
|
94
103
|
Rework requires the actual author object
|
|
95
104
|
`{addressed, changedScope, classification, delta, evidence, newRisks}`. Addressed IDs name prior
|
|
96
105
|
conditions; changedScope includes ALL host-measured changes and affected dependencies. Delta/evidence
|
|
@@ -98,9 +107,9 @@ explain the actual fix. Both author and reviewer `newRisks` require explicit ass
|
|
|
98
107
|
conditions array, including an explicit none-identified assessment when empty. The reviewer must set
|
|
99
108
|
`authorAssessmentChecked:true` and supply independent
|
|
100
109
|
`deltaVerification:{classification,evidence,implementationVerified}`. Structural changes affect
|
|
101
|
-
behavior, contracts, safeguards, architecture or acceptance meaning and require
|
|
102
|
-
|
|
103
|
-
classification, so an author's wording label cannot weaken structural verification.
|
|
110
|
+
behavior, contracts, safeguards, architecture or acceptance meaning and require phase-specific
|
|
111
|
+
structural verification as defined above. Wording changes affect expression only; the independent
|
|
112
|
+
reviewer checks that classification, so an author's wording label cannot weaken structural verification.
|
|
104
113
|
|
|
105
114
|
Focused scope is outstanding conditions plus actual changes and affected dependencies. New serious
|
|
106
115
|
risks always expand that scope. Retain ALL conditions and serious primary/precision findings until
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Step 8: Quality Engineering
|
|
2
2
|
|
|
3
|
+
### Native history through the installed CLI
|
|
4
|
+
|
|
5
|
+
For an explicitly native review, use the actual installed producer before dispatching any formal QE role:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
dz qe-rounds --source native --action prepare --project "$REPO" --feature-dir "$FEATURE" --reviewers '[{"id":"qe-primary","family":"codex"}]' --json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Bind the actual selected families and use the returned full roster, nonce and revision in the existing QE receipt at `feature/.fa-state/review-convergence-qe-review.json`. An owner-approved family exception must use `owner-exception` and preserve its explicit approval. Call `dz qe-rounds --source native --action evaluate --project "$REPO" --feature-dir "$FEATURE" --json` after actual independent review; missing or incompatible installed core/helper capabilities are NOT ESTABLISHED. No project-local module search, helper override, automatic installation or fake bridge evidence is allowed.
|
|
12
|
+
|
|
13
|
+
Native history fixes the ceiling at three witnessed cycles, including partial and unresolved originating reviews. Multiple roles, fallback, repeated prepare/evaluate and restart share one cycle. Prepare may refresh artifacts before a review originates. After witnessing, a measured code/design change requires explicit `--action begin-repair` with the actual roster before another review; unchanged third-cycle completion may finish, while a fourth opening is refused. The ceiling applies to formal feature review as well as focused roles. Do not evade it by changing family, host, source or nonce. No-verdict attempts are reported separately. Native `--ceiling` is refused.
|
|
14
|
+
|
|
15
|
+
If bytes change after an independent receipt arrives but before its first evaluation, call `--action evaluate` to retain the receipt's valid origins and conditions against its admitted old snapshot. The result remains `not-established` with `native-repair-required`; it never closes current bytes or the historical checkpoint. Then call `--action begin-repair` with the actual roster and obtain fresh independent verification of the new snapshot, including the author delta and original conditions. A missing or invalid origin cannot witness a cycle or authorize repair. Keep the surviving receipt and native authority intact through recovery.
|
|
16
|
+
|
|
17
|
+
Omit `--action` for read-only native reporting; omitted `--source` retains the historical bridge reader. Locally bound native/bridge collisions refuse either selection with both locations rather than unioning or resetting. A reviewed legacy QE host cannot retrospectively initialize a zero history. Initial admission allows only the documented surviving-evidence matrix; parseable ideation state alone is permitted.
|
|
18
|
+
|
|
19
|
+
The helper runs at most four Git calls before locking (2s and 4 MiB per call), then rechecks bounded authoritative bytes, index, directory candidates and known artifacts in one short directory-lock transaction. Limits are 256 artifact paths, 2 MiB per artifact, 16 MiB aggregate artifacts, 256 KiB host/receipt/head, 2 MiB checkpoints, a separate 4 MiB index metadata fence, 8 MiB journal, 1024 events and 256 directory candidates. The 2s monotonic budget is checked before authoritative writes; lock stale/timeout are explicitly 30s/5s. Returned late I/O refuses, but synchronous I/O cannot be interrupted. This fenced observation does not serialize arbitrary external editors or prove reviewer authenticity or deletion of all writable evidence. Commit failures preserve paths and report possible partial writes; never delete authority or reconstruct zero to resume.
|
|
20
|
+
|
|
21
|
+
|
|
3
22
|
> Verify the implementation through testing, review, and validation.
|
|
4
23
|
|
|
5
24
|
## When
|
|
@@ -84,6 +103,15 @@ on every pass. Reviewer verifications have closed fields
|
|
|
84
103
|
`{id, revision, classification, evidence, implementationVerified}`. Only the originating independent
|
|
85
104
|
role can verify its own condition at the current revision; author/lead/scribe assertions cannot.
|
|
86
105
|
|
|
106
|
+
`implementationVerified` is phase-specific for both condition verifications and
|
|
107
|
+
`deltaVerification`. In phase `ideation`, structural `implementationVerified:true` means the
|
|
108
|
+
originating independent reviewer verified the corrected ADR, architecture and phase artifacts at
|
|
109
|
+
the current revision/nonce; it does not claim production code or implementation tests exist. In
|
|
110
|
+
phase `qe`, structural `implementationVerified:true` requires independent verification of the
|
|
111
|
+
actual implementation and relevant tests at the current revision/nonce. False or missing structural
|
|
112
|
+
verification remains blocking in both phases. Field names, boolean strength, originating ownership,
|
|
113
|
+
phase/nonce/manifest binding and legacy receipt compatibility are unchanged.
|
|
114
|
+
|
|
87
115
|
Rework requires the actual author object
|
|
88
116
|
`{addressed, changedScope, classification, delta, evidence, newRisks}`. Addressed IDs name prior
|
|
89
117
|
conditions; changedScope includes ALL host-measured changes and affected dependencies. Delta/evidence
|
|
@@ -91,9 +119,9 @@ explain the actual fix. Both author and reviewer `newRisks` require explicit ass
|
|
|
91
119
|
conditions array, including an explicit none-identified assessment when empty. The reviewer must set
|
|
92
120
|
`authorAssessmentChecked:true` and supply independent
|
|
93
121
|
`deltaVerification:{classification,evidence,implementationVerified}`. Structural changes affect
|
|
94
|
-
behavior, contracts, safeguards, architecture or acceptance meaning and require
|
|
95
|
-
|
|
96
|
-
classification, so an author's wording label cannot weaken structural verification.
|
|
122
|
+
behavior, contracts, safeguards, architecture or acceptance meaning and require phase-specific
|
|
123
|
+
structural verification as defined above. Wording changes affect expression only; the independent
|
|
124
|
+
reviewer checks that classification, so an author's wording label cannot weaken structural verification.
|
|
97
125
|
|
|
98
126
|
Focused scope is outstanding conditions plus actual changes and affected dependencies. New serious
|
|
99
127
|
risks always expand that scope. Retain ALL conditions and serious primary/precision findings until
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Host checkpoint lineage, not a reviewer-supplied prior array, owns historical conditions.
|
|
3
3
|
// This checks consistency/freshness; it does not authenticate models or judge their evidence.
|
|
4
|
+
// For structural condition/delta verification, implementationVerified:true is phase-specific:
|
|
5
|
+
// ideation = independently verified corrected ADR, architecture and phase artifacts at the
|
|
6
|
+
// current revision/nonce, without claiming production code or implementation tests exist;
|
|
7
|
+
// qe = independently verified actual implementation and relevant tests at that revision/nonce.
|
|
8
|
+
// False/missing remains blocking in both phases; field names, boolean strength, origin ownership,
|
|
9
|
+
// phase/nonce/manifest binding and legacy receipt compatibility are unchanged.
|
|
4
10
|
import { createHash, randomUUID } from 'node:crypto'
|
|
5
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync } from 'node:fs'
|
|
11
|
+
import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, opendirSync, readSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync } from 'node:fs'
|
|
6
12
|
import { dirname, relative, resolve, sep } from 'node:path'
|
|
7
13
|
import { execFileSync } from 'node:child_process'
|
|
8
14
|
import { fileURLToPath } from 'node:url'
|
|
@@ -47,7 +53,7 @@ function hostProblem(host) {
|
|
|
47
53
|
if (host.conditions.some(c => c.scope.some(path => !manifest.some(p => p.path === path)))) return 'host-condition-scope-unbound'
|
|
48
54
|
return null
|
|
49
55
|
}
|
|
50
|
-
export function evaluateReviewConvergence(host, receipt, currentManifest) {
|
|
56
|
+
export function evaluateReviewConvergence(host, receipt, currentManifest, originOnly = false) {
|
|
51
57
|
const phase = host?.phase
|
|
52
58
|
const refuse = reason => result(phase || 'qe', 'not-established', host?.snapshot?.revision || null, [reason], (array(host?.conditions) ? host.conditions : []).map(c => c?.id))
|
|
53
59
|
const problem = hostProblem(host)
|
|
@@ -55,7 +61,7 @@ export function evaluateReviewConvergence(host, receipt, currentManifest) {
|
|
|
55
61
|
const owners = host.reviewers.map(r => r.id)
|
|
56
62
|
if (!same(host.snapshot.manifest, currentManifest) || digest(JSON.stringify(currentManifest)) !== host.snapshot.revision) return refuse('artifact-manifest-changed')
|
|
57
63
|
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')
|
|
58
|
-
if (!array(receipt.reviews, 16) || receipt.reviews.length !== owners.length || new Set(receipt.reviews.map(r => r?.reviewer)).size !==
|
|
64
|
+
if (!array(receipt.reviews, 16) || (originOnly ? !receipt.reviews.length || receipt.reviews.length > owners.length : receipt.reviews.length !== owners.length) || new Set(receipt.reviews.map(r => r?.reviewer)).size !== receipt.reviews.length) return refuse('reviewer-set-invalid')
|
|
59
65
|
const all = new Map(host.conditions.map(c => [c.id, c]))
|
|
60
66
|
const verified = new Set()
|
|
61
67
|
const reasons = []
|
|
@@ -129,7 +135,7 @@ function writeJson(path, value) {
|
|
|
129
135
|
writeFileSync(temporary, JSON.stringify(value, null, 2) + '\n', { flag: 'wx' })
|
|
130
136
|
renameSync(temporary, path)
|
|
131
137
|
}
|
|
132
|
-
function pathsFor(repo, feature, phase) {
|
|
138
|
+
function pathsFor(repo, feature, phase, native = false) {
|
|
133
139
|
const prefix = relative(repo, feature).split(sep).join('/')
|
|
134
140
|
if (!prefix || prefix.startsWith('../')) throw Error('feature-must-be-inside-repo')
|
|
135
141
|
safePath(repo, prefix, false)
|
|
@@ -137,12 +143,13 @@ function pathsFor(repo, feature, phase) {
|
|
|
137
143
|
if (phase === 'qe') targets.push('06_implementation_plan.md', '03.5_ideation_report.md')
|
|
138
144
|
const adr = safePath(repo, prefix + '/03_adr')
|
|
139
145
|
if (existsSync(adr) && !lstatSync(adr).isDirectory()) throw Error('adr-not-directory')
|
|
140
|
-
const decisions = existsSync(adr) ? readdirSync(adr).filter(p => /^[0-9]{3}-.+\.md$/.test(p)).sort() : []
|
|
146
|
+
const decisions = existsSync(adr) ? (native ? nativeNames(adr) : readdirSync(adr)).filter(p => /^[0-9]{3}-.+\.md$/.test(p)).sort() : []
|
|
141
147
|
if (phase === 'ideation' && !decisions.length) throw Error('adr-missing')
|
|
142
148
|
const paths = targets.filter(p => existsSync(resolve(feature, p))).concat(decisions.map(p => '03_adr/' + p)).map(p => prefix + '/' + p)
|
|
143
149
|
for (const required of phase === 'ideation' ? ['01_requirements.md', '05_architecture.md'] : ['01_requirements.md', '06_implementation_plan.md']) if (!paths.includes(prefix + '/' + required)) throw Error('design-input-missing:' + required)
|
|
144
150
|
if (phase === 'qe') {
|
|
145
|
-
const
|
|
151
|
+
const planFile = safePath(repo, prefix + '/06_implementation_plan.md', false)
|
|
152
|
+
const plan = native ? new TextDecoder('utf-8', { fatal: true }).decode(nativeBytes(planFile, 2 * 1024 * 1024)) : readFileSync(planFile, 'utf8')
|
|
146
153
|
const block = plan.split(/^EXPECTED_CODE_TARGETS:\s*$/m)
|
|
147
154
|
if (block.length !== 2) throw Error('target-block-missing-or-duplicate')
|
|
148
155
|
const planned = block[1].split('\n').filter(line => line.trim()).map(line => {
|
|
@@ -152,19 +159,19 @@ function pathsFor(repo, feature, phase) {
|
|
|
152
159
|
})
|
|
153
160
|
if (!planned.length) throw Error('target-block-empty')
|
|
154
161
|
const baseFile = resolve(feature, '.fa-state/base-ref')
|
|
155
|
-
const base = existsSync(baseFile) ? readFileSync(baseFile, 'utf8').trim() : 'HEAD'
|
|
162
|
+
const base = existsSync(baseFile) ? (native ? nativeBytes(baseFile, 256 * 1024).toString('utf8') : readFileSync(baseFile, 'utf8')).trim() : 'HEAD'
|
|
156
163
|
if (!/^[A-Za-z0-9_./:-]+$/.test(base) || base.startsWith('-')) throw Error('base-ref-invalid')
|
|
157
164
|
const guard = '.dz/guard.json'
|
|
158
165
|
// Exact index membership includes staged additions/intent-to-add. The selected
|
|
159
166
|
// base proves deletions; descendants and unrelated history confer no authority.
|
|
160
167
|
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))
|
|
168
|
+
execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, ...(native ? { timeout: 2000, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } } : {}) }).split('\0').includes(guard))
|
|
162
169
|
const allowedGuard = p => p === guard && proof.some(Boolean)
|
|
163
170
|
if (planned.some(p => /^(features|\.dz|\.agentic-qe|roam)\//.test(p) && !allowedGuard(p))) throw Error('circular-review-target')
|
|
164
171
|
paths.push(...planned)
|
|
165
172
|
// Additions/deletions are discovered by the host, not the author delta request.
|
|
166
173
|
for (const command of [['diff', '--no-renames', '--name-only', '-z', base, '--'], ['ls-files', '--others', '--exclude-standard', '-z']]) {
|
|
167
|
-
const names = execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 }).split('\0').filter(Boolean)
|
|
174
|
+
const names = execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, ...(native ? { timeout: 2000, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } } : {}) }).split('\0').filter(Boolean)
|
|
168
175
|
paths.push(...names.filter(p => allowedGuard(p) || !p.startsWith('features/') && !p.startsWith('.dz/') && !p.startsWith('.agentic-qe/') && !p.startsWith('roam/') && p !== 'architecture/map.json'))
|
|
169
176
|
}
|
|
170
177
|
}
|
|
@@ -201,7 +208,7 @@ function qeCheckpoint(repo, feature) {
|
|
|
201
208
|
function seriousFindings(source) {
|
|
202
209
|
return (array(source?.gaps) ? source.gaps : []).filter(gap => [gap.sev, gap.severity, gap.priority].some(v => /(?:^|[^A-Z0-9])(BLOCKER|CRITICAL|HIGH|P0|P1)(?:$|[^A-Z0-9])/.test(String(v || '').toUpperCase())))
|
|
203
210
|
}
|
|
204
|
-
function reconcileCheckpoint(host, receipt, measured, repo, feature) {
|
|
211
|
+
function reconcileCheckpoint(host, receipt, measured, repo, feature, knownReport) {
|
|
205
212
|
if (!measured) return null
|
|
206
213
|
const stage = measured.checkpoint
|
|
207
214
|
for (const [base, source, family, missing] of [
|
|
@@ -222,7 +229,8 @@ function reconcileCheckpoint(host, receipt, measured, repo, feature) {
|
|
|
222
229
|
}
|
|
223
230
|
if (base === 'qe-precision' && missing) {
|
|
224
231
|
const file = safePath(repo, relative(repo, feature).split(sep).join('/') + '/08_qe_report.md', false)
|
|
225
|
-
const report = readFileSync(file)
|
|
232
|
+
const report = knownReport === undefined ? readFileSync(file) : knownReport
|
|
233
|
+
if (report === null) return null
|
|
226
234
|
if (digest(report) !== proof.reportDigest || !/## Primary QE pass/.test(report.toString()) || !/## Precision QE pass/.test(report.toString()) || !/Combined Step-8 grade:/.test(report.toString())) return null
|
|
227
235
|
}
|
|
228
236
|
}
|
|
@@ -322,6 +330,159 @@ export function runGate({ action, repo, feature, phase, reviewers }) {
|
|
|
322
330
|
return verdict
|
|
323
331
|
}
|
|
324
332
|
|
|
333
|
+
// Only the CLI supplies this capability. The default standalone runGate remains synchronous.
|
|
334
|
+
export const NATIVE_REVIEW_API_VERSION = 1
|
|
335
|
+
function nativeNames(dir) {
|
|
336
|
+
if (!existsSync(dir)) return []
|
|
337
|
+
const st = lstatSync(dir)
|
|
338
|
+
if (!st.isDirectory() || st.isSymbolicLink()) throw Error('native-directory-unsafe:' + dir)
|
|
339
|
+
const names = [], fd = opendirSync(dir)
|
|
340
|
+
try { let entry; while ((entry = fd.readSync())) { names.push(entry.name); if (names.length > 256) throw Error('native-directory-candidate-limit:' + dir) } }
|
|
341
|
+
finally { fd.closeSync() }
|
|
342
|
+
return names.sort()
|
|
343
|
+
}
|
|
344
|
+
function nativeBytes(file, limit) {
|
|
345
|
+
let st
|
|
346
|
+
try { st = lstatSync(file) } catch (error) { if (error.code === 'ENOENT') return null; throw error }
|
|
347
|
+
if (!st.isFile() || st.isSymbolicLink()) throw Error('native-file-unsafe:' + file)
|
|
348
|
+
if (st.size > limit) throw Error('native-file-limit:' + file)
|
|
349
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW)
|
|
350
|
+
try {
|
|
351
|
+
const opened = fstatSync(fd)
|
|
352
|
+
if (!opened.isFile() || opened.ino !== st.ino || opened.dev !== st.dev) throw Error('native-file-changed:' + file)
|
|
353
|
+
const bytes = Buffer.alloc(limit + 1); let n = 0
|
|
354
|
+
while (n <= limit) { const got = readSync(fd, bytes, n, bytes.length - n, null); if (!got) break; n += got }
|
|
355
|
+
if (n > limit) throw Error('native-file-limit:' + file)
|
|
356
|
+
return bytes.subarray(0, n)
|
|
357
|
+
} finally { closeSync(fd) }
|
|
358
|
+
}
|
|
359
|
+
function nativeManifest(repo, paths) {
|
|
360
|
+
if (new Set(paths).size > 256) throw Error('native-artifact-path-limit')
|
|
361
|
+
let total = 0
|
|
362
|
+
return [...new Set(paths)].sort().map(path => {
|
|
363
|
+
const bytes = nativeBytes(safePath(repo, path), 2 * 1024 * 1024)
|
|
364
|
+
total += bytes?.length || 0
|
|
365
|
+
if (total > 16 * 1024 * 1024) throw Error('native-artifact-aggregate-limit')
|
|
366
|
+
return { path, digest: bytes === null ? null : digest(bytes) }
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
function nativeReceipt(host, receipt, manifest) {
|
|
370
|
+
// Reuse the unchanged strong aggregate gate with just the originating subset roster.
|
|
371
|
+
// Missing reviewers can prevent closure without invalidating a structurally valid partial origin.
|
|
372
|
+
if (!host || host.phase !== 'qe' || !dictionary(receipt, ['schema', 'phase', 'nonce', 'revision', 'reviews', 'author']) || receipt.schema !== SCHEMA || receipt.phase !== 'qe' || receipt.nonce !== host.snapshot.nonce || receipt.revision !== host.snapshot.revision || !array(receipt.reviews, 16) || !receipt.reviews.length || new Set(receipt.reviews.map(r => r?.reviewer)).size !== receipt.reviews.length) return null
|
|
373
|
+
if (!receipt.reviews.every(r => reviewerEntry(r, host.reviewers))) return null
|
|
374
|
+
const checked = evaluateReviewConvergence(host, receipt, manifest, true)
|
|
375
|
+
if (checked.verdict === 'not-established') return null
|
|
376
|
+
return receipt.reviews.map(r => ({ reviewer: r.reviewer, family: r.family }))
|
|
377
|
+
}
|
|
378
|
+
export function runNativeReviewGate({ action, repo, feature, reviewers, api }) {
|
|
379
|
+
if (!api || api.version !== 1 || typeof api.transact !== 'function') throw Error('native-core-capability-not-established')
|
|
380
|
+
if (!['prepare', 'evaluate', 'begin-repair'].includes(action)) throw Error('native-action-invalid')
|
|
381
|
+
repo = realpathSync(repo); feature = realpathSync(resolve(repo, feature))
|
|
382
|
+
const prefix = relative(repo, feature).split(sep).join('/')
|
|
383
|
+
if (!prefix || prefix.startsWith('../') || api.projectRoot !== repo || api.featurePath !== feature) throw Error('native-feature-binding-invalid')
|
|
384
|
+
safePath(repo, prefix, false)
|
|
385
|
+
const stateDir = safePath(repo, prefix + '/.fa-state')
|
|
386
|
+
const hostFile = safePath(repo, prefix + '/.fa-state/review-convergence-qe-host.json')
|
|
387
|
+
const receiptFile = safePath(repo, prefix + '/.fa-state/review-convergence-qe-review.json')
|
|
388
|
+
const checkpointFile = safePath(repo, prefix + '/.fa-state/checkpoints.jsonl')
|
|
389
|
+
const baseFile = safePath(repo, prefix + '/.fa-state/base-ref')
|
|
390
|
+
// Index bytes fence staging changes; no Git is run by the locked callback.
|
|
391
|
+
let gitDir = resolve(repo, '.git')
|
|
392
|
+
const gitStat = lstatSync(gitDir)
|
|
393
|
+
if (gitStat.isFile()) {
|
|
394
|
+
const match = /^gitdir: ([^\r\n]+)\r?\n?$/.exec(nativeBytes(gitDir, 256 * 1024).toString('utf8'))
|
|
395
|
+
if (!match) throw Error('native-gitdir-invalid')
|
|
396
|
+
gitDir = realpathSync(resolve(repo, match[1]))
|
|
397
|
+
} else if (!gitStat.isDirectory() || gitStat.isSymbolicLink()) throw Error('native-gitdir-invalid')
|
|
398
|
+
const indexFile = resolve(gitDir, 'index')
|
|
399
|
+
const measuredFiles = [hostFile, receiptFile, checkpointFile, baseFile, indexFile,
|
|
400
|
+
safePath(repo, prefix + '/.fa-state/review-convergence-ideation-host.json'),
|
|
401
|
+
safePath(repo, prefix + '/.fa-state/review-convergence-ideation-review.json'),
|
|
402
|
+
safePath(repo, prefix + '/08_qe_report.md'),
|
|
403
|
+
...(gitStat.isFile() ? [resolve(repo, '.git')] : []), resolve(gitDir, 'HEAD')]
|
|
404
|
+
const limits = measuredFiles.map(f => f === indexFile ? 4 * 1024 * 1024 : f === checkpointFile ? 2 * 1024 * 1024 : 256 * 1024)
|
|
405
|
+
const observed = measuredFiles.map((f, i) => nativeBytes(f, limits[i]))
|
|
406
|
+
const json = (index) => observed[index] === null ? null : JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(observed[index]))
|
|
407
|
+
const host = json(0)
|
|
408
|
+
let receipt = null, receiptProblem = null
|
|
409
|
+
try { receipt = json(1) } catch { receiptProblem = 'reviewer-receipt-malformed' }
|
|
410
|
+
if (observed[0] !== null && (hostProblem(host) || host.phase !== 'qe')) throw Error(hostProblem(host) || 'native-host-phase-invalid')
|
|
411
|
+
if (observed[3] !== null && !/^[A-Za-z0-9_./:-]+$/.test(observed[3].toString('utf8').trim())) throw Error('native-base-ref-invalid')
|
|
412
|
+
const ideationHost = json(5), ideationReceipt = json(6)
|
|
413
|
+
if (observed[5] !== null && (hostProblem(ideationHost) || ideationHost.phase !== 'ideation')) throw Error('native-ideation-state-invalid')
|
|
414
|
+
if (observed[6] !== null) {
|
|
415
|
+
if (!ideationHost || !dictionary(ideationReceipt, ['schema', 'phase', 'nonce', 'revision', 'reviews', 'author']) || ideationReceipt.schema !== SCHEMA || ideationReceipt.phase !== 'ideation' || !text(ideationReceipt.nonce) || !hash(ideationReceipt.revision) || !array(ideationReceipt.reviews, 16) || !ideationReceipt.reviews.length || new Set(ideationReceipt.reviews.map(r => r?.reviewer)).size !== ideationReceipt.reviews.length || !ideationReceipt.reviews.every(r => reviewerEntry(r, ideationHost.reviewers) && (!Object.hasOwn(r, 'checkpointVerification') || checkpointProof(r.checkpointVerification)))) throw Error('native-ideation-receipt-invalid')
|
|
416
|
+
const owners = ideationHost.reviewers.map(r => r.id), author = ideationReceipt.author
|
|
417
|
+
if (author !== null && (!dictionary(author, ['addressed', 'changedScope', 'classification', 'delta', 'evidence', 'newRisks']) || !strings(author.addressed) || !strings(author.changedScope, true) || !kinds.includes(author.classification) || !text(author.delta) || !text(author.evidence) || !risks(author.newRisks, owners))) throw Error('native-ideation-author-invalid')
|
|
418
|
+
for (const r of ideationReceipt.reviews) {
|
|
419
|
+
if (author ? !dictionary(r.deltaVerification, ['classification', 'evidence', 'implementationVerified']) || !kinds.includes(r.deltaVerification.classification) || !text(r.deltaVerification.evidence) || typeof r.deltaVerification.implementationVerified !== 'boolean' : r.deltaVerification !== null) throw Error('native-ideation-delta-invalid')
|
|
420
|
+
if (!r.verifications.every(v => dictionary(v, ['id', 'revision', 'classification', 'evidence', 'implementationVerified']) && text(v.id) && hash(v.revision) && kinds.includes(v.classification) && text(v.evidence) && typeof v.implementationVerified === 'boolean') || new Set(r.verifications.map(v => v.id)).size !== r.verifications.length) throw Error('native-ideation-verification-invalid')
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
let checkpoint = null
|
|
424
|
+
if (observed[2] !== null) {
|
|
425
|
+
if (observed[2].length && observed[2][observed[2].length - 1] !== 10) throw Error('native-checkpoint-candidate-torn')
|
|
426
|
+
for (const line of new TextDecoder('utf-8', { fatal: true }).decode(observed[2]).split('\n').filter(Boolean)) {
|
|
427
|
+
const entry = JSON.parse(line)
|
|
428
|
+
if (!entry || typeof entry !== 'object' || typeof entry.stage !== 'string' || !entry.stage || typeof entry.inputHash !== 'string' || !entry.inputHash || !Object.hasOwn(entry, 'result') || entry.result === null) throw Error('native-checkpoint-candidate-invalid')
|
|
429
|
+
if (entry.stage === 'qe') checkpoint = { checkpointDigest: digest(JSON.stringify(entry.result)), checkpoint: entry.result }
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const directoryFence = () => ({ adr: nativeNames(safePath(repo, prefix + '/03_adr')), bridge: nativeNames(safePath(repo, prefix + '/.fa-state/qe-bridge')), fragments: nativeNames(stateDir).filter(n => /^native-qe-(history\.jsonl|head\.json)\..*\.tmp$/.test(n)) })
|
|
433
|
+
const membership = directoryFence()
|
|
434
|
+
const paths = pathsFor(repo, feature, 'qe', true).concat((host?.conditions || []).flatMap(c => c.scope))
|
|
435
|
+
const manifest = nativeManifest(repo, paths)
|
|
436
|
+
if (action !== 'evaluate' && (!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')
|
|
437
|
+
const observationDigest = digest(JSON.stringify({ files: observed.slice(1).map(b => b === null ? null : digest(b)), membership, manifest }))
|
|
438
|
+
const checkpointClosure = host && receipt && checkpoint ? reconcileCheckpoint(host, receipt, checkpoint, repo, feature, observed[7]) : null
|
|
439
|
+
return api.transact(action, active => {
|
|
440
|
+
if (realpathSync(repo) !== repo || realpathSync(feature) !== feature) throw Error('native-project-feature-observation-changed')
|
|
441
|
+
const currentGit = lstatSync(resolve(repo, '.git'))
|
|
442
|
+
if (currentGit.ino !== gitStat.ino || currentGit.dev !== gitStat.dev) throw Error('native-gitdir-observation-changed')
|
|
443
|
+
// Only descriptor/known-path work here; no discovery through runGate, Git, model or network.
|
|
444
|
+
measuredFiles.forEach((file, i) => { const bytes = nativeBytes(file, limits[i]); if ((bytes === null) !== (observed[i] === null) || bytes !== null && !bytes.equals(observed[i])) throw Error('native-observation-changed:' + file) })
|
|
445
|
+
const currentMembership = directoryFence()
|
|
446
|
+
if (currentMembership.bridge.some(n => /^(signoff-|failed-).*\.json$/.test(n))) throw Error('native-bridge-source-collision:' + stateDir + '/native-qe-history.jsonl:' + stateDir + '/qe-bridge')
|
|
447
|
+
if (!same(membership, currentMembership)) throw Error('native-candidate-membership-changed')
|
|
448
|
+
if (!same(manifest, nativeManifest(repo, paths))) throw Error('native-artifact-observation-changed')
|
|
449
|
+
if (membership.bridge.some(n => /^(signoff-|failed-).*\.json$/.test(n))) throw Error('native-bridge-source-collision:' + stateDir + '/native-qe-history.jsonl:' + stateDir + '/qe-bridge')
|
|
450
|
+
if (membership.fragments.length) throw Error('native-temporary-fragment')
|
|
451
|
+
if (!active && (observed[1] !== null || host && (host.reviewSeen || host.rework || host.conditions.length) || checkpoint)) throw Error('native-surviving-review-evidence-refuses-admission')
|
|
452
|
+
if (active && (!host || !same(host.snapshot, active.snapshot))) throw Error('native-host-admitted-snapshot-mismatch')
|
|
453
|
+
const revision = digest(JSON.stringify(manifest))
|
|
454
|
+
const priorOrigins = nativeReceipt(host, receipt, host?.snapshot.manifest)
|
|
455
|
+
if (action === 'prepare' && host && host.snapshot.revision !== revision && (active?.witnessed || priorOrigins)) throw Error('native-repair-required')
|
|
456
|
+
if (action === 'evaluate') {
|
|
457
|
+
if (!active || !host) throw Error('native-prepare-required')
|
|
458
|
+
const drift = !same(manifest, host.snapshot.manifest)
|
|
459
|
+
// A surviving receipt may witness admitted A after bytes become B. It cannot
|
|
460
|
+
// accept B: retain A and its origins, then require a separate begin-repair.
|
|
461
|
+
// Current bytes still participate in the observation digest and every fence.
|
|
462
|
+
const origins = priorOrigins
|
|
463
|
+
let verdict = drift ? result('qe', 'not-established', host.snapshot.revision, ['native-repair-required'])
|
|
464
|
+
: receipt === null ? result('qe', 'not-established', revision, [receiptProblem || 'reviewer-receipt-missing']) : evaluateReviewConvergence(host, receipt, manifest)
|
|
465
|
+
// Closure is deliberately stronger than originating partial/unresolved evidence.
|
|
466
|
+
if (verdict.verdict === 'closed' && checkpoint && !checkpointClosure) verdict = result('qe', 'not-established', revision, ['historical-checkpoint-own-verification-missing'])
|
|
467
|
+
const preserved = new Map(host.conditions.map(c => [c.id, c]))
|
|
468
|
+
if (origins) {
|
|
469
|
+
for (const r of receipt.reviews) for (const c of r.conditions.concat(r.newRisks.conditions)) preserved.set(c.id, c)
|
|
470
|
+
if (receipt.author) for (const c of receipt.author.newRisks.conditions) if (c.scope.every(path => host.snapshot.manifest.some(p => p.path === path))) preserved.set(c.id, c)
|
|
471
|
+
}
|
|
472
|
+
if (drift) verdict.unresolved = [...preserved.keys()]
|
|
473
|
+
if (verdict.verdict === 'closed' && checkpointClosure) verdict = { ...verdict, checkpointClosure }
|
|
474
|
+
const next = { ...host, conditions: [...preserved.values()], reviewSeen: host.reviewSeen || Boolean(origins), changedPaths: verdict.verdict === 'closed' ? [] : host.changedPaths }
|
|
475
|
+
return { host: next, snapshot: host.snapshot, origins: origins || [], receiptDigest: observed[1] === null ? null : digest(observed[1]), reason: origins ? null : verdict.reasons[0] || 'native-no-valid-origin', observationDigest, result: verdict, admission: false }
|
|
476
|
+
}
|
|
477
|
+
const bound = bindReviewers(host, reviewers, checkpoint)
|
|
478
|
+
const snapshot = active?.snapshot.revision === revision ? active.snapshot : host?.snapshot.revision === revision ? host.snapshot : { nonce: randomUUID(), revision, manifest }
|
|
479
|
+
const old = new Map((host?.snapshot.manifest || []).map(p => [p.path, p.digest])), current = new Map(manifest.map(p => [p.path, p.digest]))
|
|
480
|
+
const changedPaths = host && host.snapshot.revision !== revision ? [...new Set([...old.keys(), ...current.keys()])].filter(p => old.get(p) !== current.get(p)).sort() : host?.changedPaths || []
|
|
481
|
+
const next = { schema: SCHEMA, phase: 'qe', reviewers: bound, snapshot, conditions: host?.conditions || [], reviewSeen: host?.reviewSeen || false, rework: Boolean(host && (host.rework || host.reviewSeen && host.snapshot.revision !== revision)), changedPaths }
|
|
482
|
+
return { host: next, snapshot, origins: [], receiptDigest: null, reason: null, observationDigest, result: { ...result('qe', 'prepared', revision, []), nonce: snapshot.nonce, manifest, reviewers: bound, conditions: next.conditions, checkpointDigest: checkpoint?.checkpointDigest || null }, admission: !active }
|
|
483
|
+
})
|
|
484
|
+
}
|
|
485
|
+
|
|
325
486
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
326
487
|
let phase = 'qe'
|
|
327
488
|
try {
|