wholestack 0.4.0

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/index.js ADDED
@@ -0,0 +1,297 @@
1
+ import {
2
+ ALL_DETECTORS,
3
+ Agent,
4
+ CapabilityEnforcer,
5
+ CommandRegistry,
6
+ DEFAULT_FIREWALL,
7
+ DEFAULT_POLICY,
8
+ DEFAULT_REGISTRY,
9
+ HookRunner,
10
+ InputController,
11
+ NO_CAPS,
12
+ PERMISSION_MODES,
13
+ Permissions,
14
+ PolicyEngine,
15
+ PromptFirewall,
16
+ PromptRegistry,
17
+ Session,
18
+ UNTRUSTED_CHANNELS,
19
+ UsageMeter,
20
+ applyFirewall,
21
+ applyHooks,
22
+ buildProviderOptions,
23
+ buildTools,
24
+ buildWebTools,
25
+ capsAllow,
26
+ channelAuthority,
27
+ compact,
28
+ defaultManifestFor,
29
+ diffStat,
30
+ estimateCost,
31
+ estimateTokens,
32
+ isPermissionMode,
33
+ isUntrusted,
34
+ isUntrustedTool,
35
+ listModels,
36
+ loadHookFiles,
37
+ loadMcpTools,
38
+ loadMdCommands,
39
+ loadPlugins,
40
+ loadProjectMemory,
41
+ maxSeverity,
42
+ mergeHookSets,
43
+ modelContextWindow,
44
+ modelId,
45
+ modelLabel,
46
+ renderEditDiff,
47
+ renderNewFileDiff,
48
+ resolveModel,
49
+ resolveModelKey,
50
+ severityRank,
51
+ supportsThinking
52
+ } from "./chunk-7DJJXUV4.js";
53
+
54
+ // src/prompts/template.ts
55
+ var PLACEHOLDER = /\{\{([A-Za-z0-9_.]+)\}\}/g;
56
+ function renderTemplate(tpl, vars, opts) {
57
+ const strict = opts?.strict ?? false;
58
+ return tpl.replace(PLACEHOLDER, (_match, key) => {
59
+ if (Object.prototype.hasOwnProperty.call(vars, key)) {
60
+ return vars[key];
61
+ }
62
+ if (strict) throw new Error(`renderTemplate: unknown template variable "${key}"`);
63
+ return "";
64
+ });
65
+ }
66
+ function estimateTextTokens(text) {
67
+ return Math.ceil(text.length / 4);
68
+ }
69
+ var TRIM_MARKER = " \u2026[trimmed]";
70
+ var TRIM_ORDER = [
71
+ // untrusted data channels (see UNTRUSTED_CHANNELS / "memory" in the contract)
72
+ "tool",
73
+ "mcp",
74
+ "web",
75
+ "retrieved",
76
+ "memory",
77
+ "user",
78
+ "developer"
79
+ ];
80
+ function cloneChannel(ch) {
81
+ return { ...ch };
82
+ }
83
+ function budgetChannels(input) {
84
+ const reserve = input.reserve ?? 0;
85
+ const limit = Math.max(0, input.maxTokens - reserve);
86
+ const channels = input.channels.map(cloneChannel);
87
+ const dropped = /* @__PURE__ */ new Map();
88
+ const total = () => channels.reduce((sum, ch) => sum + estimateTextTokens(ch.content), 0);
89
+ for (const kind of TRIM_ORDER) {
90
+ if (total() <= limit) break;
91
+ if (kind === "system") continue;
92
+ for (const ch of channels) {
93
+ if (ch.kind !== kind) continue;
94
+ const before = estimateTextTokens(ch.content);
95
+ const overBy = total() - limit;
96
+ if (overBy <= 0) break;
97
+ const trimmed = trimContent(ch.content, before - overBy);
98
+ if (trimmed === ch.content) continue;
99
+ ch.content = trimmed;
100
+ const removed = before - estimateTextTokens(ch.content);
101
+ if (removed > 0) dropped.set(kind, (dropped.get(kind) ?? 0) + removed);
102
+ }
103
+ }
104
+ const trimmedSummary = TRIM_ORDER.filter((k) => (dropped.get(k) ?? 0) > 0).map((kind) => ({
105
+ kind,
106
+ droppedTokens: dropped.get(kind) ?? 0
107
+ }));
108
+ return { channels, trimmed: trimmedSummary, total: total() };
109
+ }
110
+ function trimContent(content, targetTokens) {
111
+ if (estimateTextTokens(content) <= targetTokens) return content;
112
+ const markerTokens = estimateTextTokens(TRIM_MARKER);
113
+ const headChars = (targetTokens - markerTokens) * 4;
114
+ if (headChars <= 0) return TRIM_MARKER.trimStart();
115
+ const head = content.slice(0, Math.min(content.length, headChars));
116
+ return head + TRIM_MARKER;
117
+ }
118
+
119
+ // src/prompts/lint.ts
120
+ var LONG_BODY_CHARS = 2e4;
121
+ var SECRET_PATTERNS = [
122
+ { re: /\bsk-[A-Za-z0-9_-]{16,}\b/, label: "API key (sk-\u2026)" },
123
+ { re: /\bAKIA[0-9A-Z]{16}\b/, label: "AWS access key id (AKIA\u2026)" },
124
+ { re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/, label: "PEM private key block" }
125
+ ];
126
+ var UNRENDERED_VAR = /\{\{[^}]*\}\}/;
127
+ function lintSystemText(body) {
128
+ const findings = [];
129
+ if (body.trim().length === 0) {
130
+ findings.push({
131
+ rule: "empty-body",
132
+ severity: "error",
133
+ message: "Body is empty \u2014 an instruction channel with no content does nothing."
134
+ });
135
+ return findings;
136
+ }
137
+ if (body.length > LONG_BODY_CHARS) {
138
+ findings.push({
139
+ rule: "very-long-body",
140
+ severity: "warning",
141
+ message: `Body is ${body.length} chars (> ${LONG_BODY_CHARS}) \u2014 likely a paste accident or runaway concatenation.`
142
+ });
143
+ }
144
+ for (const { re, label } of SECRET_PATTERNS) {
145
+ if (re.test(body)) {
146
+ findings.push({
147
+ rule: "leaked-secret",
148
+ severity: "error",
149
+ message: `Apparent ${label} in body \u2014 strip the credential before sending.`
150
+ });
151
+ }
152
+ }
153
+ if (UNRENDERED_VAR.test(body)) {
154
+ findings.push({
155
+ rule: "unrendered-template-var",
156
+ severity: "warning",
157
+ message: 'Unrendered template variable "{{\u2026}}" left in body \u2014 the template was not filled in.'
158
+ });
159
+ }
160
+ return findings;
161
+ }
162
+ function lintPrompt(channels) {
163
+ const findings = [];
164
+ const systemChannels = channels.filter((ch) => ch.kind === "system");
165
+ if (systemChannels.length === 0) {
166
+ findings.push({
167
+ rule: "missing-system-channel",
168
+ severity: "error",
169
+ message: "No system channel present \u2014 the agent has no core instructions to anchor on.",
170
+ channel: "system"
171
+ });
172
+ } else if (systemChannels.length > 1) {
173
+ findings.push({
174
+ rule: "multiple-system-channels",
175
+ severity: "warning",
176
+ message: `${systemChannels.length} system channels present \u2014 collapse them into one to keep authority unambiguous.`,
177
+ channel: "system"
178
+ });
179
+ }
180
+ for (const ch of channels) {
181
+ if (isUntrusted(ch.kind) && ch.trusted) {
182
+ findings.push({
183
+ rule: "untrusted-marked-trusted",
184
+ severity: "error",
185
+ message: `Channel "${ch.kind}" is untrusted external input but is marked trusted:true \u2014 it must be fenced as data.`,
186
+ channel: ch.kind
187
+ });
188
+ }
189
+ if (ch.kind === "system" || ch.kind === "developer") {
190
+ for (const f of lintSystemText(ch.content)) {
191
+ findings.push({ ...f, channel: ch.kind });
192
+ }
193
+ }
194
+ }
195
+ return findings;
196
+ }
197
+
198
+ // src/prompts/channels.ts
199
+ var ChannelStack = class {
200
+ list = [];
201
+ add(kind, content, opts = {}) {
202
+ this.list.push({
203
+ kind,
204
+ authority: channelAuthority(kind),
205
+ source: opts.source,
206
+ content,
207
+ trusted: !isUntrusted(kind)
208
+ });
209
+ return this;
210
+ }
211
+ /** Convenience for the untrusted families — always firewall-fenced on render. */
212
+ addUntrusted(kind, content, source) {
213
+ return this.add(kind, content, { source });
214
+ }
215
+ channels() {
216
+ return [...this.list];
217
+ }
218
+ /**
219
+ * Render the stack into provider-ready text, with hard tier isolation:
220
+ * · `system` — system + developer channels (authoritative)
221
+ * · `user` — user channels (the human's literal input)
222
+ * · `dataBlocks` — every untrusted channel, firewalled + fenced as data
223
+ * Returns the firewall verdicts so the caller can warn on detections.
224
+ */
225
+ render(opts) {
226
+ const channels = opts.budget != null ? budgetChannels({ channels: this.list, maxTokens: opts.budget }).channels : this.list;
227
+ const system = channels.filter((ch) => ch.kind === "system" || ch.kind === "developer").map((ch) => ch.content).join("\n\n");
228
+ const user = channels.filter((ch) => ch.kind === "user").map((ch) => ch.content).join("\n\n");
229
+ const findings = [];
230
+ const blocks = [];
231
+ for (const ch of channels) {
232
+ if (!isUntrusted(ch.kind)) continue;
233
+ const verdict = opts.firewall.guard(ch, opts.policy);
234
+ findings.push(verdict);
235
+ blocks.push(verdict.safe);
236
+ }
237
+ return { system, user, dataBlocks: blocks.join("\n\n"), findings };
238
+ }
239
+ };
240
+ export {
241
+ ALL_DETECTORS,
242
+ Agent,
243
+ CapabilityEnforcer,
244
+ ChannelStack,
245
+ CommandRegistry,
246
+ DEFAULT_FIREWALL,
247
+ DEFAULT_POLICY,
248
+ DEFAULT_REGISTRY,
249
+ HookRunner,
250
+ InputController,
251
+ NO_CAPS,
252
+ PERMISSION_MODES,
253
+ Permissions,
254
+ PolicyEngine,
255
+ PromptFirewall,
256
+ PromptRegistry,
257
+ Session,
258
+ UNTRUSTED_CHANNELS,
259
+ UsageMeter,
260
+ applyFirewall,
261
+ applyHooks,
262
+ budgetChannels,
263
+ buildProviderOptions,
264
+ buildTools,
265
+ buildWebTools,
266
+ capsAllow,
267
+ channelAuthority,
268
+ compact,
269
+ defaultManifestFor,
270
+ diffStat,
271
+ estimateCost,
272
+ estimateTextTokens,
273
+ estimateTokens,
274
+ isPermissionMode,
275
+ isUntrusted,
276
+ isUntrustedTool,
277
+ lintPrompt,
278
+ lintSystemText,
279
+ listModels,
280
+ loadHookFiles,
281
+ loadMcpTools,
282
+ loadMdCommands,
283
+ loadPlugins,
284
+ loadProjectMemory,
285
+ maxSeverity,
286
+ mergeHookSets,
287
+ modelContextWindow,
288
+ modelId,
289
+ modelLabel,
290
+ renderEditDiff,
291
+ renderNewFileDiff,
292
+ renderTemplate,
293
+ resolveModel,
294
+ resolveModelKey,
295
+ severityRank,
296
+ supportsThinking
297
+ };
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "wholestack",
3
+ "version": "0.4.0",
4
+ "description": "Wholestack — a pro-grade conversational terminal agent for the Wholestack codegen engine. Talk to it in plain language: it writes ISL, generates full-stack or Solidity apps, and proves them with ShipGate. Browser login, membership-gated builds, slash commands, sessions, plan mode, diffs, MCP, plugins.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "bin": {
16
+ "wholestack": "./dist/cli.js",
17
+ "zeta": "./dist/cli.js"
18
+ },
19
+ "keywords": [
20
+ "ai",
21
+ "codegen",
22
+ "agent",
23
+ "cli",
24
+ "wholestack",
25
+ "shipgate",
26
+ "isl",
27
+ "full-stack",
28
+ "solidity",
29
+ "formal-verification"
30
+ ],
31
+ "homepage": "https://wholestack.ai",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/wholestack/wholestack.git",
35
+ "directory": "packages/zeta-g-cli"
36
+ },
37
+ "bugs": {
38
+ "url": "https://wholestack.ai"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "README.md"
46
+ ],
47
+ "engines": {
48
+ "node": ">=20.0.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean && node -e \"require('fs').chmodSync('dist/cli.js','755')\"",
52
+ "dev": "tsx src/cli.ts",
53
+ "typecheck": "tsc --noEmit",
54
+ "clean": "rimraf dist"
55
+ },
56
+ "dependencies": {
57
+ "@ai-sdk/openai": "^3.0.64",
58
+ "@modelcontextprotocol/sdk": "^1.29.0",
59
+ "ai": "^6.0.170",
60
+ "diff": "^5.2.0",
61
+ "fast-glob": "^3.3.3",
62
+ "zod": "^3.25.76"
63
+ },
64
+ "devDependencies": {
65
+ "@types/diff": "^5.2.0",
66
+ "@types/node": "^20.10.0",
67
+ "tsup": "^8.0.1",
68
+ "tsx": "^4.7.0",
69
+ "typescript": "^5.3.3",
70
+ "rimraf": "^5.0.5"
71
+ },
72
+ "author": "Wholestack",
73
+ "license": "MIT",
74
+ "sideEffects": false
75
+ }