@brainervirus/workit-core 0.6.0 → 0.6.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.
Files changed (40) hide show
  1. package/package.json +9 -9
  2. package/scripts/_shared/common.sh +18 -3
  3. package/scripts/install-opencode-plugin.sh +14 -9
  4. package/src/core/branch.ts +143 -48
  5. package/src/core/changelog.ts +17 -14
  6. package/src/core/config-guard.ts +9 -2
  7. package/src/core/config.ts +48 -15
  8. package/src/core/detector.ts +22 -11
  9. package/src/core/docs-repo.ts +49 -14
  10. package/src/core/docs-validate.ts +163 -37
  11. package/src/core/flow-state.ts +6 -2
  12. package/src/core/gitignore.ts +11 -2
  13. package/src/core/handoff-context.ts +18 -5
  14. package/src/core/hygiene.ts +27 -5
  15. package/src/core/init.ts +86 -21
  16. package/src/core/parse-sections.ts +2 -2
  17. package/src/core/plan-tasks.ts +13 -3
  18. package/src/core/ports/youtrack-api.ts +3 -1
  19. package/src/core/ports/youtrack-config.ts +1 -3
  20. package/src/core/pr-create.ts +47 -15
  21. package/src/core/present.ts +11 -2
  22. package/src/core/reminder.ts +1 -2
  23. package/src/core/repo-tool.ts +4 -1
  24. package/src/core/rules.ts +10 -7
  25. package/src/core/scripts.ts +7 -2
  26. package/src/core/sdd.ts +11 -3
  27. package/src/core/templates.ts +14 -4
  28. package/src/core/vcs-config.ts +93 -34
  29. package/src/core/verify-parse.ts +4 -2
  30. package/src/core/workspaces.ts +2 -2
  31. package/src/core/youtrack.ts +231 -56
  32. package/src/core.ts +18 -3
  33. package/src/tools/docs-repo.ts +12 -3
  34. package/src/tools/flow.ts +24 -13
  35. package/src/tools/handoff.ts +28 -23
  36. package/src/tools/present.ts +14 -10
  37. package/src/tools/repo.ts +220 -87
  38. package/src/tools/sdd.ts +93 -66
  39. package/src/tools/youtrack.ts +115 -52
  40. package/templates/superpowers-doc-contract.md +1 -1
@@ -14,7 +14,8 @@ export const vcsConfigPath = (): string =>
14
14
 
15
15
  const workspacesPath = (): string => path.join(configDir(), "workspaces.json");
16
16
 
17
- const vcsCwd = (cwd?: string): string => process.env.WORKFLOW_WORKSPACE_ROOT ?? cwd ?? process.cwd();
17
+ const vcsCwd = (cwd?: string): string =>
18
+ process.env.WORKFLOW_WORKSPACE_ROOT ?? cwd ?? process.cwd();
18
19
 
19
20
  function readVcsJson(): { config: Record<string, any>; path: string; ok: boolean } {
20
21
  const cfgPath = vcsConfigPath();
@@ -27,7 +28,9 @@ function readVcsJson(): { config: Record<string, any>; path: string; ok: boolean
27
28
  config = parsed as Record<string, any>;
28
29
  ok = true;
29
30
  }
30
- } catch { /* missing or invalid */ }
31
+ } catch {
32
+ /* missing or invalid */
33
+ }
31
34
  }
32
35
  return { config, path: cfgPath, ok };
33
36
  }
@@ -47,7 +50,11 @@ export function vcsConfig(mode: "load" | "summary" | "resolve", cwd?: string): R
47
50
  // github issues path only when BOTH providers are github (mirrors WorkspaceConfig.issues).
48
51
  let issuesProvider: string | null = null;
49
52
  let linkOnPr: boolean | null = null;
50
- if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
53
+ if (
54
+ provider === "github" &&
55
+ typeof wsIssues.provider === "string" &&
56
+ wsIssues.provider.toLowerCase() === "github"
57
+ ) {
51
58
  issuesProvider = "github";
52
59
  linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
53
60
  }
@@ -73,7 +80,8 @@ export function vcsConfig(mode: "load" | "summary" | "resolve", cwd?: string): R
73
80
  let tokenOk = false;
74
81
  if (fs.existsSync(tokenPath)) {
75
82
  const token = fs.readFileSync(tokenPath, "utf8").trim();
76
- const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
83
+ const placeholder =
84
+ !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
77
85
  tokenOk = !placeholder;
78
86
  }
79
87
 
@@ -93,7 +101,10 @@ export function vcsConfig(mode: "load" | "summary" | "resolve", cwd?: string): R
93
101
  link_on_pr: linkOnPr,
94
102
  };
95
103
  if (provider === "gitlab") {
96
- out.gitlab = { host: prov.host ?? "gitlab.com", apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4" };
104
+ out.gitlab = {
105
+ host: prov.host ?? "gitlab.com",
106
+ apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4",
107
+ };
97
108
  } else if (provider === "github") {
98
109
  out.github = { host: prov.host ?? "github.com" };
99
110
  }
@@ -118,10 +129,20 @@ export function vcsVerifyToken(): Record<string, any> {
118
129
 
119
130
  if (provider === "gitlab") {
120
131
  const host = (cfg.gitlab as Record<string, any>)?.host ?? "gitlab.com";
121
- const api = ((cfg.gitlab as Record<string, any>)?.apiUrl ?? `https://${host}/api/v4`).replace(/\/+$/, "");
122
- const result = spawnSync("curl", ["-fsS", "-H", `PRIVATE-TOKEN: ${token}`, `${api}/user`], { encoding: "utf8" });
132
+ const api = ((cfg.gitlab as Record<string, any>)?.apiUrl ?? `https://${host}/api/v4`).replace(
133
+ /\/+$/,
134
+ "",
135
+ );
136
+ const result = spawnSync("curl", ["-fsS", "-H", `PRIVATE-TOKEN: ${token}`, `${api}/user`], {
137
+ encoding: "utf8",
138
+ });
123
139
  if (result.status !== 0) {
124
- return { ok: false, provider, error: "GitLab API rejected token", detail: (result.stderr ?? result.stdout ?? "").slice(0, 200) };
140
+ return {
141
+ ok: false,
142
+ provider,
143
+ error: "GitLab API rejected token",
144
+ detail: (result.stderr ?? result.stdout ?? "").slice(0, 200),
145
+ };
125
146
  }
126
147
  try {
127
148
  const user = JSON.parse(result.stdout ?? "") as Record<string, any>;
@@ -131,9 +152,17 @@ export function vcsVerifyToken(): Record<string, any> {
131
152
  }
132
153
  }
133
154
  if (provider === "github") {
134
- const result = spawnSync("gh", ["api", "user"], { encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
155
+ const result = spawnSync("gh", ["api", "user"], {
156
+ encoding: "utf8",
157
+ env: { ...process.env, GH_TOKEN: token },
158
+ });
135
159
  if (result.status !== 0) {
136
- return { ok: false, provider, error: "GitHub API rejected token", detail: (result.stderr ?? result.stdout ?? "").slice(0, 200) };
160
+ return {
161
+ ok: false,
162
+ provider,
163
+ error: "GitHub API rejected token",
164
+ detail: (result.stderr ?? result.stdout ?? "").slice(0, 200),
165
+ };
137
166
  }
138
167
  try {
139
168
  const user = JSON.parse(result.stdout ?? "") as Record<string, any>;
@@ -156,32 +185,44 @@ export function vcsTokenCreateUrls(): Record<string, any> {
156
185
  const gitlab = (cfg.gitlab ?? {}) as Record<string, any>;
157
186
  const host = String(gitlab.host ?? "gitlab.com");
158
187
  const gitlabScopes = Array.isArray(defaults.gitlabScopes) ? defaults.gitlabScopes : ["api"];
159
- const gitlabParams = new URLSearchParams({ name, description: desc, scopes: gitlabScopes.join(",") });
188
+ const gitlabParams = new URLSearchParams({
189
+ name,
190
+ description: desc,
191
+ scopes: gitlabScopes.join(","),
192
+ });
160
193
  const gitlabUrl = `https://${host}/-/user_settings/personal_access_tokens?${gitlabParams}`;
161
194
 
162
- const githubPerms = defaults.githubPermissions ?? { pull_requests: "write", contents: "write", metadata: "read" };
195
+ const githubPerms = defaults.githubPermissions ?? {
196
+ pull_requests: "write",
197
+ contents: "write",
198
+ metadata: "read",
199
+ };
163
200
  const ghParams = new URLSearchParams({ name, description: desc, ...githubPerms });
164
201
  const githubFineUrl = `https://github.com/settings/personal-access-tokens/new?${ghParams}`;
165
202
 
166
- const classicScopes = Array.isArray(defaults.githubClassicScopes) ? defaults.githubClassicScopes : ["repo"];
203
+ const classicScopes = Array.isArray(defaults.githubClassicScopes)
204
+ ? defaults.githubClassicScopes
205
+ : ["repo"];
167
206
  const githubClassicUrl = `https://github.com/settings/tokens/new?${new URLSearchParams({ description: name, scopes: classicScopes.join(",") })}`;
168
207
 
169
208
  const provider = String(cfg.provider ?? "gitlab").toLowerCase();
170
- const active = {
171
- gitlab: {
172
- tokenFile: gitlab.tokenFile ?? path.join(configDir(), "gitlab.token"),
173
- createUrl: gitlabUrl,
174
- scopes: gitlabScopes,
175
- name,
176
- },
177
- github: {
178
- tokenFile: (cfg.github as Record<string, any>)?.tokenFile ?? path.join(configDir(), "github.token"),
179
- createUrl: githubFineUrl,
180
- createUrlClassic: githubClassicUrl,
181
- permissions: githubPerms,
182
- name,
183
- },
184
- }[provider] ?? {};
209
+ const active =
210
+ {
211
+ gitlab: {
212
+ tokenFile: gitlab.tokenFile ?? path.join(configDir(), "gitlab.token"),
213
+ createUrl: gitlabUrl,
214
+ scopes: gitlabScopes,
215
+ name,
216
+ },
217
+ github: {
218
+ tokenFile:
219
+ (cfg.github as Record<string, any>)?.tokenFile ?? path.join(configDir(), "github.token"),
220
+ createUrl: githubFineUrl,
221
+ createUrlClassic: githubClassicUrl,
222
+ permissions: githubPerms,
223
+ name,
224
+ },
225
+ }[provider] ?? {};
185
226
 
186
227
  return {
187
228
  tokenName: name,
@@ -189,7 +230,12 @@ export function vcsTokenCreateUrls(): Record<string, any> {
189
230
  activeProvider: provider,
190
231
  active,
191
232
  gitlab: { host, createUrl: gitlabUrl, scopes: gitlabScopes, tokenFile: gitlab.tokenFile },
192
- github: { createUrl: githubFineUrl, createUrlClassic: githubClassicUrl, permissions: githubPerms, tokenFile: (cfg.github as Record<string, any>)?.tokenFile },
233
+ github: {
234
+ createUrl: githubFineUrl,
235
+ createUrlClassic: githubClassicUrl,
236
+ permissions: githubPerms,
237
+ tokenFile: (cfg.github as Record<string, any>)?.tokenFile,
238
+ },
193
239
  };
194
240
  }
195
241
 
@@ -203,7 +249,10 @@ export function mergedPrStyle(limit = 6): Record<string, any> {
203
249
 
204
250
  const descInfo = (desc: string, caseInsensitiveNotes = false): Record<string, any> => ({
205
251
  hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
206
- sections: desc.split("\n").filter((l) => l.startsWith("## ")).map((l) => l.trim()),
252
+ sections: desc
253
+ .split("\n")
254
+ .filter((l) => l.startsWith("## "))
255
+ .map((l) => l.trim()),
207
256
  descriptionPreview: desc.slice(0, 600),
208
257
  });
209
258
 
@@ -216,18 +265,28 @@ export function mergedPrStyle(limit = 6): Record<string, any> {
216
265
  const project = m[1];
217
266
  const env = { ...process.env, GITLAB_TOKEN: token };
218
267
  const run = (args: string[]) => spawnSync("glab", ["api", ...args], { encoding: "utf8", env });
219
- let r = run([`projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
268
+ let r = run([
269
+ `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`,
270
+ ]);
220
271
  if (r.status !== 0) {
221
272
  r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
222
273
  }
223
274
  if (r.status !== 0) return { ok: false, error: "could not list merge requests" };
224
275
  for (const mr of JSON.parse(r.stdout ?? "[]") as Array<Record<string, any>>) {
225
276
  const desc = String(mr.description ?? "").trim();
226
- examples.push({ title: mr.title, url: mr.web_url, squash: mr.squash, ...descInfo(desc, true) });
277
+ examples.push({
278
+ title: mr.title,
279
+ url: mr.web_url,
280
+ squash: mr.squash,
281
+ ...descInfo(desc, true),
282
+ });
227
283
  }
228
284
  } else if (provider === "github") {
229
- const r = spawnSync("gh", ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"],
230
- { encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
285
+ const r = spawnSync(
286
+ "gh",
287
+ ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"],
288
+ { encoding: "utf8", env: { ...process.env, GH_TOKEN: token } },
289
+ );
231
290
  if (r.status !== 0) return { ok: false, error: "could not list pull requests" };
232
291
  for (const pr of JSON.parse(r.stdout ?? "[]") as Array<Record<string, any>>) {
233
292
  const desc = String(pr.body ?? "").trim();
@@ -3,7 +3,7 @@ export function parseVerifyOutput(stdout: string): Record<string, any> {
3
3
  const commands = [];
4
4
  const parts = stdout.split(/\n## /);
5
5
  for (const part of parts.slice(1)) {
6
- const nl = part.indexOf('\n');
6
+ const nl = part.indexOf("\n");
7
7
  const label = part.slice(0, nl).trim();
8
8
  const body = part.slice(nl + 1);
9
9
  const cmdMatch = body.match(/^command: (.+)$/m);
@@ -18,7 +18,9 @@ export function parseVerifyOutput(stdout: string): Record<string, any> {
18
18
  }
19
19
  }
20
20
 
21
- const summaryMatch = stdout.match(/# Summary[\s\S]*?passed: (\d+)[\s\S]*?failed: (\d+)[\s\S]*?skipped: (\d+)/);
21
+ const summaryMatch = stdout.match(
22
+ /# Summary[\s\S]*?passed: (\d+)[\s\S]*?failed: (\d+)[\s\S]*?skipped: (\d+)/,
23
+ );
22
24
  const passed = summaryMatch ? Number(summaryMatch[1]) : 0;
23
25
  const failed = summaryMatch ? Number(summaryMatch[2]) : 0;
24
26
  const skipped = summaryMatch ? Number(summaryMatch[3]) : 0;
@@ -70,12 +70,12 @@ export const resolveWorkspace = (cwd: string): WorkspaceConfig | null => {
70
70
  if (!parsed || typeof parsed !== "object") return null;
71
71
  const list = (parsed as { workspaces?: unknown }).workspaces;
72
72
  if (!Array.isArray(list)) return null;
73
- const cwdPosix = cwd.split(path.sep).join("/");
73
+ const cwdPosix = cwd.replaceAll("\\", "/");
74
74
  for (const entry of list) {
75
75
  if (!entry || typeof entry !== "object") continue;
76
76
  const ws = entry as WorkspaceConfig;
77
77
  if (typeof ws.glob !== "string" || !ws.glob) continue;
78
- if (globToRegExp(ws.glob).test(cwdPosix)) return ws;
78
+ if (globToRegExp(ws.glob.replaceAll("\\", "/")).test(cwdPosix)) return ws;
79
79
  }
80
80
  return null;
81
81
  };