guardian-framework 0.1.112 → 0.1.113

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/cli.js CHANGED
@@ -3202,7 +3202,7 @@ __export(exports_package, {
3202
3202
  bin: () => bin,
3203
3203
  author: () => author
3204
3204
  });
3205
- var name = "guardian-framework", version = "0.1.112", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
3205
+ var name = "guardian-framework", version = "0.1.113", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
3206
3206
  var init_package = __esm(() => {
3207
3207
  exports = {
3208
3208
  ".": {
package/dist/exports.js CHANGED
@@ -1987,7 +1987,7 @@ __export(exports_package, {
1987
1987
  bin: () => bin,
1988
1988
  author: () => author
1989
1989
  });
1990
- var name = "guardian-framework", version = "0.1.112", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1990
+ var name = "guardian-framework", version = "0.1.113", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1991
1991
  var init_package = __esm(() => {
1992
1992
  exports = {
1993
1993
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.112",
3
+ "version": "0.1.113",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -136,6 +136,100 @@ function sanitizeContext(context: string): string {
136
136
  .slice(0, 5000);
137
137
  }
138
138
 
139
+ // True when a JSON string was cut off mid-output (unclosed structures or a
140
+ // mid-string cut) — distinguishes truncation from invalid-but-complete JSON.
141
+ function isTruncatedJsonText(text: string): boolean {
142
+ const stack: string[] = [];
143
+ let inString = false;
144
+ let escaped = false;
145
+ for (let i = 0; i < text.length; i++) {
146
+ const ch = text[i];
147
+ if (inString) {
148
+ if (escaped) escaped = false;
149
+ else if (ch === "\\") escaped = true;
150
+ else if (ch === '"') inString = false;
151
+ continue;
152
+ }
153
+ if (ch === '"') inString = true;
154
+ else if (ch === "{" || ch === "[") stack.push(ch);
155
+ else if (ch === "}" || ch === "]") {
156
+ const open = stack.pop();
157
+ if ((ch === "}" && open !== "{") || (ch === "]" && open !== "[")) return false;
158
+ }
159
+ }
160
+ return inString || stack.length > 0;
161
+ }
162
+
163
+ function pickKey(item: Record<string, unknown>, keys: string[]): string {
164
+ return keys.map((k) => String(item[k] ?? "")).join("|");
165
+ }
166
+
167
+ function mergeArrayById(
168
+ existing: Record<string, unknown>[],
169
+ incoming: Record<string, unknown>[],
170
+ keys: string[],
171
+ ): Record<string, unknown>[] {
172
+ const out = new Map<string, Record<string, unknown>>();
173
+ for (const item of existing) out.set(pickKey(item, keys), item);
174
+ for (const item of incoming) {
175
+ const k = pickKey(item, keys);
176
+ const prev = out.get(k);
177
+ if (prev) out.set(k, { ...prev, ...item });
178
+ else out.set(k, item);
179
+ }
180
+ return Array.from(out.values());
181
+ }
182
+
183
+ // Merge a repeated domain_save_result call into a previously saved session so
184
+ // large domains can be saved in parts without a single oversized response.
185
+ function mergeRawJson(existing: Record<string, unknown>, incoming: Record<string, unknown>): Record<string, unknown> {
186
+ const merged: Record<string, unknown> = { ...existing, ...incoming };
187
+ const mergeArr = (key: string, keys: string[]) => {
188
+ const a = Array.isArray(existing[key]) ? (existing[key] as Record<string, unknown>[]) : [];
189
+ const b = Array.isArray(incoming[key]) ? (incoming[key] as Record<string, unknown>[]) : [];
190
+ merged[key] = mergeArrayById(a, b, keys);
191
+ };
192
+ mergeArr("actors", ["name"]);
193
+ mergeArr("functionalRequirements", ["id"]);
194
+ mergeArr("nonFunctionalRequirements", ["id"]);
195
+ mergeArr("assumptions", ["assumption"]);
196
+ mergeArr("domainEvents", ["name", "context"]);
197
+ mergeArr("ubiquitousLanguage", ["term", "boundedContext"]);
198
+ // entities dedupe by (context, name)
199
+ const entA = Array.isArray(existing.entities) ? (existing.entities as Record<string, unknown>[]) : [];
200
+ const entB = Array.isArray(incoming.entities) ? (incoming.entities as Record<string, unknown>[]) : [];
201
+ merged.entities = mergeArrayById(entA, entB, ["name", "context"]);
202
+ // bounded contexts: merge by name; union entity lists + invariants
203
+ const bcA = Array.isArray(existing.boundedContexts) ? (existing.boundedContexts as Record<string, unknown>[]) : [];
204
+ const bcB = Array.isArray(incoming.boundedContexts) ? (incoming.boundedContexts as Record<string, unknown>[]) : [];
205
+ const bcMap = new Map<string, Record<string, unknown>>();
206
+ for (const bc of [...bcA, ...bcB]) {
207
+ const name = String(bc.name ?? "");
208
+ if (!name) continue;
209
+ const prev = bcMap.get(name);
210
+ if (!prev) { bcMap.set(name, { ...bc }); continue; }
211
+ const entities = new Set<string>([
212
+ ...(Array.isArray(prev.entities) ? prev.entities.map(String) : []),
213
+ ...(Array.isArray(bc.entities) ? bc.entities.map(String) : []),
214
+ ]);
215
+ const invariants = Array.isArray(bc.invariants) && bc.invariants.length > 0
216
+ ? bc.invariants
217
+ : prev.invariants;
218
+ bcMap.set(name, {
219
+ ...prev,
220
+ ...bc,
221
+ entities: Array.from(entities),
222
+ invariants: invariants ?? [],
223
+ });
224
+ }
225
+ merged.boundedContexts = Array.from(bcMap.values());
226
+ for (const key of ["businessContext", "openQuestions", "aggregateRoots", "status"]) {
227
+ const v = incoming[key];
228
+ if (v !== undefined && String(v).trim() !== "" && String(v).trim() !== "None") merged[key] = v;
229
+ }
230
+ return merged;
231
+ }
232
+
139
233
  function buildExplorationPrompt(context: string): string {
140
234
  return [
141
235
  "You are a Domain-Driven Design expert. Analyze the following business",
@@ -288,8 +382,10 @@ export default function (pi: ExtensionAPI) {
288
382
  label: "Domain Save Result",
289
383
  description:
290
384
  "Save the agent's domain analysis JSON as a structured exploration session. " +
291
- "As a fallback for agents that prefer tool calls over direct file writes. " +
292
- "Parses JSON, writes exploration.md and updates ubiquitous-language.md.",
385
+ "Incremental: repeated calls merge into the session, so large domains can be " +
386
+ "delivered in parts (boundedContexts first, then per-context enrichments) instead of " +
387
+ "one oversized response that truncates. Writes exploration.md, session file and .raw.json; " +
388
+ "updates ubiquitous-language.md.",
293
389
  parameters: Type.Object({
294
390
  sessionId: Type.String({ description: "Session ID from --explore" }),
295
391
  responseJson: Type.String({ description: "Domain analysis JSON from the agent" }),
@@ -305,7 +401,27 @@ export default function (pi: ExtensionAPI) {
305
401
  if (jsonMatch) cleaned = jsonMatch[1].trim();
306
402
  let parsedJson: Record<string, unknown>;
307
403
  try { parsedJson = JSON.parse(cleaned) as Record<string, unknown>; }
308
- catch (e) { return { content: [{ type: "text" as const, text: "ERROR: Failed to parse JSON: " + String(e) }] }; }
404
+ catch (e) {
405
+ if (isTruncatedJsonText(cleaned)) {
406
+ return { content: [{ type: "text" as const, text:
407
+ "ERROR: domain JSON was truncated before completion. Never resend a partial model as final. " +
408
+ "Options: (1) retry with the full completed JSON; (2) for large domains save IN PARTS — " +
409
+ "domain_save_result merges: send boundedContexts first, then per-context enrichments " +
410
+ "(entities/events/UL/invariants) across several calls; (3) reduce model output strain by " +
411
+ "phasing the exploration." }] };
412
+ }
413
+ return { content: [{ type: "text" as const, text: "ERROR: Failed to parse JSON: " + String(e) }] };
414
+ }
415
+
416
+ // Incremental: merge into any previously saved structured session
417
+ // (<session>.raw.json) so large domains can be saved in parts.
418
+ const rawPath = path.join(ctx.cwd, ".pi", "domain", "exploration", sessionId + ".raw.json");
419
+ if (fs.existsSync(rawPath)) {
420
+ try {
421
+ const existing = JSON.parse(fs.readFileSync(rawPath, "utf-8")) as Record<string, unknown>;
422
+ parsedJson = mergeRawJson(existing, parsedJson);
423
+ } catch { /* ignore unreadable prior state */ }
424
+ }
309
425
 
310
426
  const now = new Date().toISOString().split("T")[0];
311
427
  const explorationDir = path.join(ctx.cwd, ".pi", "domain", "exploration");
@@ -369,6 +485,12 @@ export default function (pi: ExtensionAPI) {
369
485
  fs.writeFileSync(tmpSession, sessionContent, "utf-8");
370
486
  fs.renameSync(tmpSession, sessionPath);
371
487
 
488
+ // Persist the merged structured result (enables incremental saves +
489
+ // downstream depth validators that read .raw.json)
490
+ const rawTmp = rawPath + ".tmp";
491
+ fs.writeFileSync(rawTmp, JSON.stringify(parsedJson, null, 2), "utf-8");
492
+ fs.renameSync(rawTmp, rawPath);
493
+
372
494
  // Update ubiquitous-language.md with new terms
373
495
  const glPath = path.join(ctx.cwd, ".pi", "domain", "ubiquitous-language.md");
374
496
  const ulRaw = parsedJson.ubiquitousLanguage as Array<Record<string, unknown>> | undefined;
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-09-04T16:02:56Z",
2
+ "timestamp": "2026-09-04T16:15:16Z",
3
3
  "mode": "all",
4
4
  "stages_run": [],
5
5
  "summary": {
@@ -8,7 +8,7 @@
8
8
  "failed": 1,
9
9
  "skipped": 12
10
10
  },
11
- "duration_seconds": 0,
11
+ "duration_seconds": 1,
12
12
  "results": [
13
13
  {
14
14
  "name": "check_mr_traceability.sh",