pi-project-gate 1.2.0 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-project-gate",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Project orchestration gate for AI agents \u2014 structured issues, WIP limits, dependency blocking, and auto-generated release notes.",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/helpers.ts CHANGED
@@ -16,13 +16,25 @@ export function resolveGitea(cwd: string): { repo: string; token: string } {
16
16
  return { repo, token: credMatch ? credMatch[2] : "" };
17
17
  }
18
18
 
19
- export function giteaApi(path: string, method: string, body: Record<string, unknown> | null, opts: { repo: string; token?: string }, cwd: string): { ok: boolean; data: unknown; error?: string } {
19
+ export async function giteaApi(path: string, method: string, body: Record<string, unknown> | null, opts: { repo: string; token?: string }, _cwd: string): Promise<{ ok: boolean; data: unknown; error?: string }> {
20
20
  const base = `http://127.0.0.1:3001/api/v1/repos/${opts.repo}`;
21
- const headers = [opts.token ? `-H "Authorization: token ${opts.token}"` : "", `-H "Content-Type: application/json"`].filter(Boolean).join(" ");
22
- const dataFlag = body ? `-d '${JSON.stringify(body).replace(/'/g, "'\\''")}'` : "";
23
- const cmd = `curl -sf -w "\\n%{http_code}" -X ${method} "${base}${path}" ${headers} ${dataFlag}`;
24
- const r = exec(cmd, cwd);
25
- if (!r.ok) { const lines = r.stdout.split("\n"); return { ok: false, data: null, error: r.stderr || lines.slice(0, -1).join("\n") || "API error" }; }
26
- const lines = r.stdout.split("\n"); const bodyText = lines.slice(0, -1).join("\n");
27
- try { return { ok: true, data: JSON.parse(bodyText) }; } catch { return { ok: true, data: bodyText }; }
21
+ const url = `${base}${path}`;
22
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
23
+ if (opts.token) headers["Authorization"] = `token ${opts.token}`;
24
+
25
+ try {
26
+ const res = await fetch(url, {
27
+ method,
28
+ headers,
29
+ body: body ? JSON.stringify(body) : undefined,
30
+ });
31
+ const text = await res.text();
32
+ if (!res.ok) {
33
+ const lines = text.split("\n");
34
+ return { ok: false, data: null, error: text || lines.slice(0, -1).join("\n") || "API error" };
35
+ }
36
+ try { return { ok: true, data: JSON.parse(text) }; } catch { return { ok: true, data: text }; }
37
+ } catch (e: any) {
38
+ return { ok: false, data: null, error: e.message || "Network error" };
39
+ }
28
40
  }
@@ -51,7 +51,7 @@ export const createTool = {
51
51
  if (params.milestone) payload.milestone = params.milestone;
52
52
  if (params.assignee) payload.assignee = params.assignee;
53
53
 
54
- const r = giteaApi("/issues", "POST", payload, opts, ctx.cwd);
54
+ const r = await giteaApi("/issues", "POST", payload, opts, ctx.cwd);
55
55
  if (!r.ok || !r.data) {
56
56
  return {
57
57
  content: [{ type: "text", text: `❌ Failed to create issue: ${r.error || "unknown error"}` }],
@@ -101,7 +101,7 @@ export const updateTool = {
101
101
  const issueId = String(params.issue_id).replace(/^#/, "");
102
102
 
103
103
  // Fetch current issue to verify it exists
104
- const current = giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
104
+ const current = await giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
105
105
  if (!current.ok || !current.data) {
106
106
  return {
107
107
  content: [{ type: "text", text: `❌ Issue #${issueId} not found.` }],
@@ -144,7 +144,7 @@ export const updateTool = {
144
144
  };
145
145
  }
146
146
 
147
- const r = giteaApi(`/issues/${issueId}`, "PATCH", payload, opts, ctx.cwd);
147
+ const r = await giteaApi(`/issues/${issueId}`, "PATCH", payload, opts, ctx.cwd);
148
148
  if (!r.ok || !r.data) {
149
149
  return {
150
150
  content: [{ type: "text", text: `❌ Failed to update issue: ${r.error || "unknown error"}` }],
@@ -201,7 +201,7 @@ export const listTool = {
201
201
  if (params.assignee) queryParts.push(`assignee=${encodeURIComponent(params.assignee)}`);
202
202
  if (params.q) queryParts.push(`q=${encodeURIComponent(params.q)}`);
203
203
 
204
- const r = giteaApi(`/issues?${queryParts.join("&")}`, "GET", null, opts, ctx.cwd);
204
+ const r = await giteaApi(`/issues?${queryParts.join("&")}`, "GET", null, opts, ctx.cwd);
205
205
  if (!r.ok) {
206
206
  return {
207
207
  content: [{ type: "text", text: `❌ Failed to list issues: ${r.error || "unknown error"}` }],
@@ -257,7 +257,7 @@ export const getTool = {
257
257
  const opts = resolveGitea(ctx.cwd);
258
258
  const issueId = String(params.issue_id).replace(/^#/, "");
259
259
 
260
- const r = giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
260
+ const r = await giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
261
261
  if (!r.ok || !r.data) {
262
262
  return {
263
263
  content: [{ type: "text", text: `❌ Issue #${issueId} not found.` }],
@@ -289,7 +289,7 @@ export const getTool = {
289
289
  // Fetch comments
290
290
  const includeComments = params.include_comments !== false;
291
291
  if (includeComments) {
292
- const cr = giteaApi(
292
+ const cr = await giteaApi(
293
293
  `/issues/${issueId}/comments?limit=20`,
294
294
  "GET",
295
295
  null,
@@ -13,7 +13,7 @@ export const checkTool = {
13
13
  async execute(_id: string, params: any, _s: any, _u: any, ctx: ExtensionContext) {
14
14
  const config = loadConfig(ctx.cwd); const opts = resolveGitea(ctx.cwd);
15
15
  const issueId = params.issue_id.replace(/^#/, "");
16
- const r = giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
16
+ const r = await giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
17
17
  if (!r.ok || !r.data) return { content: [{ type: "text", text: `Issue #${issueId} not found.` }], isError: true, details: {} };
18
18
  const issue = r.data as Record<string, unknown>;
19
19
  const lines: string[] = []; const issues: string[] = [];
@@ -27,7 +27,7 @@ export const checkTool = {
27
27
  const deps = parseDependencies(body, config);
28
28
  if (deps.length > 0) {
29
29
  const blocked: string[] = [];
30
- for (const dep of deps) { const dr = giteaApi(`/issues/${dep}`, "GET", null, opts, ctx.cwd); if (dr.ok && (dr.data as any)?.state === "open") blocked.push(dep); }
30
+ for (const dep of deps) { const dr = await giteaApi(`/issues/${dep}`, "GET", null, opts, ctx.cwd); if (dr.ok && (dr.data as any)?.state === "open") blocked.push(dep); }
31
31
  if (blocked.length > 0) issues.push(`🔒 Blocked by: #${blocked.join(", #")}`);
32
32
  else lines.push(" Dependencies: ✅");
33
33
  }
@@ -46,7 +46,7 @@ export const startTool = {
46
46
  async execute(_id: string, params: any, _s: any, _u: any, ctx: ExtensionContext) {
47
47
  const config = loadConfig(ctx.cwd); const opts = resolveGitea(ctx.cwd);
48
48
  const issueId = params.issue_id.replace(/^#/, "");
49
- const r = giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
49
+ const r = await giteaApi(`/issues/${issueId}`, "GET", null, opts, ctx.cwd);
50
50
  if (!r.ok || !r.data) return { content: [{ type: "text", text: `Issue #${issueId} not found.` }], isError: true, details: {} };
51
51
  const issue = r.data as Record<string, unknown>;
52
52
  const body = (issue.body as string) || "";
@@ -55,10 +55,10 @@ export const startTool = {
55
55
  const deps = parseDependencies(body, config);
56
56
  if (deps.length > 0) {
57
57
  const blocked: string[] = [];
58
- for (const dep of deps) { const dr = giteaApi(`/issues/${dep}`, "GET", null, opts, ctx.cwd); if (dr.ok && (dr.data as any)?.state === "open") blocked.push(dep); }
58
+ for (const dep of deps) { const dr = await giteaApi(`/issues/${dep}`, "GET", null, opts, ctx.cwd); if (dr.ok && (dr.data as any)?.state === "open") blocked.push(dep); }
59
59
  if (blocked.length > 0) return { content: [{ type: "text", text: `🔒 Blocked: #${blocked.join(", #")}` }], isError: true, details: {} };
60
60
  }
61
- const wipR = giteaApi("/pulls?state=open&limit=100", "GET", null, opts, ctx.cwd);
61
+ const wipR = await giteaApi("/pulls?state=open&limit=100", "GET", null, opts, ctx.cwd);
62
62
  const prs = Array.isArray(wipR.data) ? wipR.data : [];
63
63
  const author = (issue.user as any)?.login || "factory";
64
64
  const currentWip = prs.filter((p: any) => p.user?.login === author).length;
@@ -75,13 +75,13 @@ export const statusTool = {
75
75
  async execute(_id: string, _p: any, _s: any, _u: any, ctx: ExtensionContext) {
76
76
  const config = loadConfig(ctx.cwd); const opts = resolveGitea(ctx.cwd);
77
77
  const lines = ["📊 Project Status", ""];
78
- const wipR = giteaApi("/pulls?state=open&limit=100", "GET", null, opts, ctx.cwd);
78
+ const wipR = await giteaApi("/pulls?state=open&limit=100", "GET", null, opts, ctx.cwd);
79
79
  const prs = Array.isArray(wipR.data) ? wipR.data : [];
80
80
  const byAuthor: Record<string, number> = {};
81
81
  for (const pr of prs) { const a = (pr as any).user?.login || "?"; byAuthor[a] = (byAuthor[a] || 0) + 1; }
82
82
  lines.push(`🏗 WIP: ${prs.length} open PRs (limit: ${config.maxWip})`);
83
83
  for (const [a, c] of Object.entries(byAuthor).sort(([, a], [, b]) => b - a)) lines.push(` ${a}: ${c}/${config.maxWip} ${c >= config.maxWip ? "⚠️" : "✅"}`);
84
- const issuesR = giteaApi("/issues?state=open&limit=10", "GET", null, opts, ctx.cwd);
84
+ const issuesR = await giteaApi("/issues?state=open&limit=10", "GET", null, opts, ctx.cwd);
85
85
  if (issuesR.ok && Array.isArray(issuesR.data)) {
86
86
  const assigned = (issuesR.data as any[]).filter((i: any) => i.assignee).slice(0, 5);
87
87
  if (assigned.length > 0) { lines.push("", "In Progress:"); for (const i of assigned) lines.push(` - #${i.number} [${i.assignee?.login}] ${i.title}`); }