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.
|
|
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": "
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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
|
-
"
|
|
292
|
-
"
|
|
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) {
|
|
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-
|
|
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":
|
|
11
|
+
"duration_seconds": 1,
|
|
12
12
|
"results": [
|
|
13
13
|
{
|
|
14
14
|
"name": "check_mr_traceability.sh",
|