@veewo/gitnexus 1.4.11-rc.2 → 1.5.0-rc
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/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +55 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +190 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.test.js +13 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.d.ts +22 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.js +100 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.js +13 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +27 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +118 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +16 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +60 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +331 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +42 -0
- package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js +4 -4
- package/dist/benchmark/unity-lazy-context-sampler.d.ts +6 -0
- package/dist/benchmark/unity-lazy-context-sampler.js +49 -13
- package/dist/benchmark/unity-lazy-context-sampler.test.js +4 -0
- package/dist/cli/ai-context.js +6 -1
- package/dist/cli/eval-server.js +0 -3
- package/dist/cli/index.js +8 -0
- package/dist/cli/mcp.js +0 -3
- package/dist/cli/rule-lab.d.ts +42 -0
- package/dist/cli/rule-lab.js +157 -0
- package/dist/cli/rule-lab.test.d.ts +1 -0
- package/dist/cli/rule-lab.test.js +11 -0
- package/dist/cli/tool.d.ts +7 -1
- package/dist/cli/tool.js +6 -0
- package/dist/core/config/unity-config.d.ts +20 -0
- package/dist/core/config/unity-config.js +46 -0
- package/dist/core/graph/types.d.ts +1 -1
- package/dist/core/ingestion/pipeline.js +38 -13
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.d.ts +0 -2
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.js +26 -213
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +1 -1
- package/dist/core/ingestion/unity-resource-processor.js +87 -22
- package/dist/core/ingestion/unity-resource-processor.test.js +67 -2
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +11 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.js +179 -0
- package/dist/core/unity/options.d.ts +4 -0
- package/dist/core/unity/options.js +18 -0
- package/dist/core/unity/options.test.js +11 -1
- package/dist/core/unity/resolver.js +11 -1
- package/dist/core/unity/resolver.test.js +62 -0
- package/dist/core/unity/yaml-object-graph.js +1 -1
- package/dist/core/unity/yaml-object-graph.test.js +16 -0
- package/dist/mcp/local/derived-process-reader.d.ts +2 -0
- package/dist/mcp/local/derived-process-reader.js +15 -0
- package/dist/mcp/local/local-backend.d.ts +56 -0
- package/dist/mcp/local/local-backend.js +1003 -53
- package/dist/mcp/local/local-backend.unity-merge.test.js +1 -1
- package/dist/mcp/local/process-confidence.js +1 -1
- package/dist/mcp/local/process-evidence.d.ts +1 -0
- package/dist/mcp/local/process-evidence.js +22 -0
- package/dist/mcp/local/process-evidence.test.js +11 -1
- package/dist/mcp/local/process-ref.d.ts +24 -0
- package/dist/mcp/local/process-ref.js +33 -0
- package/dist/mcp/local/process-ref.test.d.ts +1 -0
- package/dist/mcp/local/process-ref.test.js +24 -0
- package/dist/mcp/local/runtime-chain-verify.d.ts +15 -1
- package/dist/mcp/local/runtime-chain-verify.js +191 -187
- package/dist/mcp/local/runtime-chain-verify.test.js +546 -19
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +63 -0
- package/dist/mcp/local/runtime-claim-rule-registry.js +308 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.d.ts +1 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +215 -0
- package/dist/mcp/local/runtime-claim.d.ts +38 -0
- package/dist/mcp/local/runtime-claim.js +54 -0
- package/dist/mcp/local/runtime-claim.test.d.ts +1 -0
- package/dist/mcp/local/runtime-claim.test.js +27 -0
- package/dist/mcp/local/unity-enrichment.d.ts +1 -0
- package/dist/mcp/local/unity-enrichment.js +1 -1
- package/dist/mcp/local/unity-evidence-view.d.ts +26 -0
- package/dist/mcp/local/unity-evidence-view.js +96 -0
- package/dist/mcp/local/unity-evidence-view.test.d.ts +1 -0
- package/dist/mcp/local/unity-evidence-view.test.js +39 -0
- package/dist/mcp/local/unity-lazy-hydrator.d.ts +2 -2
- package/dist/mcp/local/unity-lazy-hydrator.js +3 -3
- package/dist/mcp/local/unity-lazy-hydrator.test.js +4 -4
- package/dist/mcp/local/unity-parity-cache.js +2 -6
- package/dist/mcp/local/unity-parity-seed-loader.d.ts +1 -0
- package/dist/mcp/local/unity-parity-seed-loader.js +10 -16
- package/dist/mcp/local/unity-parity-seed-loader.test.js +3 -12
- package/dist/mcp/local/unity-runtime-hydration.d.ts +3 -2
- package/dist/mcp/local/unity-runtime-hydration.js +13 -16
- package/dist/mcp/local/unity-runtime-hydration.test.js +15 -1
- package/dist/mcp/resources.js +13 -0
- package/dist/mcp/tools.js +166 -13
- package/dist/rule-lab/analyze.d.ts +12 -0
- package/dist/rule-lab/analyze.js +90 -0
- package/dist/rule-lab/analyze.test.d.ts +1 -0
- package/dist/rule-lab/analyze.test.js +28 -0
- package/dist/rule-lab/compile.d.ts +5 -0
- package/dist/rule-lab/compile.js +51 -0
- package/dist/rule-lab/compiled-bundles.d.ts +30 -0
- package/dist/rule-lab/compiled-bundles.js +36 -0
- package/dist/rule-lab/curate.d.ts +32 -0
- package/dist/rule-lab/curate.js +134 -0
- package/dist/rule-lab/curate.test.d.ts +1 -0
- package/dist/rule-lab/curate.test.js +72 -0
- package/dist/rule-lab/discover.d.ts +13 -0
- package/dist/rule-lab/discover.js +74 -0
- package/dist/rule-lab/discover.test.d.ts +1 -0
- package/dist/rule-lab/discover.test.js +42 -0
- package/dist/rule-lab/paths.d.ts +21 -0
- package/dist/rule-lab/paths.js +37 -0
- package/dist/rule-lab/paths.test.d.ts +1 -0
- package/dist/rule-lab/paths.test.js +46 -0
- package/dist/rule-lab/promote.d.ts +26 -0
- package/dist/rule-lab/promote.js +314 -0
- package/dist/rule-lab/promote.test.d.ts +1 -0
- package/dist/rule-lab/promote.test.js +164 -0
- package/dist/rule-lab/regress.d.ts +60 -0
- package/dist/rule-lab/regress.js +122 -0
- package/dist/rule-lab/regress.test.d.ts +1 -0
- package/dist/rule-lab/regress.test.js +68 -0
- package/dist/rule-lab/review-pack.d.ts +31 -0
- package/dist/rule-lab/review-pack.js +125 -0
- package/dist/rule-lab/review-pack.test.d.ts +1 -0
- package/dist/rule-lab/review-pack.test.js +49 -0
- package/dist/rule-lab/types.d.ts +99 -0
- package/dist/rule-lab/types.js +1 -0
- package/package.json +1 -1
- package/skills/_shared/unity-hydration-contract.md +11 -0
- package/skills/_shared/unity-ui-trace-contract.md +33 -0
- package/skills/gitnexus-cli.md +11 -25
- package/skills/gitnexus-guide.md +2 -0
- package/skills/gitnexus-unity-rule-gen.md +318 -0
- package/dist/core/ingestion/unity-lifecycle-config.d.ts +0 -5
- package/dist/core/ingestion/unity-lifecycle-config.js +0 -25
- package/dist/mcp/local/unity-lazy-config.d.ts +0 -6
- package/dist/mcp/local/unity-lazy-config.js +0 -7
- package/dist/mcp/local/unity-lazy-config.test.js +0 -9
- package/dist/mcp/local/unity-process-confidence-config.d.ts +0 -1
- package/dist/mcp/local/unity-process-confidence-config.js +0 -4
- package/dist/mcp/local/unity-runtime-chain-verify-config.d.ts +0 -1
- package/dist/mcp/local/unity-runtime-chain-verify-config.js +0 -10
- /package/dist/{mcp/local/unity-lazy-config.test.d.ts → benchmark/u2-e2e/hydration-policy-repeatability-runner.test.d.ts} +0 -0
|
@@ -2,13 +2,15 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { describe, expect, it } from 'vitest';
|
|
5
|
-
import { verifyRuntimeChainOnDemand } from './runtime-chain-verify.js';
|
|
5
|
+
import { verifyRuntimeChainOnDemand, verifyRuntimeClaimOnDemand } from './runtime-chain-verify.js';
|
|
6
|
+
import { promoteCuratedRules } from '../../rule-lab/promote.js';
|
|
6
7
|
async function makeTempRepo() {
|
|
7
8
|
const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-verify-'));
|
|
8
9
|
await fs.mkdir(path.join(repoPath, 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb'), { recursive: true });
|
|
9
10
|
await fs.mkdir(path.join(repoPath, 'Assets/NEON/Graphs/PlayerGun/Gungraph_use'), { recursive: true });
|
|
10
11
|
await fs.mkdir(path.join(repoPath, 'Assets/NEON/Code/Game/Graph/Nodes/Reloads'), { recursive: true });
|
|
11
12
|
await fs.writeFile(path.join(repoPath, 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset'), 'gungraph: {guid: 69199acacbf8a7e489ad4aa872efcabd}\n');
|
|
13
|
+
await fs.writeFile(path.join(repoPath, 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset.meta'), 'guid: 69199acacbf8a7e489ad4aa872efcabd\n');
|
|
12
14
|
await fs.writeFile(path.join(repoPath, 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset'), 'ResultRPM: GunOutput.RPM\n');
|
|
13
15
|
await fs.writeFile(path.join(repoPath, 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/Reload.cs.meta'), 'guid: bd387039cacb475381a86f156b54bac2\n');
|
|
14
16
|
await fs.mkdir(path.join(repoPath, 'Assets/NEON/Code/Game/PowerUps'), { recursive: true });
|
|
@@ -18,39 +20,564 @@ async function makeTempRepo() {
|
|
|
18
20
|
await fs.writeFile(path.join(repoPath, 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs'), 'void GetValue() {}\n');
|
|
19
21
|
return repoPath;
|
|
20
22
|
}
|
|
23
|
+
async function fileExists(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(filePath);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function resolveWorkspaceRulesRoot() {
|
|
33
|
+
const candidates = [
|
|
34
|
+
path.resolve('.gitnexus/rules'),
|
|
35
|
+
path.resolve('../.gitnexus/rules'),
|
|
36
|
+
];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
if (await fileExists(path.join(candidate, 'catalog.json'))) {
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return candidates[0];
|
|
43
|
+
}
|
|
44
|
+
function makeExecuteParameterized(repoPath) {
|
|
45
|
+
return async (query, params) => {
|
|
46
|
+
const q = String(query || '');
|
|
47
|
+
const weaponPath = 'Assets/NEON/Code/Game/PowerUps/WeaponPowerUp.cs';
|
|
48
|
+
const gunGraphPath = 'Assets/NEON/Code/Game/Core/GunGraph.cs';
|
|
49
|
+
const reloadBasePath = 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs';
|
|
50
|
+
const symbolRows = {
|
|
51
|
+
WeaponPowerUp: {
|
|
52
|
+
id: `Class:${weaponPath}:WeaponPowerUp`,
|
|
53
|
+
name: 'WeaponPowerUp',
|
|
54
|
+
type: 'Class',
|
|
55
|
+
filePath: weaponPath,
|
|
56
|
+
startLine: 1,
|
|
57
|
+
},
|
|
58
|
+
GunGraph: {
|
|
59
|
+
id: `Class:${gunGraphPath}:GunGraph`,
|
|
60
|
+
name: 'GunGraph',
|
|
61
|
+
type: 'Class',
|
|
62
|
+
filePath: gunGraphPath,
|
|
63
|
+
startLine: 1,
|
|
64
|
+
},
|
|
65
|
+
ReloadBase: {
|
|
66
|
+
id: `Class:${reloadBasePath}:ReloadBase`,
|
|
67
|
+
name: 'ReloadBase',
|
|
68
|
+
type: 'Class',
|
|
69
|
+
filePath: reloadBasePath,
|
|
70
|
+
startLine: 1,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
const reloadBasePresent = await fileExists(path.join(repoPath, reloadBasePath));
|
|
74
|
+
if (q.includes('WHERE n.filePath = $filePath')) {
|
|
75
|
+
const filePath = String(params?.filePath || '');
|
|
76
|
+
const symbolName = String(params?.symbolName || '');
|
|
77
|
+
const candidates = Object.values(symbolRows).filter((row) => row.filePath === filePath && (row.name !== 'ReloadBase' || reloadBasePresent));
|
|
78
|
+
if (!symbolName)
|
|
79
|
+
return candidates;
|
|
80
|
+
return candidates.filter((row) => row.name === symbolName);
|
|
81
|
+
}
|
|
82
|
+
if (q.includes('WHERE n.name IN $symbolNames')) {
|
|
83
|
+
const names = Array.isArray(params?.symbolNames) ? params?.symbolNames : [];
|
|
84
|
+
return names
|
|
85
|
+
.map((name) => symbolRows[String(name)])
|
|
86
|
+
.filter((row) => row && (row.name !== 'ReloadBase' || reloadBasePresent))
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
if (q.includes("MATCH (s {id: $symbolId})-[r:CodeRelation {type: 'CALLS'}]->(t)")) {
|
|
90
|
+
const symbolId = String(params?.symbolId || '');
|
|
91
|
+
if (symbolId === symbolRows.ReloadBase.id && reloadBasePresent) {
|
|
92
|
+
return [{
|
|
93
|
+
sourceId: symbolRows.ReloadBase.id,
|
|
94
|
+
sourceName: 'ReloadBase',
|
|
95
|
+
sourceFilePath: reloadBasePath,
|
|
96
|
+
sourceStartLine: 1,
|
|
97
|
+
targetId: `Method:${reloadBasePath}:CheckReload`,
|
|
98
|
+
targetName: 'CheckReload',
|
|
99
|
+
targetFilePath: reloadBasePath,
|
|
100
|
+
targetStartLine: 12,
|
|
101
|
+
}];
|
|
102
|
+
}
|
|
103
|
+
if (symbolId === symbolRows.WeaponPowerUp.id) {
|
|
104
|
+
return [{
|
|
105
|
+
sourceId: symbolRows.WeaponPowerUp.id,
|
|
106
|
+
sourceName: 'WeaponPowerUp',
|
|
107
|
+
sourceFilePath: weaponPath,
|
|
108
|
+
sourceStartLine: 1,
|
|
109
|
+
targetId: `Method:${weaponPath}:Equip`,
|
|
110
|
+
targetName: 'Equip',
|
|
111
|
+
targetFilePath: weaponPath,
|
|
112
|
+
targetStartLine: 1,
|
|
113
|
+
}];
|
|
114
|
+
}
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
if (q.includes("MATCH (s)-[r:CodeRelation {type: 'CALLS'}]->(t {id: $symbolId})")) {
|
|
118
|
+
const symbolId = String(params?.symbolId || '');
|
|
119
|
+
if (symbolId === symbolRows.ReloadBase.id && reloadBasePresent) {
|
|
120
|
+
return [{
|
|
121
|
+
sourceId: `Method:${weaponPath}:Equip`,
|
|
122
|
+
sourceName: 'Equip',
|
|
123
|
+
sourceFilePath: weaponPath,
|
|
124
|
+
sourceStartLine: 1,
|
|
125
|
+
targetId: symbolRows.ReloadBase.id,
|
|
126
|
+
targetName: 'ReloadBase',
|
|
127
|
+
targetFilePath: reloadBasePath,
|
|
128
|
+
targetStartLine: 1,
|
|
129
|
+
}];
|
|
130
|
+
}
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
if (q.includes("MATCH (n {id: $symbolId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)")
|
|
134
|
+
&& q.includes("MATCH (m)-[r:CodeRelation {type: 'CALLS'}]->(t)")) {
|
|
135
|
+
const symbolId = String(params?.symbolId || '');
|
|
136
|
+
if (symbolId === symbolRows.ReloadBase.id && reloadBasePresent) {
|
|
137
|
+
return [{
|
|
138
|
+
sourceId: `Method:${reloadBasePath}:OnEquip`,
|
|
139
|
+
sourceName: 'OnEquip',
|
|
140
|
+
sourceFilePath: reloadBasePath,
|
|
141
|
+
sourceStartLine: 5,
|
|
142
|
+
targetId: `Method:${reloadBasePath}:CheckReload`,
|
|
143
|
+
targetName: 'CheckReload',
|
|
144
|
+
targetFilePath: reloadBasePath,
|
|
145
|
+
targetStartLine: 12,
|
|
146
|
+
}];
|
|
147
|
+
}
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
if (q.includes("MATCH (n {id: $symbolId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)")
|
|
151
|
+
&& q.includes("MATCH (s)-[r:CodeRelation {type: 'CALLS'}]->(m)")) {
|
|
152
|
+
const symbolId = String(params?.symbolId || '');
|
|
153
|
+
if (symbolId === symbolRows.ReloadBase.id && reloadBasePresent) {
|
|
154
|
+
return [{
|
|
155
|
+
sourceId: `Method:${weaponPath}:Equip`,
|
|
156
|
+
sourceName: 'Equip',
|
|
157
|
+
sourceFilePath: weaponPath,
|
|
158
|
+
sourceStartLine: 1,
|
|
159
|
+
targetId: `Method:${reloadBasePath}:OnEquip`,
|
|
160
|
+
targetName: 'OnEquip',
|
|
161
|
+
targetFilePath: reloadBasePath,
|
|
162
|
+
targetStartLine: 5,
|
|
163
|
+
}];
|
|
164
|
+
}
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
if (q.includes("r.reason STARTS WITH 'unity-rule-'") && q.includes('r.reason CONTAINS $ruleId')) {
|
|
168
|
+
const ruleId = String(params?.ruleId || '');
|
|
169
|
+
if (ruleId && reloadBasePresent) {
|
|
170
|
+
return [{
|
|
171
|
+
sourceName: 'unity-runtime-root',
|
|
172
|
+
sourceFilePath: '',
|
|
173
|
+
sourceStartLine: 1,
|
|
174
|
+
targetName: 'ReloadBase',
|
|
175
|
+
targetFilePath: reloadBasePath,
|
|
176
|
+
targetStartLine: 1,
|
|
177
|
+
reason: `unity-rule-resource-load:${ruleId}`,
|
|
178
|
+
}];
|
|
179
|
+
}
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
return [];
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function hasBalancedShellQuotes(command) {
|
|
186
|
+
let inSingle = false;
|
|
187
|
+
let inDouble = false;
|
|
188
|
+
let escaped = false;
|
|
189
|
+
for (const ch of String(command || '')) {
|
|
190
|
+
if (escaped) {
|
|
191
|
+
escaped = false;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (ch === '\\') {
|
|
195
|
+
escaped = true;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (!inDouble && ch === '\'') {
|
|
199
|
+
inSingle = !inSingle;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (!inSingle && ch === '"') {
|
|
203
|
+
inDouble = !inDouble;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return !inSingle && !inDouble && !escaped;
|
|
207
|
+
}
|
|
208
|
+
async function writeRules(repoPath, ruleYamlByFile, rootDirName = 'rules') {
|
|
209
|
+
const rulesRoot = path.join(repoPath, '.gitnexus', rootDirName);
|
|
210
|
+
await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
|
|
211
|
+
const entries = Object.entries(ruleYamlByFile).map(([file, content]) => {
|
|
212
|
+
const id = String(content.match(/^id:\s*(.+)$/m)?.[1] || '').trim();
|
|
213
|
+
const version = String(content.match(/^version:\s*(.+)$/m)?.[1] || '').trim();
|
|
214
|
+
return { id, version, file };
|
|
215
|
+
});
|
|
216
|
+
await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({ rules: entries }, null, 2), 'utf-8');
|
|
217
|
+
for (const [file, content] of Object.entries(ruleYamlByFile)) {
|
|
218
|
+
await fs.writeFile(path.join(rulesRoot, file), content, 'utf-8');
|
|
219
|
+
}
|
|
220
|
+
return rulesRoot;
|
|
221
|
+
}
|
|
21
222
|
describe('runtime chain verify', () => {
|
|
22
|
-
it('
|
|
223
|
+
it('does not run reload fallback when no rule is matched', async () => {
|
|
23
224
|
const repoPath = await makeTempRepo();
|
|
24
225
|
const out = await verifyRuntimeChainOnDemand({
|
|
25
226
|
repoPath,
|
|
26
227
|
queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
27
|
-
executeParameterized:
|
|
28
|
-
if (String(params?.filePathPattern || '').includes('WeaponPowerUp.cs')) {
|
|
29
|
-
return [{ filePath: 'Assets/NEON/Code/Game/PowerUps/WeaponPowerUp.cs', startLine: 1 }];
|
|
30
|
-
}
|
|
31
|
-
if (String(params?.filePathPattern || '').includes('GunGraph')) {
|
|
32
|
-
return [{ filePath: 'Assets/NEON/Code/Game/Core/GunGraph.cs', startLine: 1 }];
|
|
33
|
-
}
|
|
34
|
-
if (String(params?.filePathPattern || '').includes('ReloadBase.cs')) {
|
|
35
|
-
return [{ filePath: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs', startLine: 1 }];
|
|
36
|
-
}
|
|
37
|
-
return [];
|
|
38
|
-
},
|
|
228
|
+
executeParameterized: makeExecuteParameterized(repoPath),
|
|
39
229
|
resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
|
|
40
230
|
});
|
|
41
|
-
expect(out
|
|
42
|
-
|
|
43
|
-
const loader = out?.hops.find((hop) => hop.hop_type === 'code_loader');
|
|
44
|
-
expect(loader?.snippet || '').toMatch(/CurGunGraph\s*=/i);
|
|
231
|
+
expect(out).toBeUndefined();
|
|
232
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
45
233
|
});
|
|
46
|
-
it('
|
|
234
|
+
it('runtime chain gaps are actionable under matched rule execution', async () => {
|
|
47
235
|
const out = await verifyRuntimeChainOnDemand({
|
|
48
236
|
repoPath: await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-gaps-')),
|
|
49
237
|
queryText: 'Reload',
|
|
50
238
|
executeParameterized: async () => [],
|
|
51
239
|
resourceBindings: [],
|
|
240
|
+
rule: {
|
|
241
|
+
id: 'demo.reload.strict.v2',
|
|
242
|
+
version: '2.0.0',
|
|
243
|
+
trigger_family: 'reload',
|
|
244
|
+
resource_types: ['asset'],
|
|
245
|
+
host_base_type: ['ReloadBase'],
|
|
246
|
+
required_hops: ['resource', 'guid_map', 'code_loader', 'code_runtime'],
|
|
247
|
+
guarantees: ['strict_chain_closed'],
|
|
248
|
+
non_guarantees: ['strict_no_runtime_execution'],
|
|
249
|
+
next_action: 'node strict',
|
|
250
|
+
file_path: '.gitnexus/rules/approved/demo.reload.strict.v2.yaml',
|
|
251
|
+
},
|
|
252
|
+
requiredHops: ['resource', 'guid_map', 'code_loader', 'code_runtime'],
|
|
52
253
|
});
|
|
53
254
|
expect(out?.gaps.length).toBeGreaterThan(0);
|
|
54
255
|
expect(out?.gaps.every((gap) => !!gap.next_command)).toBe(true);
|
|
55
256
|
});
|
|
257
|
+
it('accepts seed-to-mapped resource equivalence for resource hop verification', async () => {
|
|
258
|
+
const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-mapped-resource-'));
|
|
259
|
+
const out = await verifyRuntimeChainOnDemand({
|
|
260
|
+
repoPath,
|
|
261
|
+
queryText: 'EnergyByAttackCount Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/0_初始武器/1_weapon_0_james_new.asset',
|
|
262
|
+
resourceSeedPath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/0_初始武器/1_weapon_0_james_new.asset',
|
|
263
|
+
mappedSeedTargets: ['Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_0_james1.asset'],
|
|
264
|
+
executeParameterized: async () => [],
|
|
265
|
+
resourceBindings: [{ resourcePath: 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_0_james1.asset' }],
|
|
266
|
+
rule: {
|
|
267
|
+
id: 'demo.energy.seed-map.v1',
|
|
268
|
+
version: '1.0.0',
|
|
269
|
+
trigger_family: 'energy',
|
|
270
|
+
resource_types: ['asset'],
|
|
271
|
+
host_base_type: ['GunGraphNode'],
|
|
272
|
+
required_hops: ['resource'],
|
|
273
|
+
guarantees: ['seed_mapped_resource_is_accepted'],
|
|
274
|
+
non_guarantees: ['does_not_verify_full_runtime_order'],
|
|
275
|
+
next_action: 'node mapped-resource',
|
|
276
|
+
file_path: '.gitnexus/rules/approved/demo.energy.seed-map.v1.yaml',
|
|
277
|
+
},
|
|
278
|
+
requiredHops: ['resource'],
|
|
279
|
+
});
|
|
280
|
+
expect(out?.status).toBe('verified_full');
|
|
281
|
+
expect(out?.gaps.length).toBe(0);
|
|
282
|
+
expect(out?.hops[0]?.note || '').toContain('mapped resource equivalence');
|
|
283
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
284
|
+
});
|
|
285
|
+
it('phase2 runtime claim returns explicit rule_not_matched', async () => {
|
|
286
|
+
const workspaceRulesRoot = await resolveWorkspaceRulesRoot();
|
|
287
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
288
|
+
repoPath: path.resolve('.'),
|
|
289
|
+
queryText: 'CompletelyUnrelatedChain',
|
|
290
|
+
executeParameterized: async () => [],
|
|
291
|
+
resourceBindings: [],
|
|
292
|
+
rulesRoot: workspaceRulesRoot,
|
|
293
|
+
});
|
|
294
|
+
expect(out.status).toBe('failed');
|
|
295
|
+
expect(out.reason).toBe('rule_not_matched');
|
|
296
|
+
expect(out.next_action).toBeTruthy();
|
|
297
|
+
});
|
|
298
|
+
it('phase2 runtime claim maps missing catalog to rule_not_matched', async () => {
|
|
299
|
+
const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-missing-catalog-'));
|
|
300
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
301
|
+
repoPath,
|
|
302
|
+
queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
303
|
+
executeParameterized: async () => [],
|
|
304
|
+
resourceBindings: [],
|
|
305
|
+
rulesRoot: path.join(repoPath, '.gitnexus', 'rules'),
|
|
306
|
+
});
|
|
307
|
+
expect(out.status).toBe('failed');
|
|
308
|
+
expect(out.reason).toBe('rule_not_matched');
|
|
309
|
+
expect(out.next_action).toBeTruthy();
|
|
310
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
311
|
+
});
|
|
312
|
+
it('phase2 runtime claim maps missing rule file to rule_not_matched', async () => {
|
|
313
|
+
const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-missing-rule-file-'));
|
|
314
|
+
const rulesRoot = path.join(repoPath, '.gitnexus', 'rules');
|
|
315
|
+
await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
|
|
316
|
+
await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({
|
|
317
|
+
rules: [{ id: 'demo.reload.rule.v1', version: '1.0.0', file: 'approved/demo.reload.rule.v1.yaml' }],
|
|
318
|
+
}), 'utf-8');
|
|
319
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
320
|
+
repoPath,
|
|
321
|
+
queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
322
|
+
executeParameterized: async () => [],
|
|
323
|
+
resourceBindings: [],
|
|
324
|
+
rulesRoot,
|
|
325
|
+
});
|
|
326
|
+
expect(out.status).toBe('failed');
|
|
327
|
+
expect(out.reason).toBe('rule_not_matched');
|
|
328
|
+
expect(out.next_action).toBeTruthy();
|
|
329
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
330
|
+
});
|
|
331
|
+
it('phase2 runtime claim uses bootstrap reload rule metadata', async () => {
|
|
332
|
+
const repoPath = await makeTempRepo();
|
|
333
|
+
const workspaceRulesRoot = await resolveWorkspaceRulesRoot();
|
|
334
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
335
|
+
repoPath,
|
|
336
|
+
queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
337
|
+
executeParameterized: makeExecuteParameterized(repoPath),
|
|
338
|
+
resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
|
|
339
|
+
rulesRoot: workspaceRulesRoot,
|
|
340
|
+
});
|
|
341
|
+
expect(out.rule_id).toBe('unity.gungraph.reload.output-getvalue.v1');
|
|
342
|
+
expect(out.rule_version).toBe('1.0.0');
|
|
343
|
+
});
|
|
344
|
+
it('phase2 next_action remains shell-parsable when unmatched', async () => {
|
|
345
|
+
const workspaceRulesRoot = await resolveWorkspaceRulesRoot();
|
|
346
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
347
|
+
repoPath: path.resolve('.'),
|
|
348
|
+
queryText: 'CompletelyUnrelatedChain',
|
|
349
|
+
executeParameterized: async () => [],
|
|
350
|
+
resourceBindings: [],
|
|
351
|
+
rulesRoot: workspaceRulesRoot,
|
|
352
|
+
});
|
|
353
|
+
expect(out.reason).toBe('rule_not_matched');
|
|
354
|
+
expect(typeof out.next_action).toBe('string');
|
|
355
|
+
expect(hasBalancedShellQuotes(String(out.next_action || ''))).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
it('phase2 runtime claim required_hops are rule-driven', async () => {
|
|
358
|
+
const repoPath = await makeTempRepo();
|
|
359
|
+
const executeParameterized = makeExecuteParameterized(repoPath);
|
|
360
|
+
const strictExecuteParameterized = async (query, params) => {
|
|
361
|
+
const q = String(query || '');
|
|
362
|
+
if (q.includes("MATCH (n {id: $symbolId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)")) {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
return executeParameterized(query, params);
|
|
366
|
+
};
|
|
367
|
+
const strictRulesRoot = await writeRules(repoPath, {
|
|
368
|
+
'approved/demo.reload.strict.v1.yaml': [
|
|
369
|
+
'id: demo.reload.strict.v1',
|
|
370
|
+
'version: 1.0.0',
|
|
371
|
+
'trigger_family: reload',
|
|
372
|
+
'resource_types:',
|
|
373
|
+
' - asset',
|
|
374
|
+
'host_base_type:',
|
|
375
|
+
' - ReloadBase',
|
|
376
|
+
'required_hops:',
|
|
377
|
+
' - resource',
|
|
378
|
+
' - guid_map',
|
|
379
|
+
' - code_loader',
|
|
380
|
+
' - code_runtime',
|
|
381
|
+
'guarantees:',
|
|
382
|
+
' - strict_chain_closed',
|
|
383
|
+
'non_guarantees:',
|
|
384
|
+
' - strict_no_runtime_execution',
|
|
385
|
+
'next_action: node strict',
|
|
386
|
+
].join('\n'),
|
|
387
|
+
}, 'rules-strict');
|
|
388
|
+
const relaxedRulesRoot = await writeRules(repoPath, {
|
|
389
|
+
'approved/demo.reload.relaxed.v1.yaml': [
|
|
390
|
+
'id: demo.reload.relaxed.v1',
|
|
391
|
+
'version: 1.0.0',
|
|
392
|
+
'trigger_family: reload',
|
|
393
|
+
'resource_types:',
|
|
394
|
+
' - asset',
|
|
395
|
+
'host_base_type:',
|
|
396
|
+
' - ReloadBase',
|
|
397
|
+
'required_hops:',
|
|
398
|
+
' - resource',
|
|
399
|
+
' - guid_map',
|
|
400
|
+
' - code_loader',
|
|
401
|
+
'guarantees:',
|
|
402
|
+
' - relaxed_chain_closed',
|
|
403
|
+
'non_guarantees:',
|
|
404
|
+
' - relaxed_no_runtime_execution',
|
|
405
|
+
'next_action: node relaxed',
|
|
406
|
+
].join('\n'),
|
|
407
|
+
}, 'rules-relaxed');
|
|
408
|
+
const strict = await verifyRuntimeClaimOnDemand({
|
|
409
|
+
repoPath,
|
|
410
|
+
queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
411
|
+
executeParameterized: strictExecuteParameterized,
|
|
412
|
+
resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
|
|
413
|
+
rulesRoot: strictRulesRoot,
|
|
414
|
+
});
|
|
415
|
+
const relaxed = await verifyRuntimeClaimOnDemand({
|
|
416
|
+
repoPath,
|
|
417
|
+
queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
|
|
418
|
+
executeParameterized,
|
|
419
|
+
resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
|
|
420
|
+
rulesRoot: relaxedRulesRoot,
|
|
421
|
+
});
|
|
422
|
+
expect(strict.status).toBe('verified_partial');
|
|
423
|
+
expect(relaxed.status).toBe('verified_full');
|
|
424
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
425
|
+
});
|
|
426
|
+
it('phase2 runtime claim guarantees/non_guarantees come from matched rule', async () => {
|
|
427
|
+
const repoPath = await makeTempRepo();
|
|
428
|
+
const rulesRoot = await writeRules(repoPath, {
|
|
429
|
+
'approved/demo.reload.claim-semantics.v1.yaml': [
|
|
430
|
+
'id: demo.reload.claim-semantics.v1',
|
|
431
|
+
'version: 2.0.0',
|
|
432
|
+
'trigger_family: reload',
|
|
433
|
+
'resource_types:',
|
|
434
|
+
' - asset',
|
|
435
|
+
'host_base_type:',
|
|
436
|
+
' - ReloadBase',
|
|
437
|
+
'required_hops:',
|
|
438
|
+
' - resource',
|
|
439
|
+
' - guid_map',
|
|
440
|
+
' - code_loader',
|
|
441
|
+
' - code_runtime',
|
|
442
|
+
'guarantees:',
|
|
443
|
+
' - custom_guarantee_from_rule',
|
|
444
|
+
'non_guarantees:',
|
|
445
|
+
' - custom_non_guarantee_from_rule',
|
|
446
|
+
'next_action: node claim-semantics',
|
|
447
|
+
'match:',
|
|
448
|
+
' trigger_tokens:',
|
|
449
|
+
' - reload',
|
|
450
|
+
'topology:',
|
|
451
|
+
' - hop: resource',
|
|
452
|
+
' from:',
|
|
453
|
+
' entity: resource',
|
|
454
|
+
' to:',
|
|
455
|
+
' entity: script',
|
|
456
|
+
' edge:',
|
|
457
|
+
' kind: binds_script',
|
|
458
|
+
'closure:',
|
|
459
|
+
' required_hops:',
|
|
460
|
+
' - resource',
|
|
461
|
+
' - guid_map',
|
|
462
|
+
' - code_loader',
|
|
463
|
+
' - code_runtime',
|
|
464
|
+
' failure_map:',
|
|
465
|
+
' missing_evidence: rule_matched_but_evidence_missing',
|
|
466
|
+
'claims:',
|
|
467
|
+
' guarantees:',
|
|
468
|
+
' - custom_guarantee_from_rule',
|
|
469
|
+
' non_guarantees:',
|
|
470
|
+
' - custom_non_guarantee_from_rule',
|
|
471
|
+
' next_action: node claim-semantics',
|
|
472
|
+
].join('\n'),
|
|
473
|
+
});
|
|
474
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
475
|
+
repoPath,
|
|
476
|
+
queryText: 'Reload runtime start sequence',
|
|
477
|
+
executeParameterized: makeExecuteParameterized(repoPath),
|
|
478
|
+
resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
|
|
479
|
+
rulesRoot,
|
|
480
|
+
});
|
|
481
|
+
expect(out.rule_id).toBe('demo.reload.claim-semantics.v1');
|
|
482
|
+
expect(out.rule_version).toBe('2.0.0');
|
|
483
|
+
expect(out.guarantees).toEqual(['custom_guarantee_from_rule']);
|
|
484
|
+
expect(out.non_guarantees).toEqual(['custom_non_guarantee_from_rule']);
|
|
485
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
486
|
+
});
|
|
487
|
+
it('phase2 non-reload trigger family executes rule-driven verifier', async () => {
|
|
488
|
+
const repoPath = await makeTempRepo();
|
|
489
|
+
const rulesRoot = await writeRules(repoPath, {
|
|
490
|
+
'approved/demo.startup.v1.yaml': [
|
|
491
|
+
'id: demo.startup.v1',
|
|
492
|
+
'version: 1.0.0',
|
|
493
|
+
'trigger_family: startup',
|
|
494
|
+
'resource_types:',
|
|
495
|
+
' - asset',
|
|
496
|
+
'host_base_type:',
|
|
497
|
+
' - StartupNode',
|
|
498
|
+
'required_hops:',
|
|
499
|
+
' - resource',
|
|
500
|
+
'guarantees:',
|
|
501
|
+
' - startup_chain_closed',
|
|
502
|
+
'non_guarantees:',
|
|
503
|
+
' - startup_not_executed',
|
|
504
|
+
'next_action: node startup',
|
|
505
|
+
].join('\n'),
|
|
506
|
+
});
|
|
507
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
508
|
+
repoPath,
|
|
509
|
+
queryText: 'Startup Graph Trigger',
|
|
510
|
+
executeParameterized: makeExecuteParameterized(repoPath),
|
|
511
|
+
resourceBindings: [{ resourcePath: 'Assets/Custom/Rules/startup.asset' }],
|
|
512
|
+
rulesRoot,
|
|
513
|
+
});
|
|
514
|
+
expect(out.rule_id).toBe('demo.startup.v1');
|
|
515
|
+
expect(out.status).toBe('verified_full');
|
|
516
|
+
expect(out.evidence_level).toBe('verified_segment');
|
|
517
|
+
expect(out.reason).toBeUndefined();
|
|
518
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
519
|
+
});
|
|
520
|
+
it('phase2 rule_not_matched does not leak first rule next_action', async () => {
|
|
521
|
+
const repoPath = await makeTempRepo();
|
|
522
|
+
const rulesRoot = await writeRules(repoPath, {
|
|
523
|
+
'approved/demo.startup.v1.yaml': [
|
|
524
|
+
'id: demo.startup.v1',
|
|
525
|
+
'version: 1.0.0',
|
|
526
|
+
'trigger_family: startup',
|
|
527
|
+
'resource_types:',
|
|
528
|
+
' - asset',
|
|
529
|
+
'host_base_type:',
|
|
530
|
+
' - StartupNode',
|
|
531
|
+
'required_hops:',
|
|
532
|
+
' - resource',
|
|
533
|
+
'guarantees:',
|
|
534
|
+
' - startup_chain_closed',
|
|
535
|
+
'non_guarantees:',
|
|
536
|
+
' - startup_not_executed',
|
|
537
|
+
'next_action: node startup-only-action',
|
|
538
|
+
].join('\n'),
|
|
539
|
+
});
|
|
540
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
541
|
+
repoPath,
|
|
542
|
+
queryText: 'Reload runtime start sequence',
|
|
543
|
+
executeParameterized: makeExecuteParameterized(repoPath),
|
|
544
|
+
resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
|
|
545
|
+
rulesRoot,
|
|
546
|
+
});
|
|
547
|
+
expect(out.reason).toBe('rule_not_matched');
|
|
548
|
+
expect(String(out.next_action || '')).not.toContain('startup-only-action');
|
|
549
|
+
expect(String(out.next_action || '')).toContain('Reload runtime start sequence');
|
|
550
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
551
|
+
});
|
|
552
|
+
it('phase5 rule-lab promoted rule is loadable', async () => {
|
|
553
|
+
const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-rule-lab-promote-'));
|
|
554
|
+
const sliceDir = path.join(repoPath, '.gitnexus', 'rules', 'lab', 'runs', 'run-x', 'slices', 'slice-a');
|
|
555
|
+
await fs.mkdir(sliceDir, { recursive: true });
|
|
556
|
+
await fs.writeFile(path.join(sliceDir, 'curated.json'), JSON.stringify({
|
|
557
|
+
run_id: 'run-x',
|
|
558
|
+
slice_id: 'slice-a',
|
|
559
|
+
curated: [
|
|
560
|
+
{
|
|
561
|
+
id: 'candidate-startup-1',
|
|
562
|
+
rule_id: 'demo.startup.v1',
|
|
563
|
+
title: 'startup startup graph',
|
|
564
|
+
confirmed_chain: {
|
|
565
|
+
steps: [{ hop_type: 'resource', anchor: 'Assets/Rules/startup.asset:1', snippet: 'Startup Graph Trigger' }],
|
|
566
|
+
},
|
|
567
|
+
guarantees: ['startup trigger matching is confirmed'],
|
|
568
|
+
non_guarantees: ['does not prove full runtime ordering'],
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
}, null, 2), 'utf-8');
|
|
572
|
+
await promoteCuratedRules({ repoPath, runId: 'run-x', sliceId: 'slice-a', version: '1.0.0' });
|
|
573
|
+
const out = await verifyRuntimeClaimOnDemand({
|
|
574
|
+
repoPath,
|
|
575
|
+
queryText: 'Startup Graph Trigger',
|
|
576
|
+
executeParameterized: async () => [],
|
|
577
|
+
resourceBindings: [{ resourcePath: 'Assets/Rules/startup.asset' }],
|
|
578
|
+
});
|
|
579
|
+
expect(out.rule_id).toBe('demo.startup.v1');
|
|
580
|
+
expect(out.reason).toBeUndefined();
|
|
581
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
582
|
+
});
|
|
56
583
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { UnityResourceBinding, LifecycleOverrides } from '../../rule-lab/types.js';
|
|
2
|
+
export interface RuntimeClaimRuleCatalogEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
version: string;
|
|
5
|
+
file?: string;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
family?: 'analyze_rules' | 'verification_rules';
|
|
8
|
+
}
|
|
9
|
+
export interface RuntimeClaimRule {
|
|
10
|
+
id: string;
|
|
11
|
+
version: string;
|
|
12
|
+
trigger_family: string;
|
|
13
|
+
resource_types: string[];
|
|
14
|
+
host_base_type: string[];
|
|
15
|
+
match?: {
|
|
16
|
+
trigger_tokens: string[];
|
|
17
|
+
symbol_kind?: string[];
|
|
18
|
+
module_scope?: string[];
|
|
19
|
+
resource_types?: string[];
|
|
20
|
+
host_base_type?: string[];
|
|
21
|
+
};
|
|
22
|
+
required_hops: string[];
|
|
23
|
+
guarantees: string[];
|
|
24
|
+
non_guarantees: string[];
|
|
25
|
+
next_action?: string;
|
|
26
|
+
family?: 'analyze_rules' | 'verification_rules';
|
|
27
|
+
resource_bindings?: UnityResourceBinding[];
|
|
28
|
+
lifecycle_overrides?: LifecycleOverrides;
|
|
29
|
+
file_path: string;
|
|
30
|
+
topology?: Array<{
|
|
31
|
+
hop: string;
|
|
32
|
+
from: Record<string, unknown>;
|
|
33
|
+
to: Record<string, unknown>;
|
|
34
|
+
edge: {
|
|
35
|
+
kind: string;
|
|
36
|
+
};
|
|
37
|
+
constraints?: Record<string, unknown>;
|
|
38
|
+
}>;
|
|
39
|
+
closure?: {
|
|
40
|
+
required_hops: string[];
|
|
41
|
+
failure_map: Record<string, string>;
|
|
42
|
+
};
|
|
43
|
+
claims?: {
|
|
44
|
+
guarantees: string[];
|
|
45
|
+
non_guarantees: string[];
|
|
46
|
+
next_action: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export interface RuntimeClaimRuleRegistry {
|
|
50
|
+
repoPath: string;
|
|
51
|
+
rulesRoot: string;
|
|
52
|
+
catalogPath: string;
|
|
53
|
+
activeRules: RuntimeClaimRule[];
|
|
54
|
+
}
|
|
55
|
+
export type RuleRegistryLoadErrorCode = 'rule_catalog_missing' | 'rule_catalog_invalid' | 'rule_file_missing';
|
|
56
|
+
export declare class RuleRegistryLoadError extends Error {
|
|
57
|
+
code: RuleRegistryLoadErrorCode;
|
|
58
|
+
details?: Record<string, string>;
|
|
59
|
+
constructor(code: RuleRegistryLoadErrorCode, message: string, details?: Record<string, string>);
|
|
60
|
+
}
|
|
61
|
+
export declare function parseRuleYaml(raw: string, filePath: string): RuntimeClaimRule;
|
|
62
|
+
export declare function loadRuleRegistry(repoPath: string, rulesRoot?: string): Promise<RuntimeClaimRuleRegistry>;
|
|
63
|
+
export declare function loadAnalyzeRules(repoPath: string, rulesRoot?: string): Promise<RuntimeClaimRule[]>;
|