guardian-framework 0.1.73 → 0.1.75
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 +1 -1
- package/dist/exports.js +1 -1
- package/package.json +1 -1
- package/templates/pi/extensions/architect-lib/forge-adapter.ts +11 -1
- package/templates/pi/extensions/architect-lib/helpers.ts +61 -5
- package/templates/pi/extensions/architect.ts +38 -4
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/scripts/git/create-tracking-issue.sh +1 -1
- package/templates/pi/scripts/git/link-issue-to-epic.sh +1 -1
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.
|
|
3200
|
+
var name = "guardian-framework", version = "0.1.75", 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.
|
|
1985
|
+
var name = "guardian-framework", version = "0.1.75", 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
|
@@ -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/${
|
|
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
|
|
|
@@ -105,6 +106,48 @@ export function createRemoteIssue(
|
|
|
105
106
|
return { success: false, issueNumber: null, error: "Could not parse issue number" };
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
// Create a real GitLab Epic via the glab CLI.
|
|
110
|
+
// Returns the epic IID (string) on success, null on failure.
|
|
111
|
+
export function createGitlabEpic(
|
|
112
|
+
cwd: string,
|
|
113
|
+
repository: string,
|
|
114
|
+
title: string,
|
|
115
|
+
description: string,
|
|
116
|
+
): string | null {
|
|
117
|
+
// Derive group from repository path: "group/subgroup/project" -> "group/subgroup"
|
|
118
|
+
const parts = repository.split("/");
|
|
119
|
+
if (parts.length < 2) return null;
|
|
120
|
+
const group = parts.slice(0, -1).join("/");
|
|
121
|
+
|
|
122
|
+
// Build description from markdown content (strip YAML frontmatter)
|
|
123
|
+
const desc = description.replace(/^---[\s\S]*?---\n*/, "").trim();
|
|
124
|
+
|
|
125
|
+
// Create epic via glab CLI
|
|
126
|
+
const result = runScript(
|
|
127
|
+
cwd,
|
|
128
|
+
`glab epic create --group "${group}" --title "${title}" --description "${desc.slice(0, 5000)}" 2>&1`,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (result.exitCode !== 0) {
|
|
132
|
+
// Fallback: try the API directly (URL-encode nested group paths)
|
|
133
|
+
const encodedGroup = glabEncodePath(group);
|
|
134
|
+
const body = JSON.stringify({ title, description: desc.slice(0, 5000) });
|
|
135
|
+
const apiResult = runScript(
|
|
136
|
+
cwd,
|
|
137
|
+
`glab api --method POST "groups/${encodedGroup}/epics" --body '${body}' 2>/dev/null | jq -r '.iid // empty'`,
|
|
138
|
+
);
|
|
139
|
+
if (apiResult.exitCode !== 0 || !apiResult.stdout.trim()) {
|
|
140
|
+
console.warn(`Failed to create GitLab epic: ${result.stdout || apiResult.stdout}`);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return apiResult.stdout.trim();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Parse epic IID from output: "https://gitlab.com/groups/.../-/epics/42"
|
|
147
|
+
const epicMatch = result.stdout.match(/epics\/(\d+)/);
|
|
148
|
+
return epicMatch ? epicMatch[1] : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
108
151
|
// Ensure the GitHub/GitLab repository exists and local git remote is configured.
|
|
109
152
|
// Returns the repository slug if remote is ready, empty string if not available.
|
|
110
153
|
export function ensureRemoteRepo(
|
|
@@ -156,15 +199,28 @@ export function ensureRemoteRepo(
|
|
|
156
199
|
|
|
157
200
|
// GitLab path — detect self-hosted base URL from glab config
|
|
158
201
|
const glabBaseUrl = getGitBaseUrl("glab");
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
202
|
+
|
|
203
|
+
// Split repository path into group + project name for glab CLI
|
|
204
|
+
const parts = repository.split("/");
|
|
205
|
+
const projectName = parts.pop()!;
|
|
206
|
+
const group = parts.join("/");
|
|
207
|
+
|
|
208
|
+
const createResult = group
|
|
209
|
+
? runScript(
|
|
210
|
+
cwd,
|
|
211
|
+
`glab repo create "${projectName}" --group "${group}" --private --description "Epic: ${epicName}" 2>&1`,
|
|
212
|
+
)
|
|
213
|
+
: runScript(
|
|
214
|
+
cwd,
|
|
215
|
+
`glab repo create "${repository}" --private --description "Epic: ${epicName}" 2>&1`,
|
|
216
|
+
);
|
|
217
|
+
|
|
163
218
|
if (createResult.exitCode !== 0) {
|
|
164
219
|
// Repo may already exist — verify via API before giving up
|
|
220
|
+
const encodedPath = glabEncodePath(repository);
|
|
165
221
|
const viewResult = runScript(
|
|
166
222
|
cwd,
|
|
167
|
-
`glab api "projects/${
|
|
223
|
+
`glab api "projects/${encodedPath}" --method GET 2>/dev/null | jq -r '.id // empty'`,
|
|
168
224
|
);
|
|
169
225
|
if (viewResult.exitCode !== 0 || !viewResult.stdout.trim()) {
|
|
170
226
|
console.warn(`Failed to create or find GitLab project: ${createResult.stdout}`);
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
readRepoTool,
|
|
29
29
|
runScript,
|
|
30
30
|
saveRoadmapState,
|
|
31
|
+
createGitlabEpic,
|
|
31
32
|
} from "./architect-lib/helpers";
|
|
32
33
|
import {
|
|
33
34
|
generateArchitectureReadinessMarkdown,
|
|
@@ -175,6 +176,16 @@ class EpicManager {
|
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
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
|
+
|
|
178
189
|
const issues: { id: string; title: string; status: string; remoteIssueId?: string | null }[] = [];
|
|
179
190
|
const issuesDir = join(this.cwd, ".pi/issues");
|
|
180
191
|
if (!existsSync(issuesDir)) mkdirSync(issuesDir, { recursive: true });
|
|
@@ -302,29 +313,52 @@ class EpicManager {
|
|
|
302
313
|
issues.push(readinessEntry);
|
|
303
314
|
|
|
304
315
|
// ── TDD test generation ──
|
|
305
|
-
|
|
316
|
+
const tddTestFiles: string[] = [];
|
|
306
317
|
if (tdd) {
|
|
307
318
|
const language = readLanguage(this.cwd);
|
|
308
319
|
if (isTddSupported(language)) {
|
|
309
|
-
tddTestFiles
|
|
320
|
+
tddTestFiles.push(...generateEpicTestFiles({
|
|
310
321
|
components: slice.nextLogicalSlice,
|
|
311
322
|
moduleId: slice.module,
|
|
312
323
|
cwd: this.cwd,
|
|
313
324
|
language,
|
|
314
|
-
});
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── GitLab Epic creation (only for GitLab repos with remote access) ──
|
|
330
|
+
let effectiveEpicId: string | null = null;
|
|
331
|
+
if (repoTool === "glab" && hasRemote && remoteRepo) {
|
|
332
|
+
const epicTitle = `Epic: ${name} — ${slice.module}`;
|
|
333
|
+
const epicDesc = [
|
|
334
|
+
`# ${epicTitle}`,
|
|
335
|
+
"",
|
|
336
|
+
`**Module:** ${slice.module}`,
|
|
337
|
+
`**Components:** ${slice.nextLogicalSlice.length}`,
|
|
338
|
+
"",
|
|
339
|
+
"## Issues",
|
|
340
|
+
...issues.map((i) => `- ${i.title}${i.remoteIssueId ? ` (#${i.remoteIssueId})` : ""}`),
|
|
341
|
+
"",
|
|
342
|
+
"---",
|
|
343
|
+
"Auto-generated by Guardian Architect",
|
|
344
|
+
].join("\n");
|
|
345
|
+
effectiveEpicId = createGitlabEpic(this.cwd, remoteRepo, epicTitle, epicDesc);
|
|
346
|
+
if (effectiveEpicId) {
|
|
347
|
+
console.warn(`Created GitLab epic #${effectiveEpicId}`);
|
|
315
348
|
}
|
|
316
349
|
}
|
|
317
350
|
|
|
318
351
|
const state: EpicState = {
|
|
319
352
|
name,
|
|
320
353
|
trackingIssueId: effectiveTrackingId,
|
|
321
|
-
epicId:
|
|
354
|
+
epicId: effectiveEpicId,
|
|
322
355
|
slices: [slice],
|
|
323
356
|
issues,
|
|
324
357
|
status: "planning",
|
|
325
358
|
currentIssueIndex: 0,
|
|
326
359
|
createdAt: new Date().toISOString(),
|
|
327
360
|
tdd,
|
|
361
|
+
tddTestFiles,
|
|
328
362
|
};
|
|
329
363
|
|
|
330
364
|
this.state = state;
|
|
@@ -163,7 +163,7 @@ case "$PLATFORM" in
|
|
|
163
163
|
|
|
164
164
|
# Try to add labels separately (non-fatal)
|
|
165
165
|
if [[ -n "$LABELS" && -n "$ISSUE_NUMBER" ]]; then
|
|
166
|
-
glab issue update "$ISSUE_NUMBER" --label "$LABELS" 2>/dev/null || true
|
|
166
|
+
glab issue update "$ISSUE_NUMBER" --label "$LABELS" ${REPO:+--repo "$REPO"} 2>/dev/null || true
|
|
167
167
|
fi
|
|
168
168
|
|
|
169
169
|
echo "TRACKING_ID=$ISSUE_NUMBER"
|
|
@@ -57,7 +57,7 @@ Relates to #$EPIC_ID" 2>/dev/null
|
|
|
57
57
|
# API: POST /groups/:group/epics/:epic_iid/issues/:issue_iid
|
|
58
58
|
# Derive group from repository path: "group/subgroup/project" -> "group/subgroup"
|
|
59
59
|
if [[ -n "$REPO" ]]; then
|
|
60
|
-
|
|
60
|
+
group_path=$(echo "$REPO" | rev | cut -d'/' -f2- | rev)
|
|
61
61
|
if [[ -n "$group_path" ]]; then
|
|
62
62
|
glab api --method POST "groups/$group_path/epics/$EPIC_ID/issues/$ISSUE_ID" 2>/dev/null && {
|
|
63
63
|
echo "Linked GitLab issue #$ISSUE_ID to epic #$EPIC_ID"
|