guardian-framework 0.1.127 → 0.1.129

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.127", 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.129", 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
  ".": {
@@ -12178,6 +12178,60 @@ function answerExploration(sessionId, responseJson, options) {
12178
12178
  warnings
12179
12179
  };
12180
12180
  }
12181
+ function moduleHasImplementation(projectDir, moduleName) {
12182
+ const sanitized = (moduleName || "").toLowerCase().replace(/[^a-z0-9]/g, "");
12183
+ if (!sanitized)
12184
+ return false;
12185
+ const roots = ["src", "crates", "packages", "internal", "lib", "app", "modules"];
12186
+ const exts = ".*.(java|kt|ts|tsx|js|jsx|rs|py|go)$";
12187
+ for (const root of roots) {
12188
+ const base = path2.join(projectDir, root);
12189
+ if (!fs.existsSync(base))
12190
+ continue;
12191
+ try {
12192
+ const walk = (dir, depth) => {
12193
+ if (depth > 6)
12194
+ return false;
12195
+ let entries;
12196
+ try {
12197
+ entries = fs.readdirSync(dir, { withFileTypes: true });
12198
+ } catch {
12199
+ return false;
12200
+ }
12201
+ for (const e of entries) {
12202
+ if (e.name === "node_modules" || e.name === "target" || e.name === "dist" || e.name === ".git")
12203
+ continue;
12204
+ const full = path2.join(dir, e.name);
12205
+ if (e.isDirectory()) {
12206
+ if (e.name === sanitized && hasSourceBelow(full, depth))
12207
+ return true;
12208
+ if (walk(full, depth + 1))
12209
+ return true;
12210
+ }
12211
+ }
12212
+ return false;
12213
+ };
12214
+ const hasSourceBelow = (dir, depth) => {
12215
+ let entries;
12216
+ try {
12217
+ entries = fs.readdirSync(dir, { withFileTypes: true });
12218
+ } catch {
12219
+ return false;
12220
+ }
12221
+ for (const e of entries) {
12222
+ if (e.isFile() && new RegExp(exts).test(e.name) && !/test|spec/i.test(e.name))
12223
+ return true;
12224
+ if (e.isDirectory() && e.name !== "node_modules" && e.name !== "target" && depth < 6 && hasSourceBelow(path2.join(dir, e.name), depth + 1))
12225
+ return true;
12226
+ }
12227
+ return false;
12228
+ };
12229
+ if (walk(base, 0))
12230
+ return true;
12231
+ } catch {}
12232
+ }
12233
+ return false;
12234
+ }
12181
12235
  function readExplorationSession(sessionId, projectDir) {
12182
12236
  const root = projectDir ?? process.cwd();
12183
12237
  const filePath = path2.join(root, ".pi", "domain", "exploration", `${sessionId}.md`);
@@ -12376,8 +12430,18 @@ function scaffoldFromExploration(sessionId, options) {
12376
12430
  const depthHeaderNote = bc.subdomainType === undefined || bc.subdomainType === null ? " (unclassified \u2014 defaulted to tactical; re-run exploration with ADR-008 depth)" : "";
12377
12431
  const contextEdges = (result.contextMap || []).filter((e) => e.from === bc.name || e.to === bc.name);
12378
12432
  const invariants = bc.invariants || [];
12433
+ const force = options?.overwrite === true;
12379
12434
  if (!dryRun && fs.existsSync(filePath)) {
12380
- warnings.push(`Module already exists, will be overwritten: ${fileName}`);
12435
+ const implemented = moduleHasImplementation(projectDir, moduleName);
12436
+ if (implemented && !force) {
12437
+ warnings.push(`Module is IMPLEMENTED, preserved (use overwrite:true to force): ${fileName}`);
12438
+ continue;
12439
+ }
12440
+ if (implemented && force) {
12441
+ warnings.push(`Overwriting implemented module doc (--force): ${fileName}`);
12442
+ } else {
12443
+ warnings.push(`Refreshing planned module doc (no implementation yet): ${fileName}`);
12444
+ }
12381
12445
  }
12382
12446
  const aggregateRoots = contextEntities.filter((e) => e.type === "aggregate-root");
12383
12447
  const regularEntities = contextEntities.filter((e) => e.type === "entity");
@@ -13150,7 +13214,7 @@ async function runDomain(targetDir, args, options) {
13150
13214
  await runExplore(targetDir, options);
13151
13215
  break;
13152
13216
  case "scaffold":
13153
- await runScaffold(targetDir, args[1], options);
13217
+ await runScaffold(targetDir, args[1], { ...options, overwrite: args.includes("--force") });
13154
13218
  break;
13155
13219
  case "answer":
13156
13220
  await runAnswer(targetDir, args[1], args[2], options);
@@ -13257,7 +13321,8 @@ async function runScaffold(targetDir, sessionId, options) {
13257
13321
  try {
13258
13322
  const result = scaffoldFromExploration(sessionId, {
13259
13323
  projectDir: targetDir,
13260
- dryRun: options.dryRun
13324
+ dryRun: options.dryRun,
13325
+ overwrite: options.overwrite
13261
13326
  });
13262
13327
  if (options.dryRun) {
13263
13328
  outro(`Dry-run: would generate ${result.modulesGenerated} module(s)`);
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.127", 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.129", 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
  ".": {
@@ -12502,6 +12502,60 @@ function answerExploration(sessionId, responseJson, options) {
12502
12502
  warnings
12503
12503
  };
12504
12504
  }
12505
+ function moduleHasImplementation(projectDir, moduleName) {
12506
+ const sanitized = (moduleName || "").toLowerCase().replace(/[^a-z0-9]/g, "");
12507
+ if (!sanitized)
12508
+ return false;
12509
+ const roots = ["src", "crates", "packages", "internal", "lib", "app", "modules"];
12510
+ const exts = ".*.(java|kt|ts|tsx|js|jsx|rs|py|go)$";
12511
+ for (const root of roots) {
12512
+ const base = path11.join(projectDir, root);
12513
+ if (!fs12.existsSync(base))
12514
+ continue;
12515
+ try {
12516
+ const walk = (dir, depth) => {
12517
+ if (depth > 6)
12518
+ return false;
12519
+ let entries;
12520
+ try {
12521
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
12522
+ } catch {
12523
+ return false;
12524
+ }
12525
+ for (const e of entries) {
12526
+ if (e.name === "node_modules" || e.name === "target" || e.name === "dist" || e.name === ".git")
12527
+ continue;
12528
+ const full = path11.join(dir, e.name);
12529
+ if (e.isDirectory()) {
12530
+ if (e.name === sanitized && hasSourceBelow(full, depth))
12531
+ return true;
12532
+ if (walk(full, depth + 1))
12533
+ return true;
12534
+ }
12535
+ }
12536
+ return false;
12537
+ };
12538
+ const hasSourceBelow = (dir, depth) => {
12539
+ let entries;
12540
+ try {
12541
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
12542
+ } catch {
12543
+ return false;
12544
+ }
12545
+ for (const e of entries) {
12546
+ if (e.isFile() && new RegExp(exts).test(e.name) && !/test|spec/i.test(e.name))
12547
+ return true;
12548
+ if (e.isDirectory() && e.name !== "node_modules" && e.name !== "target" && depth < 6 && hasSourceBelow(path11.join(dir, e.name), depth + 1))
12549
+ return true;
12550
+ }
12551
+ return false;
12552
+ };
12553
+ if (walk(base, 0))
12554
+ return true;
12555
+ } catch {}
12556
+ }
12557
+ return false;
12558
+ }
12505
12559
  function readExplorationSession(sessionId, projectDir) {
12506
12560
  const root = projectDir ?? process.cwd();
12507
12561
  const filePath = path11.join(root, ".pi", "domain", "exploration", `${sessionId}.md`);
@@ -12700,8 +12754,18 @@ function scaffoldFromExploration(sessionId, options) {
12700
12754
  const depthHeaderNote = bc.subdomainType === undefined || bc.subdomainType === null ? " (unclassified — defaulted to tactical; re-run exploration with ADR-008 depth)" : "";
12701
12755
  const contextEdges = (result.contextMap || []).filter((e) => e.from === bc.name || e.to === bc.name);
12702
12756
  const invariants = bc.invariants || [];
12757
+ const force = options?.overwrite === true;
12703
12758
  if (!dryRun && fs12.existsSync(filePath)) {
12704
- warnings.push(`Module already exists, will be overwritten: ${fileName}`);
12759
+ const implemented = moduleHasImplementation(projectDir, moduleName);
12760
+ if (implemented && !force) {
12761
+ warnings.push(`Module is IMPLEMENTED, preserved (use overwrite:true to force): ${fileName}`);
12762
+ continue;
12763
+ }
12764
+ if (implemented && force) {
12765
+ warnings.push(`Overwriting implemented module doc (--force): ${fileName}`);
12766
+ } else {
12767
+ warnings.push(`Refreshing planned module doc (no implementation yet): ${fileName}`);
12768
+ }
12705
12769
  }
12706
12770
  const aggregateRoots = contextEntities.filter((e) => e.type === "aggregate-root");
12707
12771
  const regularEntities = contextEntities.filter((e) => e.type === "entity");
@@ -13474,7 +13538,7 @@ async function runDomain(targetDir, args, options) {
13474
13538
  await runExplore(targetDir, options);
13475
13539
  break;
13476
13540
  case "scaffold":
13477
- await runScaffold(targetDir, args[1], options);
13541
+ await runScaffold(targetDir, args[1], { ...options, overwrite: args.includes("--force") });
13478
13542
  break;
13479
13543
  case "answer":
13480
13544
  await runAnswer(targetDir, args[1], args[2], options);
@@ -13581,7 +13645,8 @@ async function runScaffold(targetDir, sessionId, options) {
13581
13645
  try {
13582
13646
  const result = scaffoldFromExploration(sessionId, {
13583
13647
  projectDir: targetDir,
13584
- dryRun: options.dryRun
13648
+ dryRun: options.dryRun,
13649
+ overwrite: options.overwrite
13585
13650
  });
13586
13651
  if (options.dryRun) {
13587
13652
  outro(`Dry-run: would generate ${result.modulesGenerated} module(s)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.127",
3
+ "version": "0.1.129",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -239,6 +239,45 @@ function mergeRawJson(existing: Record<string, unknown>, incoming: Record<string
239
239
  return merged;
240
240
  }
241
241
 
242
+ // True when a module has real implementation code (not just a planned doc).
243
+ function moduleHasCode(cwd: string, moduleName: string): boolean {
244
+ const sanitized = (moduleName || "").toLowerCase().replace(/[^a-z0-9]/g, "");
245
+ if (!sanitized) return false;
246
+ const roots = ["src", "crates", "packages", "internal", "lib", "app", "modules"];
247
+ const codeFile = (n: string): boolean => /.*\.(java|kt|ts|tsx|js|jsx|rs|py|go)$/.test(n) && !/test|spec/i.test(n);
248
+ const walk = (dir: string, depth: number): boolean => {
249
+ if (depth > 6) return false;
250
+ let entries;
251
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return false; }
252
+ for (const e of entries) {
253
+ if (["node_modules", "target", "dist", ".git"].includes(e.name)) continue;
254
+ const full = path.join(dir, e.name);
255
+ if (e.isFile() && codeFile(e.name)) return true;
256
+ if (e.isDirectory() && (e.name === sanitized || walk(full, depth + 1))) {
257
+ // entered the module dir: any code file below counts
258
+ if (e.name === sanitized && hasCodeBelow(full, depth)) return true;
259
+ }
260
+ }
261
+ return false;
262
+ };
263
+ const hasCodeBelow = (dir: string, depth: number): boolean => {
264
+ let entries;
265
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return false; }
266
+ for (const e of entries) {
267
+ if (["node_modules", "target", "dist"].includes(e.name)) continue;
268
+ const full = path.join(dir, e.name);
269
+ if (e.isFile() && codeFile(e.name)) return true;
270
+ if (e.isDirectory() && depth < 6 && hasCodeBelow(full, depth + 1)) return true;
271
+ }
272
+ return false;
273
+ };
274
+ for (const r of roots) {
275
+ const base = path.join(cwd, r);
276
+ if (fs.existsSync(base) && walk(base, 0)) return true;
277
+ }
278
+ return false;
279
+ }
280
+
242
281
  function buildExplorationPrompt(context: string): string {
243
282
  return [
244
283
  "You are a Domain-Driven Design expert. Analyze the following business",
@@ -978,10 +1017,12 @@ export default function (pi: ExtensionAPI) {
978
1017
  // /domain --architect-scaffold <session-id>
979
1018
  // /domain --architect-scaffold <session-id>
980
1019
  if (trimmed.startsWith("--architect-scaffold")) {
981
- const sessionId = trimmed.slice("--architect-scaffold".length).trim();
1020
+ const rest = trimmed.slice("--architect-scaffold".length).trim();
1021
+ const forceScaffold = rest.includes("--force");
1022
+ const sessionId = rest.split(/\s+/)[0] || "";
982
1023
  if (!sessionId) {
983
1024
  ctx.ui.notify(
984
- "Usage: /domain --architect-scaffold <session-id>",
1025
+ "Usage: /domain --architect-scaffold <session-id> [--force]",
985
1026
  "error",
986
1027
  );
987
1028
  return "(domain command handled)";
@@ -1056,7 +1097,7 @@ export default function (pi: ExtensionAPI) {
1056
1097
  let scaffoldError = "";
1057
1098
 
1058
1099
  try {
1059
- const cmd = "guardian-framework domain scaffold " + sessionId + " 2>&1";
1100
+ const cmd = "guardian-framework domain scaffold " + sessionId + (forceScaffold ? " --force" : "") + " 2>&1";
1060
1101
  const shellResult = await shellExec(ctx, cmd);
1061
1102
  if (shellResult.exitCode === 0) {
1062
1103
  const outputLines = shellResult.stdout.split("\n");
@@ -1079,8 +1120,13 @@ export default function (pi: ExtensionAPI) {
1079
1120
  ctx.ui.notify("Module scaffold unavailable - agent can create docs directly", "info");
1080
1121
  }
1081
1122
 
1082
- // Step 2: Always regenerate ADR-001 to reflect current session
1123
+ // Step 2: Refresh ADR-001 only when it does not exist yet (or --force).
1124
+ // Never clobber an existing ADR that documents implemented architecture.
1083
1125
  const adrPath = path.join(decisionsDir, "ADR-001-architecture-pattern.md");
1126
+ if (fs.existsSync(adrPath) && !forceScaffold) {
1127
+ ctx.ui.notify("Existing ADR-001 preserved — module docs scaffolded (use --force to refresh ADR)", "info");
1128
+ return "(domain command handled)";
1129
+ }
1084
1130
  const bcList = bcNames.length > 0
1085
1131
  ? bcNames.map(n => " - " + n).join("\n")
1086
1132
  : " - (to be defined during architecture planning)";
@@ -1229,9 +1275,11 @@ export default function (pi: ExtensionAPI) {
1229
1275
  return resultLines.join("\n");
1230
1276
  }
1231
1277
  if (trimmed.startsWith("--enrich")) {
1232
- const sessionId = trimmed.slice("--enrich".length).trim();
1278
+ const restE = trimmed.slice("--enrich".length).trim();
1279
+ const forceEnrich = restE.includes("--force");
1280
+ const sessionId = restE.split(/\s+/)[0] || "";
1233
1281
  if (!sessionId) {
1234
- ctx.ui.notify("Usage: /domain --enrich <session-id>", "error");
1282
+ ctx.ui.notify("Usage: /domain --enrich <session-id> [--force]", "error");
1235
1283
  return "(domain command handled)";
1236
1284
  }
1237
1285
 
@@ -1244,9 +1292,47 @@ export default function (pi: ExtensionAPI) {
1244
1292
  return "(domain command handled)";
1245
1293
  }
1246
1294
 
1247
- const moduleFiles = fs.readdirSync(modulesDir).filter(f => f.endsWith(".md") && f !== "module-template.md");
1295
+ // Enrichment is SESSION-SCOPED: only module docs that belong to THIS
1296
+ // session's bounded contexts may be touched, and existing module docs
1297
+ // are preserved unless --force. Prevents clobbering other streams.
1298
+ const allModules = fs.readdirSync(modulesDir).filter(f => f.endsWith(".md") && f !== "module-template.md");
1299
+ const kebab = (n: string) => n.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
1300
+ let sessionModules: string[] = [];
1301
+ const sRawPath = path.join(explorationDir, sessionId + ".raw.json");
1302
+ if (fs.existsSync(sRawPath)) {
1303
+ try {
1304
+ const rawS = JSON.parse(fs.readFileSync(sRawPath, "utf-8")) as Record<string, unknown>;
1305
+ const bcs = Array.isArray(rawS.boundedContexts) ? (rawS.boundedContexts as Record<string, unknown>[]) : [];
1306
+ sessionModules = bcs.map((bc) => kebab(String(bc.name ?? "")) + ".md").filter((n) => allModules.includes(n));
1307
+ } catch { /* ignore */ }
1308
+ }
1309
+ if (sessionModules.length === 0) {
1310
+ // fallback: session md bounded-contexts table
1311
+ const md = fs.existsSync(sessionPath) ? fs.readFileSync(sessionPath, "utf-8") : "";
1312
+ const m = md.match(/## Bounded Contexts[\s\S]*?\n((?:\|.*\n)+?)(?=\n##|$)/);
1313
+ if (m) {
1314
+ sessionModules = m[1].split("\n")
1315
+ .map((l) => l.split("|").map((c) => c.trim()).filter(Boolean)[0])
1316
+ .filter(Boolean)
1317
+ .map((n) => kebab(n) + ".md")
1318
+ .filter((n) => allModules.includes(n));
1319
+ }
1320
+ }
1321
+ const preserved: string[] = [];
1322
+ const moduleFiles = sessionModules.filter((f) => {
1323
+ const exists = fs.existsSync(path.join(modulesDir, f));
1324
+ const implemented = moduleHasCode(ctx.cwd, f.replace(/\.md$/, ""));
1325
+ if (exists && implemented && !forceEnrich) {
1326
+ preserved.push(f);
1327
+ return false;
1328
+ }
1329
+ return true;
1330
+ });
1331
+ if (preserved.length > 0) {
1332
+ ctx.ui.notify("Preserving existing module docs (use --force to enrich): " + preserved.join(", "), "info");
1333
+ }
1248
1334
  if (moduleFiles.length === 0) {
1249
- ctx.ui.notify("No module docs found. Run /domain --architect-scaffold " + sessionId + " first.", "error");
1335
+ ctx.ui.notify("No new module docs for this session. Existing docs preserved — no enrich needed (or pass --force).", "error");
1250
1336
  return "(domain command handled)";
1251
1337
  }
1252
1338
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-09-06T11:35:20Z",
2
+ "timestamp": "2026-09-06T13:20:31Z",
3
3
  "mode": "all",
4
4
  "stages_run": [],
5
5
  "summary": {