guardian-framework 0.1.112 → 0.1.114
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.114", 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.
|
|
1990
|
+
var name = "guardian-framework", version = "0.1.114", 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
|
@@ -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;
|
|
@@ -675,32 +797,58 @@ export default function (pi: ExtensionAPI) {
|
|
|
675
797
|
// Using sendMessage with triggerTurn=true causes the agent to process
|
|
676
798
|
// this as a new conversation turn, not as a command response.
|
|
677
799
|
const analysisPrompt = [
|
|
678
|
-
"
|
|
679
|
-
"
|
|
800
|
+
"Analyze this business domain with Domain-Driven Design. You MUST work in SMALL",
|
|
801
|
+
"incremental steps — never produce the whole model in one message, or you will be",
|
|
802
|
+
"truncated mid-JSON and lose work.",
|
|
680
803
|
"",
|
|
681
|
-
"
|
|
682
|
-
"
|
|
683
|
-
"2. ANALYZE the domain: actors, FR/NFR, assumptions, bounded contexts, entities, events , glossary",
|
|
684
|
-
"3. FILL IN all empty tables in .pi/domain/exploration.md with your analysis",
|
|
685
|
-
"4. UPDATE .pi/domain/ubiquitous-language.md with the glossary terms",
|
|
804
|
+
"Session: " + sessionId,
|
|
805
|
+
"Business Context: " + sanitized,
|
|
686
806
|
"",
|
|
687
|
-
"
|
|
688
|
-
|
|
807
|
+
"STEP PROTOCOL (strict — one step per message, each message <= ~1,500 tokens of JSON):",
|
|
808
|
+
"",
|
|
809
|
+
"1. STRATEGIC PASS (this message): reply with ONLY this JSON, nothing else —",
|
|
810
|
+
'```json',
|
|
811
|
+
'{',
|
|
812
|
+
' "actors": [{ "name": "...", "description": "...", "interactions": "..." }],',
|
|
813
|
+
' "assumptions": [{ "assumption": "...", "impactIfWrong": "...", "mitigation": "..." }],',
|
|
814
|
+
' "boundedContexts": [',
|
|
815
|
+
' { "name": "...", "description": "...", "entities": ["..."],',
|
|
816
|
+
' "subdomainType": "core|supporting|generic", "subdomainRationale": "one line" }',
|
|
817
|
+
' ],',
|
|
818
|
+
' "contextMap": [{ "from": "A", "to": "B", "relationship": "partnership|shared-kernel|conformist|anticorruption-layer|open-host", "note": "..." }],',
|
|
819
|
+
' "openQuestions": "..."',
|
|
820
|
+
'}',
|
|
821
|
+
'```',
|
|
822
|
+
"then call domain_save_result with { sessionId, responseJson: <that JSON> }. It merges;",
|
|
823
|
+
"the file is saved for you. Do NOT also edit exploration.md by hand.",
|
|
689
824
|
"",
|
|
690
|
-
"
|
|
825
|
+
"2. PER-CONTEXT PASS (repeat for EACH core/tactical bounded context, one context per message):",
|
|
826
|
+
"reply with ONLY this JSON for one context, then call domain_save_result again:",
|
|
691
827
|
'```json',
|
|
692
|
-
|
|
693
|
-
' "
|
|
694
|
-
'
|
|
695
|
-
' "
|
|
696
|
-
'
|
|
697
|
-
'
|
|
698
|
-
'
|
|
699
|
-
'
|
|
700
|
-
' "
|
|
701
|
-
"
|
|
828
|
+
'{',
|
|
829
|
+
' "boundedContexts": [{ "name": "TheContext", "description": "...", "entities": ["Entity1"],',
|
|
830
|
+
' "invariants": [{ "rule": "...", "illegalStatePrevented": "..." }] }],',
|
|
831
|
+
' "entities": [',
|
|
832
|
+
' { "name": "Entity1", "context": "TheContext", "type": "entity|value-object|aggregate-root",',
|
|
833
|
+
' "description": "...",',
|
|
834
|
+
' "justification": { "identityPersists": true, "immutable": false, "equalByState": false } }',
|
|
835
|
+
' ],',
|
|
836
|
+
' "domainEvents": [',
|
|
837
|
+
' { "name": "Event", "context": "TheContext", "description": "...", "triggeredBy": "...",',
|
|
838
|
+
' "triage": { "domainExpertCares": true, "crossesAggregateBoundary": false, "needsAudit": false } }',
|
|
839
|
+
' ],',
|
|
840
|
+
' "ubiquitousLanguage": [{ "term": "...", "definition": "...", "boundedContext": "TheContext", "aliases": [], "examples": "" }]',
|
|
841
|
+
'}',
|
|
702
842
|
'```',
|
|
843
|
+
"Supporting/generic contexts need no detail pass — the strategic entry is enough.",
|
|
844
|
+
"",
|
|
845
|
+
"RULES:",
|
|
846
|
+
"- If a message of yours is cut off mid-JSON, continue from exactly where you stopped in the",
|
|
847
|
+
" NEXT message (never restart the whole model).",
|
|
848
|
+
"- Never emit the full model in one response and never exceed ~1,500 tokens of JSON per message.",
|
|
849
|
+
"- After the last context, run /domain --validate " + sessionId + " and report the checks.",
|
|
703
850
|
].join("\n");
|
|
851
|
+
|
|
704
852
|
|
|
705
853
|
try {
|
|
706
854
|
pi.sendMessage(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2026-09-
|
|
2
|
+
"timestamp": "2026-09-04T18:20:52Z",
|
|
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",
|