@vibecheckai/cli 3.2.0 → 3.2.2
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/bin/runners/lib/agent-firewall/change-packet/builder.js +214 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/analysis-core.js +198 -180
- package/bin/runners/lib/analyzers.js +1119 -536
- package/bin/runners/lib/cli-output.js +236 -210
- package/bin/runners/lib/detectors-v2.js +547 -785
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +144 -738
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/terminal-ui.js +188 -770
- package/bin/runners/lib/truth.js +1004 -321
- package/bin/runners/lib/unified-output.js +162 -158
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runScan.js +113 -10
- package/bin/runners/runShip.js +7 -8
- package/bin/runners/runTruth.js +89 -0
- package/mcp-server/agent-firewall-interceptor.js +164 -0
- package/mcp-server/index.js +347 -313
- package/mcp-server/truth-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1412 -1045
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Explosion Rule
|
|
3
|
+
*
|
|
4
|
+
* Blocks if too many files touched.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate scope explosion rule
|
|
11
|
+
* @param {object} params
|
|
12
|
+
* @param {array} params.files - Changed files
|
|
13
|
+
* @param {string} params.intent - Agent intent message
|
|
14
|
+
* @param {object} params.policy - Policy configuration
|
|
15
|
+
* @returns {object|null} Violation or null
|
|
16
|
+
*/
|
|
17
|
+
function evaluate({ files, intent, policy }) {
|
|
18
|
+
const ruleConfig = policy.rules?.scope_explosion;
|
|
19
|
+
|
|
20
|
+
if (!ruleConfig || !ruleConfig.enabled) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const scope = policy.scope || {};
|
|
25
|
+
const maxFiles = scope.max_files_touched || 10;
|
|
26
|
+
const maxLines = scope.max_lines_changed || 600;
|
|
27
|
+
const requireIntent = scope.require_intent_for_expand_scope || false;
|
|
28
|
+
|
|
29
|
+
const totalFiles = files.length;
|
|
30
|
+
const totalLines = files.reduce((sum, f) => sum + (f.linesChanged || 0), 0);
|
|
31
|
+
|
|
32
|
+
// Check file count
|
|
33
|
+
if (totalFiles > maxFiles) {
|
|
34
|
+
const hasIntent = intent && intent.trim().length > 0;
|
|
35
|
+
|
|
36
|
+
if (requireIntent && !hasIntent) {
|
|
37
|
+
return {
|
|
38
|
+
rule: "scope_explosion",
|
|
39
|
+
severity: ruleConfig.severity || "block",
|
|
40
|
+
message: `Scope explosion: ${totalFiles} files touched (max: ${maxFiles}). Intent required for scope expansion.`,
|
|
41
|
+
metadata: {
|
|
42
|
+
totalFiles,
|
|
43
|
+
maxFiles,
|
|
44
|
+
hasIntent
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
} else if (!requireIntent) {
|
|
48
|
+
return {
|
|
49
|
+
rule: "scope_explosion",
|
|
50
|
+
severity: ruleConfig.severity || "block",
|
|
51
|
+
message: `Scope explosion: ${totalFiles} files touched (max: ${maxFiles})`,
|
|
52
|
+
metadata: {
|
|
53
|
+
totalFiles,
|
|
54
|
+
maxFiles
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check line count
|
|
61
|
+
if (totalLines > maxLines) {
|
|
62
|
+
const hasIntent = intent && intent.trim().length > 0;
|
|
63
|
+
|
|
64
|
+
if (requireIntent && !hasIntent) {
|
|
65
|
+
return {
|
|
66
|
+
rule: "scope_explosion",
|
|
67
|
+
severity: ruleConfig.severity || "block",
|
|
68
|
+
message: `Scope explosion: ${totalLines} lines changed (max: ${maxLines}). Intent required for scope expansion.`,
|
|
69
|
+
metadata: {
|
|
70
|
+
totalLines,
|
|
71
|
+
maxLines,
|
|
72
|
+
hasIntent
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
} else if (!requireIntent) {
|
|
76
|
+
return {
|
|
77
|
+
rule: "scope_explosion",
|
|
78
|
+
severity: ruleConfig.severity || "block",
|
|
79
|
+
message: `Scope explosion: ${totalLines} lines changed (max: ${maxLines})`,
|
|
80
|
+
metadata: {
|
|
81
|
+
totalLines,
|
|
82
|
+
maxLines
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
evaluate
|
|
93
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unsafe Side Effect Rule
|
|
3
|
+
*
|
|
4
|
+
* Blocks unverified side effects.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { CLAIM_TYPES } = require("../../claims/claim-types");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate unsafe side effect rule
|
|
13
|
+
* @param {object} params
|
|
14
|
+
* @param {array} params.claims - Extracted claims
|
|
15
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
16
|
+
* @param {object} params.policy - Policy configuration
|
|
17
|
+
* @returns {object|null} Violation or null
|
|
18
|
+
*/
|
|
19
|
+
function evaluate({ claims, evidence, policy }) {
|
|
20
|
+
const ruleConfig = policy.rules?.unsafe_side_effect;
|
|
21
|
+
|
|
22
|
+
if (!ruleConfig || !ruleConfig.enabled) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const verification = policy.verification || {};
|
|
27
|
+
const requireForDomains = verification.require_for_domains || [];
|
|
28
|
+
|
|
29
|
+
// Find side effect claims
|
|
30
|
+
for (let i = 0; i < claims.length; i++) {
|
|
31
|
+
const claim = claims[i];
|
|
32
|
+
|
|
33
|
+
if (claim.type === CLAIM_TYPES.SIDE_EFFECT) {
|
|
34
|
+
const ev = evidence.find(e => e.claimId === `claim_${i}`);
|
|
35
|
+
|
|
36
|
+
// Check if domain requires verification
|
|
37
|
+
const claimDomain = claim.domain || "general";
|
|
38
|
+
const requiresVerification = requireForDomains.includes(claimDomain);
|
|
39
|
+
|
|
40
|
+
if (requiresVerification && ev && ev.result === "UNPROVEN") {
|
|
41
|
+
return {
|
|
42
|
+
rule: "unsafe_side_effect",
|
|
43
|
+
severity: ruleConfig.severity || "block",
|
|
44
|
+
message: `Unsafe side effect: ${claim.value} requires verification (test coverage or reality proof)`,
|
|
45
|
+
claimId: `claim_${i}`,
|
|
46
|
+
claim
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
evaluate
|
|
57
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["version", "mode", "scope", "rules"],
|
|
5
|
+
"properties": {
|
|
6
|
+
"version": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"description": "Policy schema version"
|
|
9
|
+
},
|
|
10
|
+
"mode": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"enum": ["observe", "enforce"],
|
|
13
|
+
"description": "Firewall mode: observe (log only) or enforce (block writes)"
|
|
14
|
+
},
|
|
15
|
+
"profile": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Policy profile name (e.g., 'repo-lock', 'balanced', 'permissive')"
|
|
18
|
+
},
|
|
19
|
+
"scope": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"max_files_touched": {
|
|
23
|
+
"type": "number",
|
|
24
|
+
"description": "Maximum number of files that can be touched in a single change"
|
|
25
|
+
},
|
|
26
|
+
"max_lines_changed": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "Maximum number of lines that can be changed in a single change"
|
|
29
|
+
},
|
|
30
|
+
"blocked_paths": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": {
|
|
33
|
+
"type": "string"
|
|
34
|
+
},
|
|
35
|
+
"description": "Glob patterns for paths that are blocked from changes"
|
|
36
|
+
},
|
|
37
|
+
"allowed_paths": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "string"
|
|
41
|
+
},
|
|
42
|
+
"description": "Glob patterns for paths that are allowed for changes"
|
|
43
|
+
},
|
|
44
|
+
"require_intent_for_expand_scope": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"description": "Require explicit intent message when scope exceeds limits"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"hard_domains": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"routes": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"description": "Enforce route truth"
|
|
56
|
+
},
|
|
57
|
+
"env": {
|
|
58
|
+
"type": "boolean",
|
|
59
|
+
"description": "Enforce environment variable truth"
|
|
60
|
+
},
|
|
61
|
+
"auth": {
|
|
62
|
+
"type": "boolean",
|
|
63
|
+
"description": "Enforce authentication truth"
|
|
64
|
+
},
|
|
65
|
+
"contracts": {
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"description": "Enforce API contract truth"
|
|
68
|
+
},
|
|
69
|
+
"payments": {
|
|
70
|
+
"type": "boolean",
|
|
71
|
+
"description": "Enforce payment-related changes"
|
|
72
|
+
},
|
|
73
|
+
"side_effects": {
|
|
74
|
+
"type": "boolean",
|
|
75
|
+
"description": "Enforce side effect verification"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"rules": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"additionalProperties": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"severity": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"enum": ["allow", "warn", "block"],
|
|
87
|
+
"description": "Rule severity level"
|
|
88
|
+
},
|
|
89
|
+
"block_if_domain": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"enum": ["auth", "payments", "side_effects", "routes", "env", "contracts"]
|
|
94
|
+
},
|
|
95
|
+
"description": "Upgrade to block if change affects these domains"
|
|
96
|
+
},
|
|
97
|
+
"enabled": {
|
|
98
|
+
"type": "boolean",
|
|
99
|
+
"description": "Whether this rule is enabled"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"evidence": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"require_pointers": {
|
|
108
|
+
"type": "boolean",
|
|
109
|
+
"description": "Require file:line pointers in evidence"
|
|
110
|
+
},
|
|
111
|
+
"acceptable_sources": {
|
|
112
|
+
"type": "array",
|
|
113
|
+
"items": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"enum": ["truthpack.routes", "truthpack.env", "truthpack.auth", "truthpack.contracts", "repo.search"]
|
|
116
|
+
},
|
|
117
|
+
"description": "Acceptable evidence sources"
|
|
118
|
+
},
|
|
119
|
+
"pointer_format": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Expected pointer format (e.g., 'file:lineStart-lineEnd')"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"verification": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {
|
|
128
|
+
"require_for_domains": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"enum": ["auth", "payments", "side_effects"]
|
|
133
|
+
},
|
|
134
|
+
"description": "Domains that require verification"
|
|
135
|
+
},
|
|
136
|
+
"accepted": {
|
|
137
|
+
"type": "array",
|
|
138
|
+
"items": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"enum": ["tests", "reality"]
|
|
141
|
+
},
|
|
142
|
+
"description": "Accepted verification methods"
|
|
143
|
+
},
|
|
144
|
+
"reality": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"properties": {
|
|
147
|
+
"enabled": {
|
|
148
|
+
"type": "boolean"
|
|
149
|
+
},
|
|
150
|
+
"block_on": {
|
|
151
|
+
"type": "array",
|
|
152
|
+
"items": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"enum": ["fake_success", "no_mutation", "network_error"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"output": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"properties": {
|
|
164
|
+
"write_change_packets": {
|
|
165
|
+
"type": "boolean",
|
|
166
|
+
"description": "Write change packets to disk"
|
|
167
|
+
},
|
|
168
|
+
"packet_dir": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"description": "Directory for change packets"
|
|
171
|
+
},
|
|
172
|
+
"report_formats": {
|
|
173
|
+
"type": "array",
|
|
174
|
+
"items": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"enum": ["md", "html", "json", "sarif"]
|
|
177
|
+
},
|
|
178
|
+
"description": "Report output formats"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verdict Generator
|
|
3
|
+
*
|
|
4
|
+
* Combines rule results into final verdict.
|
|
5
|
+
* Priority: BLOCK > WARN > ALLOW
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate verdict from violations
|
|
12
|
+
* @param {array} violations - Array of rule violations
|
|
13
|
+
* @returns {object} Verdict object
|
|
14
|
+
*/
|
|
15
|
+
function generateVerdict(violations) {
|
|
16
|
+
if (!violations || violations.length === 0) {
|
|
17
|
+
return {
|
|
18
|
+
decision: "ALLOW",
|
|
19
|
+
violations: [],
|
|
20
|
+
message: "No violations detected"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check for blocks (highest priority)
|
|
25
|
+
const blocks = violations.filter(v => v.severity === "block");
|
|
26
|
+
if (blocks.length > 0) {
|
|
27
|
+
return {
|
|
28
|
+
decision: "BLOCK",
|
|
29
|
+
violations,
|
|
30
|
+
message: `BLOCKED: ${blocks.length} blocking violation(s) found`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check for warnings
|
|
35
|
+
const warns = violations.filter(v => v.severity === "warn");
|
|
36
|
+
if (warns.length > 0) {
|
|
37
|
+
return {
|
|
38
|
+
decision: "WARN",
|
|
39
|
+
violations,
|
|
40
|
+
message: `WARNING: ${warns.length} warning(s) found`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Only allows (shouldn't happen, but handle gracefully)
|
|
45
|
+
return {
|
|
46
|
+
decision: "ALLOW",
|
|
47
|
+
violations,
|
|
48
|
+
message: "Violations found but all are allow-level"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
generateVerdict
|
|
54
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truthpack Accessor
|
|
3
|
+
*
|
|
4
|
+
* Unified interface for accessing truthpack data.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { loadTruthpack } = require("./loader");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get routes from truthpack
|
|
13
|
+
* @param {string} projectRoot - Project root directory
|
|
14
|
+
* @returns {array} Array of routes
|
|
15
|
+
*/
|
|
16
|
+
function getRoutes(projectRoot) {
|
|
17
|
+
const truthpack = loadTruthpack(projectRoot);
|
|
18
|
+
return truthpack.routes?.routes || [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get environment variables from truthpack
|
|
23
|
+
* @param {string} projectRoot - Project root directory
|
|
24
|
+
* @returns {object} Env vars data
|
|
25
|
+
*/
|
|
26
|
+
function getEnvVars(projectRoot) {
|
|
27
|
+
const truthpack = loadTruthpack(projectRoot);
|
|
28
|
+
return truthpack.env || { vars: [], declared: [], declaredSources: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get auth rules from truthpack
|
|
33
|
+
* @param {string} projectRoot - Project root directory
|
|
34
|
+
* @returns {object} Auth rules data
|
|
35
|
+
*/
|
|
36
|
+
function getAuthRules(projectRoot) {
|
|
37
|
+
const truthpack = loadTruthpack(projectRoot);
|
|
38
|
+
return truthpack.auth || { nextMiddleware: [], nextMatcherPatterns: [], fastify: {} };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get contracts from truthpack
|
|
43
|
+
* @param {string} projectRoot - Project root directory
|
|
44
|
+
* @returns {object} Contracts data
|
|
45
|
+
*/
|
|
46
|
+
function getContracts(projectRoot) {
|
|
47
|
+
const truthpack = loadTruthpack(projectRoot);
|
|
48
|
+
return truthpack.contracts || {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get UI graph from truthpack
|
|
53
|
+
* @param {string} projectRoot - Project root directory
|
|
54
|
+
* @returns {object|null} UI graph or null
|
|
55
|
+
*/
|
|
56
|
+
function getUIGraph(projectRoot) {
|
|
57
|
+
const truthpack = loadTruthpack(projectRoot);
|
|
58
|
+
return truthpack.uiGraph || null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
getRoutes,
|
|
63
|
+
getEnvVars,
|
|
64
|
+
getAuthRules,
|
|
65
|
+
getContracts,
|
|
66
|
+
getUIGraph
|
|
67
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truthpack Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads truthpack files from .vibecheck/truthpack/
|
|
5
|
+
* Caches for performance and validates freshness.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
|
|
13
|
+
const TRUTHPACK_DIR = ".vibecheck/truthpack";
|
|
14
|
+
const STALE_AFTER_HOURS = 24;
|
|
15
|
+
|
|
16
|
+
let cache = {
|
|
17
|
+
routes: null,
|
|
18
|
+
env: null,
|
|
19
|
+
auth: null,
|
|
20
|
+
contracts: null,
|
|
21
|
+
loadedAt: null
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load truthpack from project root
|
|
26
|
+
* @param {string} projectRoot - Project root directory
|
|
27
|
+
* @param {boolean} forceRefresh - Force reload even if cached
|
|
28
|
+
* @returns {object} Truthpack data
|
|
29
|
+
*/
|
|
30
|
+
function loadTruthpack(projectRoot, forceRefresh = false) {
|
|
31
|
+
const truthpackPath = path.join(projectRoot, TRUTHPACK_DIR);
|
|
32
|
+
|
|
33
|
+
// Check cache freshness
|
|
34
|
+
if (!forceRefresh && cache.loadedAt) {
|
|
35
|
+
const ageHours = (Date.now() - cache.loadedAt) / (1000 * 60 * 60);
|
|
36
|
+
if (ageHours < STALE_AFTER_HOURS && cache.routes !== null) {
|
|
37
|
+
return cache;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const truthpack = {
|
|
42
|
+
routes: loadTruthpackFile(projectRoot, "routes.json"),
|
|
43
|
+
env: loadTruthpackFile(projectRoot, "env.json"),
|
|
44
|
+
auth: loadTruthpackFile(projectRoot, "auth.json"),
|
|
45
|
+
contracts: loadTruthpackFile(projectRoot, "contracts.json"),
|
|
46
|
+
uiGraph: loadTruthpackFile(projectRoot, "ui.graph.json")
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Update cache
|
|
50
|
+
cache = {
|
|
51
|
+
...truthpack,
|
|
52
|
+
loadedAt: Date.now()
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return truthpack;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load a specific truthpack file
|
|
60
|
+
* @param {string} projectRoot - Project root directory
|
|
61
|
+
* @param {string} filename - Truthpack filename
|
|
62
|
+
* @returns {object|null} Parsed JSON or null if not found
|
|
63
|
+
*/
|
|
64
|
+
function loadTruthpackFile(projectRoot, filename) {
|
|
65
|
+
const filePath = path.join(projectRoot, TRUTHPACK_DIR, filename);
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(filePath)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.warn(`Failed to load truthpack file ${filename}: ${error.message}`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if truthpack is fresh
|
|
81
|
+
* @param {string} projectRoot - Project root directory
|
|
82
|
+
* @returns {boolean} True if truthpack exists and is fresh
|
|
83
|
+
*/
|
|
84
|
+
function isTruthpackFresh(projectRoot) {
|
|
85
|
+
const truthpackPath = path.join(projectRoot, TRUTHPACK_DIR);
|
|
86
|
+
const routesPath = path.join(truthpackPath, "routes.json");
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(routesPath)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const stats = fs.statSync(routesPath);
|
|
93
|
+
const ageHours = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60);
|
|
94
|
+
|
|
95
|
+
return ageHours < STALE_AFTER_HOURS;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Clear truthpack cache
|
|
100
|
+
*/
|
|
101
|
+
function clearCache() {
|
|
102
|
+
cache = {
|
|
103
|
+
routes: null,
|
|
104
|
+
env: null,
|
|
105
|
+
auth: null,
|
|
106
|
+
contracts: null,
|
|
107
|
+
loadedAt: null
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
loadTruthpack,
|
|
113
|
+
loadTruthpackFile,
|
|
114
|
+
isTruthpackFresh,
|
|
115
|
+
clearCache
|
|
116
|
+
};
|