guardian-framework 0.1.111 → 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.111", 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
  ".": {
@@ -3211,7 +3211,7 @@ var init_package = __esm(() => {
3211
3211
  }
3212
3212
  };
3213
3213
  bin = {
3214
- "guardian-framework": "./dist/cli.js"
3214
+ "guardian-framework": "dist/cli.js"
3215
3215
  };
3216
3216
  files = ["dist/", "templates/"];
3217
3217
  engines = {
@@ -3237,7 +3237,7 @@ var init_package = __esm(() => {
3237
3237
  };
3238
3238
  repository = {
3239
3239
  type: "git",
3240
- url: "https://github.com/arman-jalili/guardian-framework"
3240
+ url: "git+https://github.com/arman-jalili/guardian-framework.git"
3241
3241
  };
3242
3242
  bugs = {
3243
3243
  url: "https://github.com/arman-jalili/guardian-framework/issues"
@@ -11851,7 +11851,21 @@ function parseExplorationResponse(rawResponse, sessionId) {
11851
11851
  if (jsonMatch) {
11852
11852
  cleaned = jsonMatch[1].trim();
11853
11853
  }
11854
- const parsed = JSON.parse(cleaned);
11854
+ let parsed;
11855
+ try {
11856
+ parsed = JSON.parse(cleaned);
11857
+ } catch (err) {
11858
+ if (isTruncatedJson(cleaned)) {
11859
+ return {
11860
+ ok: false,
11861
+ error: "Exploration response was truncated before completion (incomplete JSON). " + "Retry with a higher model output limit, ask the model to continue from " + "exactly where it stopped (parseContinuedExplorationResponse), or split the " + "exploration into phases (strategic model first, then per-core-context enrichment)."
11862
+ };
11863
+ }
11864
+ return {
11865
+ ok: false,
11866
+ error: `Invalid exploration response: ${err instanceof Error ? err.message : String(err)}`
11867
+ };
11868
+ }
11855
11869
  const result = {
11856
11870
  sessionId: sessionId ?? parsed.sessionId ?? crypto.randomUUID(),
11857
11871
  businessContext: coerceString(parsed.businessContext, ""),
@@ -11929,6 +11943,33 @@ function parseExplorationResponse(rawResponse, sessionId) {
11929
11943
  };
11930
11944
  }
11931
11945
  }
11946
+ function isTruncatedJson(text2) {
11947
+ const stack = [];
11948
+ let inString = false;
11949
+ let escaped = false;
11950
+ for (let i2 = 0;i2 < text2.length; i2++) {
11951
+ const ch = text2[i2];
11952
+ if (inString) {
11953
+ if (escaped)
11954
+ escaped = false;
11955
+ else if (ch === "\\")
11956
+ escaped = true;
11957
+ else if (ch === '"')
11958
+ inString = false;
11959
+ continue;
11960
+ }
11961
+ if (ch === '"')
11962
+ inString = true;
11963
+ else if (ch === "{" || ch === "[")
11964
+ stack.push(ch);
11965
+ else if (ch === "}" || ch === "]") {
11966
+ const open = stack.pop();
11967
+ if (ch === "}" && open !== "{" || ch === "]" && open !== "[")
11968
+ return false;
11969
+ }
11970
+ }
11971
+ return inString || stack.length > 0;
11972
+ }
11932
11973
  function coerceString(val, fallback) {
11933
11974
  if (typeof val === "string")
11934
11975
  return val;
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.111", 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
  ".": {
@@ -1996,7 +1996,7 @@ var init_package = __esm(() => {
1996
1996
  }
1997
1997
  };
1998
1998
  bin = {
1999
- "guardian-framework": "./dist/cli.js"
1999
+ "guardian-framework": "dist/cli.js"
2000
2000
  };
2001
2001
  files = ["dist/", "templates/"];
2002
2002
  engines = {
@@ -2022,7 +2022,7 @@ var init_package = __esm(() => {
2022
2022
  };
2023
2023
  repository = {
2024
2024
  type: "git",
2025
- url: "https://github.com/arman-jalili/guardian-framework"
2025
+ url: "git+https://github.com/arman-jalili/guardian-framework.git"
2026
2026
  };
2027
2027
  bugs = {
2028
2028
  url: "https://github.com/arman-jalili/guardian-framework/issues"
@@ -12175,7 +12175,21 @@ function parseExplorationResponse(rawResponse, sessionId) {
12175
12175
  if (jsonMatch) {
12176
12176
  cleaned = jsonMatch[1].trim();
12177
12177
  }
12178
- const parsed = JSON.parse(cleaned);
12178
+ let parsed;
12179
+ try {
12180
+ parsed = JSON.parse(cleaned);
12181
+ } catch (err) {
12182
+ if (isTruncatedJson(cleaned)) {
12183
+ return {
12184
+ ok: false,
12185
+ error: "Exploration response was truncated before completion (incomplete JSON). " + "Retry with a higher model output limit, ask the model to continue from " + "exactly where it stopped (parseContinuedExplorationResponse), or split the " + "exploration into phases (strategic model first, then per-core-context enrichment)."
12186
+ };
12187
+ }
12188
+ return {
12189
+ ok: false,
12190
+ error: `Invalid exploration response: ${err instanceof Error ? err.message : String(err)}`
12191
+ };
12192
+ }
12179
12193
  const result = {
12180
12194
  sessionId: sessionId ?? parsed.sessionId ?? crypto4.randomUUID(),
12181
12195
  businessContext: coerceString(parsed.businessContext, ""),
@@ -12253,6 +12267,33 @@ function parseExplorationResponse(rawResponse, sessionId) {
12253
12267
  };
12254
12268
  }
12255
12269
  }
12270
+ function isTruncatedJson(text2) {
12271
+ const stack = [];
12272
+ let inString = false;
12273
+ let escaped = false;
12274
+ for (let i2 = 0;i2 < text2.length; i2++) {
12275
+ const ch = text2[i2];
12276
+ if (inString) {
12277
+ if (escaped)
12278
+ escaped = false;
12279
+ else if (ch === "\\")
12280
+ escaped = true;
12281
+ else if (ch === '"')
12282
+ inString = false;
12283
+ continue;
12284
+ }
12285
+ if (ch === '"')
12286
+ inString = true;
12287
+ else if (ch === "{" || ch === "[")
12288
+ stack.push(ch);
12289
+ else if (ch === "}" || ch === "]") {
12290
+ const open = stack.pop();
12291
+ if (ch === "}" && open !== "{" || ch === "]" && open !== "[")
12292
+ return false;
12293
+ }
12294
+ }
12295
+ return inString || stack.length > 0;
12296
+ }
12256
12297
  function coerceString(val, fallback) {
12257
12298
  if (typeof val === "string")
12258
12299
  return val;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.111",
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",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "types": "dist/exports.d.ts",
14
14
  "bin": {
15
- "guardian-framework": "./dist/cli.js"
15
+ "guardian-framework": "dist/cli.js"
16
16
  },
17
17
  "files": ["dist/", "templates/"],
18
18
  "engines": {
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "https://github.com/arman-jalili/guardian-framework"
41
+ "url": "git+https://github.com/arman-jalili/guardian-framework.git"
42
42
  },
43
43
  "homepage": "https://github.com/arman-jalili/guardian-framework#readme",
44
44
  "bugs": {
@@ -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-04T15:30:01Z",
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",