guardian-framework 0.1.74 → 0.1.76

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.74", 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.76", 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.74", 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.76", 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.74",
3
+ "version": "0.1.76",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -72,6 +72,15 @@ export function readLanguage(cwd: string): string {
72
72
  return "typescript";
73
73
  }
74
74
 
75
+ /**
76
+ * URL-encode a GitLab project/group path for API calls.
77
+ * GitLab API requires nested paths to be URL-encoded:
78
+ * "group/subgroup/project" -> "group%2Fsubgroup%2Fproject"
79
+ */
80
+ export function glabEncodePath(rawPath: string): string {
81
+ return encodeURIComponent(rawPath);
82
+ }
83
+
75
84
  /**
76
85
  * Get forge base URL. For GitLab, detects self-hosted instances via glab config.
77
86
  */
@@ -142,10 +151,11 @@ export function verifyForgeAccess(cwd: string): { authenticated: boolean; projec
142
151
 
143
152
  // Verify project-level access
144
153
  if (repository) {
154
+ const encodedRepo = repoTool === "glab" ? glabEncodePath(repository) : repository;
145
155
  const projectCheck = runScript(
146
156
  cwd,
147
157
  repoTool === "glab"
148
- ? `glab api "projects/${repository}" --method GET 2>/dev/null | jq -r '.id // empty'`
158
+ ? `glab api "projects/${encodedRepo}" --method GET 2>/dev/null | jq -r '.id // empty'`
149
159
  : `gh api "repos/${repository}" --method GET 2>/dev/null | jq -r '.id // empty'`,
150
160
  );
151
161
  if (projectCheck.exitCode === 0 && projectCheck.stdout.trim().length > 0) {
@@ -10,6 +10,7 @@ import {
10
10
  commandExists as adapterCommandExists,
11
11
  runScript as adapterRunScript,
12
12
  verifyForgeAccess,
13
+ glabEncodePath,
13
14
  } from "./forge-adapter.ts";
14
15
  // ── Constants ──
15
16
 
@@ -42,16 +43,17 @@ function execSyncWithRetry(
42
43
  cwd: string,
43
44
  opts: { timeout?: number; maxRetries?: number; delayMs?: number } = {},
44
45
  ): { exitCode: number; stdout: string } {
45
- const maxRetries = opts.maxRetries ?? 2;
46
- const delayMs = opts.delayMs ?? 2000;
46
+ const maxRetries = opts.maxRetries ?? 4;
47
+ const delayMs = opts.delayMs ?? 5000;
47
48
  let lastError = "";
48
49
 
49
50
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
50
51
  if (attempt > 0) {
51
- const wait = delayMs * Math.pow(2, attempt - 1);
52
- const waitSec = Math.ceil(wait / 1000);
52
+ const wait = delayMs * Math.pow(1.5, attempt - 1);
53
+ const waitMs = Math.min(wait, 30_000);
54
+ const waitSec = Math.ceil(waitMs / 1000);
53
55
  try {
54
- execFileSync("sleep", [String(waitSec)], { cwd, timeout: 30_000, encoding: "utf-8" });
56
+ execFileSync("sleep", [String(waitSec)], { cwd, timeout: 60_000, encoding: "utf-8" });
55
57
  } catch { /* ignore */ }
56
58
  }
57
59
  try {
@@ -128,11 +130,12 @@ export function createGitlabEpic(
128
130
  );
129
131
 
130
132
  if (result.exitCode !== 0) {
131
- // Fallback: try the API directly
133
+ // Fallback: try the API directly (URL-encode nested group paths)
134
+ const encodedGroup = glabEncodePath(group);
132
135
  const body = JSON.stringify({ title, description: desc.slice(0, 5000) });
133
136
  const apiResult = runScript(
134
137
  cwd,
135
- `glab api --method POST "groups/${group}/epics" --body '${body}' 2>/dev/null | jq -r '.iid // empty'`,
138
+ `glab api --method POST "groups/${encodedGroup}/epics" --body '${body}' 2>/dev/null | jq -r '.iid // empty'`,
136
139
  );
137
140
  if (apiResult.exitCode !== 0 || !apiResult.stdout.trim()) {
138
141
  console.warn(`Failed to create GitLab epic: ${result.stdout || apiResult.stdout}`);
@@ -197,22 +200,60 @@ export function ensureRemoteRepo(
197
200
 
198
201
  // GitLab path — detect self-hosted base URL from glab config
199
202
  const glabBaseUrl = getGitBaseUrl("glab");
200
- const createResult = runScript(
201
- cwd,
202
- `glab repo create "${repository}" --private --description "Epic: ${epicName}" 2>&1`,
203
- );
203
+
204
+ // Split repository path into group + project name for glab CLI
205
+ const parts = repository.split("/");
206
+ const projectName = parts.pop()!;
207
+ const group = parts.join("/");
208
+
209
+ const createResult = group
210
+ ? runScript(
211
+ cwd,
212
+ `glab repo create "${projectName}" --group "${group}" --private --description "Epic: ${epicName}" 2>&1`,
213
+ )
214
+ : runScript(
215
+ cwd,
216
+ `glab repo create "${repository}" --private --description "Epic: ${epicName}" 2>&1`,
217
+ );
218
+
219
+ let projectId = "";
204
220
  if (createResult.exitCode !== 0) {
205
221
  // Repo may already exist — verify via API before giving up
222
+ const encodedPath = glabEncodePath(repository);
206
223
  const viewResult = runScript(
207
224
  cwd,
208
- `glab api "projects/${repository}" --method GET 2>/dev/null | jq -r '.id // empty'`,
225
+ `glab api "projects/${encodedPath}" --method GET 2>/dev/null | jq -r '.id // empty'`,
209
226
  );
210
227
  if (viewResult.exitCode !== 0 || !viewResult.stdout.trim()) {
211
228
  console.warn(`Failed to create or find GitLab project: ${createResult.stdout}`);
212
229
  return "";
213
230
  }
214
- console.warn(`GitLab project already exists, connecting to it.`);
231
+ projectId = viewResult.stdout.trim();
232
+ console.warn(`GitLab project already exists (id=${projectId}), connecting to it.`);
233
+ } else {
234
+ // Project was just created — wait for GitLab to provision it
235
+ // (new projects can take 10-30s before accepting issues)
236
+ const encodedPath = glabEncodePath(repository);
237
+ console.warn(`Waiting for GitLab project to be ready...`);
238
+ for (let attempt = 0; attempt < 12; attempt++) {
239
+ const checkResult = runScript(
240
+ cwd,
241
+ `glab api "projects/${encodedPath}" --method GET 2>/dev/null | jq -r '.id // empty'`,
242
+ );
243
+ if (checkResult.exitCode === 0 && checkResult.stdout.trim()) {
244
+ projectId = checkResult.stdout.trim();
245
+ console.warn(`GitLab project ready (id=${projectId})`);
246
+ break;
247
+ }
248
+ if (attempt < 11) {
249
+ try { execFileSync("sleep", ["5"], { cwd, timeout: 30_000 }); } catch { /* ignore */ }
250
+ }
251
+ }
252
+ if (!projectId) {
253
+ console.warn(`GitLab project not ready after 60s, continuing anyway.`);
254
+ }
215
255
  }
256
+
216
257
  runScript(cwd, "git remote remove origin 2>/dev/null");
217
258
  const httpsUrl = `${glabBaseUrl}/${repository}.git`;
218
259
  runScript(cwd, `git remote add origin "${httpsUrl}"`);
@@ -176,6 +176,16 @@ class EpicManager {
176
176
  }
177
177
  }
178
178
 
179
+ // Loud warning if GitLab remote setup failed
180
+ if (repoTool === "glab" && !hasRemote) {
181
+ console.warn(
182
+ "⚠️ GitLab remote repo setup failed. Pipeline will run LOCAL-ONLY."
183
+ + " Issues will be created as local .md files, not on GitLab."
184
+ + " Check that the repository path in guardian-manifest.json exists"
185
+ + " and your GitLab token has 'write_repository' scope.",
186
+ );
187
+ }
188
+
179
189
  const issues: { id: string; title: string; status: string; remoteIssueId?: string | null }[] = [];
180
190
  const issuesDir = join(this.cwd, ".pi/issues");
181
191
  if (!existsSync(issuesDir)) mkdirSync(issuesDir, { recursive: true });
@@ -303,7 +313,7 @@ class EpicManager {
303
313
  issues.push(readinessEntry);
304
314
 
305
315
  // ── TDD test generation ──
306
- let tddTestFiles: string[] = [];
316
+ const tddTestFiles: string[] = [];
307
317
  if (tdd) {
308
318
  const language = readLanguage(this.cwd);
309
319
  if (isTddSupported(language)) {
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-07-13T11:51:20Z",
2
+ "timestamp": "2026-07-13T12:58:14Z",
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",