guardian-framework 0.1.80 → 0.1.82

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
@@ -3197,7 +3197,7 @@ __export(exports_package, {
3197
3197
  bin: () => bin,
3198
3198
  author: () => author
3199
3199
  });
3200
- var name = "guardian-framework", version = "0.1.80", 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;
3200
+ var name = "guardian-framework", version = "0.1.82", 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;
3201
3201
  var init_package = __esm(() => {
3202
3202
  exports = {
3203
3203
  ".": {
package/dist/exports.js CHANGED
@@ -1982,7 +1982,7 @@ __export(exports_package, {
1982
1982
  bin: () => bin,
1983
1983
  author: () => author
1984
1984
  });
1985
- var name = "guardian-framework", version = "0.1.80", 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;
1985
+ var name = "guardian-framework", version = "0.1.82", 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;
1986
1986
  var init_package = __esm(() => {
1987
1987
  exports = {
1988
1988
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.80",
3
+ "version": "0.1.82",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -9,10 +9,59 @@
9
9
  * reach the agent. The agent makes them green via Red→Green→Refactor.
10
10
  */
11
11
 
12
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import type { ModuleComponent } from "./types.ts";
15
15
 
16
+ // ── Java package derivation (from project manifest) ──
17
+
18
+ /** Read groupId from guardian-manifest.json (never from pom.xml). */
19
+ function readGroupId(cwd: string): string {
20
+ try {
21
+ const raw = readFileSync(join(cwd, "guardian-manifest.json"), "utf-8");
22
+ const manifest = JSON.parse(raw);
23
+ if (manifest.groupId && typeof manifest.groupId === "string") {
24
+ return manifest.groupId;
25
+ }
26
+ } catch {}
27
+ return "com.example";
28
+ }
29
+
30
+ /**
31
+ * Read the module→Java-package mapping from guardian-manifest.json.
32
+ * The manifest should have a `javaModulePackages` field:
33
+ * \`\`\`json
34
+ * {
35
+ * "javaModulePackages": {
36
+ * "integration-acl": "acl",
37
+ * "order-intake": "orderintake"
38
+ * }
39
+ * }
40
+ * \`\`\`
41
+ * Missing modules throw — no silent fallback to bad paths.
42
+ */
43
+ function readJavaModulePackages(cwd: string): Record<string, string> {
44
+ try {
45
+ const raw = readFileSync(join(cwd, "guardian-manifest.json"), "utf-8");
46
+ const manifest = JSON.parse(raw);
47
+ if (manifest.javaModulePackages && typeof manifest.javaModulePackages === "object") {
48
+ return manifest.javaModulePackages;
49
+ }
50
+ } catch {}
51
+ return {};
52
+ }
53
+
54
+ /** Resolve a module ID to its Java sub-package suffix. Throws if unknown. */
55
+ function javaModulePackage(cwd: string, moduleId: string): string {
56
+ const mapping = readJavaModulePackages(cwd);
57
+ const pkg = mapping[moduleId];
58
+ if (pkg) return pkg;
59
+ throw new Error(
60
+ `Unknown Java package for module "${moduleId}". ` +
61
+ `Add it to the "javaModulePackages" field in guardian-manifest.json.`
62
+ );
63
+ }
64
+
16
65
  // ── Supported Languages ──
17
66
 
18
67
  const TDD_LANGUAGES = new Set(["typescript", "rust", "python", "go", "java"]);
@@ -60,8 +109,14 @@ export function generateEpicTestFiles(options: TddGenerateOptions): string[] {
60
109
  let modulePath: string;
61
110
 
62
111
  if (language === "java") {
63
- testBaseDir = testBaseDir ?? "src/test/java";
64
- modulePath = (moduleName || moduleId).replace(/[^a-zA-Z0-9\/]/g, "-");
112
+ // Java: src/test/java/<groupId-path>/<module-package>/
113
+ // All components in the same module share the same package dir.
114
+ const groupId = readGroupId(cwd);
115
+ const groupPath = groupId.replace(/\./g, "/");
116
+ const modPkg = javaModulePackage(cwd, moduleName || moduleId);
117
+ const modPath = modPkg.replace(/\./g, "/");
118
+ testBaseDir = testBaseDir ?? join("src", "test", "java");
119
+ modulePath = join(groupPath, modPath);
65
120
  } else {
66
121
  testBaseDir = testBaseDir ?? "tests/unit";
67
122
  modulePath = moduleName || moduleId;
@@ -72,7 +127,19 @@ export function generateEpicTestFiles(options: TddGenerateOptions): string[] {
72
127
  for (const comp of components) {
73
128
  const componentId = comp.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
74
129
 
75
- const testDir = join(cwd, testBaseDir, modulePath, componentId);
130
+ let testDir: string;
131
+ let javaPackage: string | undefined;
132
+
133
+ if (language === "java") {
134
+ // No componentId subdir — all components in same package dir
135
+ testDir = join(cwd, testBaseDir, modulePath);
136
+ const groupId = readGroupId(cwd);
137
+ const modPkg = javaModulePackage(cwd, moduleName || moduleId);
138
+ javaPackage = `${groupId}.${modPkg}`;
139
+ } else {
140
+ testDir = join(cwd, testBaseDir, modulePath, componentId);
141
+ }
142
+
76
143
  mkdirSync(testDir, { recursive: true });
77
144
 
78
145
  const fileName = getTestFileName(componentId, language);
@@ -81,7 +148,7 @@ export function generateEpicTestFiles(options: TddGenerateOptions): string[] {
81
148
  // Skip if file already exists (don't overwrite user edits)
82
149
  if (existsSync(filePath)) continue;
83
150
 
84
- const content = generateTestContent(comp, language, moduleId, componentId);
151
+ const content = generateTestContent(comp, language, moduleId, componentId, javaPackage);
85
152
  writeFileSync(filePath, content, "utf-8");
86
153
  generated.push(filePath);
87
154
  }
@@ -115,6 +182,7 @@ function generateTestContent(
115
182
  language: string,
116
183
  moduleId: string,
117
184
  componentId: string,
185
+ javaPackage?: string,
118
186
  ): string {
119
187
  switch (language) {
120
188
  case "rust":
@@ -124,7 +192,7 @@ function generateTestContent(
124
192
  case "go":
125
193
  return generateGo(component, moduleId, componentId);
126
194
  case "java":
127
- return generateJava(component, moduleId, componentId);
195
+ return generateJava(component, moduleId, componentId, javaPackage);
128
196
  case "typescript":
129
197
  default:
130
198
  return generateTypeScript(component, moduleId, componentId);
@@ -136,7 +204,7 @@ function generateTestContent(
136
204
  function generateTypeScript(
137
205
  component: ModuleComponent,
138
206
  moduleId: string,
139
- _componentId: string,
207
+ componentId: string,
140
208
  ): string {
141
209
  const lines: string[] = [
142
210
  `// Generated by Guardian TDD`,
@@ -310,6 +378,7 @@ function generateJava(
310
378
  component: ModuleComponent,
311
379
  moduleId: string,
312
380
  componentId: string,
381
+ javaPackage?: string,
313
382
  ): string {
314
383
  const clsName = toPascalCase(componentId);
315
384
  const lines: string[] = [
@@ -317,6 +386,14 @@ function generateJava(
317
386
  `// From: ${component.name} (${moduleId} module)`,
318
387
  `// These tests will FAIL until ${component.name} is implemented.`,
319
388
  ``,
389
+ ];
390
+
391
+ // Always include package declaration for Java
392
+ if (javaPackage) {
393
+ lines.push(`package ${javaPackage};`, ``);
394
+ }
395
+
396
+ lines.push(
320
397
  `import org.junit.jupiter.api.Test;`,
321
398
  `import static org.junit.jupiter.api.Assertions.*;`,
322
399
  ``,
@@ -329,10 +406,10 @@ function generateJava(
329
406
  ` // assertNotNull(instance);`,
330
407
  ` fail("Test not yet implemented");`,
331
408
  ` }`,
332
- ];
409
+ );
333
410
 
334
411
  for (const dep of component.dependencies) {
335
- lines.push(...[
412
+ lines.push(
336
413
  ``,
337
414
  ` @Test`,
338
415
  ` void test${clsName}_InteractsWith_${capitalize(dep.replace(/[^a-zA-Z0-9]/g, ""))}() {`,
@@ -341,11 +418,10 @@ function generateJava(
341
418
  ` // ${clsName} instance = new ${clsName}(mock);`,
342
419
  ` fail("Dependency test not yet implemented");`,
343
420
  ` }`,
344
- ]);
421
+ );
345
422
  }
346
423
 
347
- lines.push(`}`);
348
- lines.push(``);
424
+ lines.push(`}`, ``);
349
425
  return lines.join("\n");
350
426
  }
351
427
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-07-16T10:04:14Z",
2
+ "timestamp": "2026-07-16T13:04:53Z",
3
3
  "mode": "all",
4
4
  "stages_run": [],
5
5
  "summary": {
@@ -29,7 +29,9 @@ module/
29
29
  │ ├── repository/ # Repository interfaces
30
30
  │ │ └── EntityRepository.java
31
31
  │ └── error/ # Typed exception classes
32
- │ └── DomainError.java
32
+ │ ├── DomainException.java # Abstract base for all domain errors
33
+ │ ├── DomainError.java # Concrete domain error
34
+ │ └── ErrorCode.java # Machine-readable error codes
33
35
  ├── application/ # Service interfaces and DTOs
34
36
  │ ├── service/ # Service interfaces
35
37
  │ │ └── EntityService.java
@@ -177,25 +179,39 @@ public enum Status {
177
179
  ## 3. Domain Error Handling
178
180
 
179
181
  ```java
180
- // domain/error/DomainError.java
182
+ // domain/error/DomainException.java
181
183
 
182
184
  package com.project.module.domain.error;
183
185
 
184
- public class DomainError extends RuntimeException {
185
-
186
+ /**
187
+ * Base exception for all domain errors.
188
+ * Extending this (rather than RuntimeException directly) allows
189
+ * catching all domain-level errors in a single handler.
190
+ */
191
+ public abstract class DomainException extends RuntimeException {
186
192
  private final ErrorCode code;
187
193
 
188
- public DomainError(String message) {
189
- super(message);
190
- this.code = ErrorCode.VALIDATION;
191
- }
192
-
193
- public DomainError(ErrorCode code, String message) {
194
+ protected DomainException(ErrorCode code, String message) {
194
195
  super(message);
195
196
  this.code = code;
196
197
  }
197
198
 
198
199
  public ErrorCode getCode() { return code; }
200
+ }
201
+
202
+ // domain/error/DomainError.java
203
+
204
+ package com.project.module.domain.error;
205
+
206
+ public class DomainError extends DomainException {
207
+
208
+ public DomainError(String message) {
209
+ super(ErrorCode.VALIDATION, message);
210
+ }
211
+
212
+ public DomainError(ErrorCode code, String message) {
213
+ super(code, message);
214
+ }
199
215
 
200
216
  public static DomainError notFound(String id) {
201
217
  return new DomainError(ErrorCode.NOT_FOUND, "Resource " + id + " not found");
@@ -221,7 +237,8 @@ public enum ErrorCode {
221
237
  ```
222
238
 
223
239
  ### Rules
224
- - ✅ Use `RuntimeException` subclasses for domain errors
240
+ - ✅ Use `DomainException` subclasses for domain errors (never `RuntimeException` directly)
241
+ - ✅ Define `DomainException` as an abstract base in `domain/error/`
225
242
  - ✅ Use `ErrorCode` enum for machine-readable error types
226
243
  - ✅ Use `record` for value objects (Java 16+)
227
244
  - ❌ Don't use Spring annotations in domain layer