@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.
- package/package.json +9 -9
- package/scripts/_shared/common.sh +18 -3
- package/scripts/install-opencode-plugin.sh +14 -9
- package/src/core/branch.ts +143 -48
- package/src/core/changelog.ts +17 -14
- package/src/core/config-guard.ts +9 -2
- package/src/core/config.ts +48 -15
- package/src/core/detector.ts +22 -11
- package/src/core/docs-repo.ts +49 -14
- package/src/core/docs-validate.ts +163 -37
- package/src/core/flow-state.ts +6 -2
- package/src/core/gitignore.ts +11 -2
- package/src/core/handoff-context.ts +18 -5
- package/src/core/hygiene.ts +27 -5
- package/src/core/init.ts +86 -21
- package/src/core/parse-sections.ts +2 -2
- package/src/core/plan-tasks.ts +13 -3
- package/src/core/ports/youtrack-api.ts +3 -1
- package/src/core/ports/youtrack-config.ts +1 -3
- package/src/core/pr-create.ts +47 -15
- package/src/core/present.ts +11 -2
- package/src/core/reminder.ts +1 -2
- package/src/core/repo-tool.ts +4 -1
- package/src/core/rules.ts +10 -7
- package/src/core/scripts.ts +7 -2
- package/src/core/sdd.ts +11 -3
- package/src/core/templates.ts +14 -4
- package/src/core/vcs-config.ts +93 -34
- package/src/core/verify-parse.ts +4 -2
- package/src/core/workspaces.ts +2 -2
- package/src/core/youtrack.ts +231 -56
- package/src/core.ts +18 -3
- package/src/tools/docs-repo.ts +12 -3
- package/src/tools/flow.ts +24 -13
- package/src/tools/handoff.ts +28 -23
- package/src/tools/present.ts +14 -10
- package/src/tools/repo.ts +220 -87
- package/src/tools/sdd.ts +93 -66
- package/src/tools/youtrack.ts +115 -52
- package/templates/superpowers-doc-contract.md +1 -1
package/src/core/vcs-config.ts
CHANGED
|
@@ -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 =>
|
|
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 {
|
|
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 (
|
|
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 =
|
|
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 = {
|
|
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
|
-
|
|
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 {
|
|
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"], {
|
|
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 {
|
|
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({
|
|
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 ?? {
|
|
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)
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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: {
|
|
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
|
|
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([
|
|
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({
|
|
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(
|
|
230
|
-
|
|
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();
|
package/src/core/verify-parse.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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;
|
package/src/core/workspaces.ts
CHANGED
|
@@ -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.
|
|
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
|
};
|