@brainervirus/workit-core 1.2.0 → 1.3.0
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 +1 -1
- package/skills/workit-babysit/SKILL.md +20 -26
- package/skills/workit-behavioral-tdd/SKILL.md +0 -5
- package/skills/workit-blast-radius/SKILL.md +0 -5
- package/skills/workit-challenge/SKILL.md +30 -47
- package/skills/workit-debug/SKILL.md +5 -27
- package/skills/workit-deslop/SKILL.md +0 -5
- package/skills/workit-diagram/SKILL.md +0 -5
- package/skills/workit-green-run/SKILL.md +0 -5
- package/skills/workit-handoff/SKILL.md +2 -8
- package/skills/workit-implement/SKILL.md +14 -20
- package/skills/workit-mockup/SKILL.md +0 -5
- package/skills/workit-plan/SKILL.md +30 -63
- package/skills/workit-review/SKILL.md +0 -5
- package/skills/workit-steer/SKILL.md +27 -27
- package/src/core/authority.ts +31 -8
- package/src/core/auto-approval.ts +44 -32
- package/src/core/branch.ts +293 -42
- package/src/core/config-conversion.ts +4 -4
- package/src/core/config-guard.ts +1 -7
- package/src/core/config.ts +1 -1
- package/src/core/cutover.ts +1207 -140
- package/src/core/doctor.ts +155 -82
- package/src/core/external-action-effects.ts +1102 -209
- package/src/core/external-action.ts +230 -103
- package/src/core/init.ts +25 -115
- package/src/core/legacy-ownership.ts +47 -0
- package/src/core/methods.ts +56 -52
- package/src/core/ports/pr-create.ts +23 -15
- package/src/core/pr-create.ts +374 -84
- package/src/core/registration.ts +0 -15
- package/src/core/repo-tools.ts +0 -1
- package/src/core/route-intent.ts +34 -82
- package/src/core/safe-write.ts +66 -1
- package/src/core/setup-state.ts +7 -0
- package/src/core/setup.ts +175 -82
- package/src/core/task-contract.ts +16 -3
- package/src/core/task-engine.ts +136 -31
- package/src/core/task-evaluation.ts +3 -5
- package/src/core/task-store.ts +159 -1
- package/src/core/tracker-issues.ts +55 -118
- package/src/core/vcs-config.ts +178 -187
- package/src/core/workers.ts +17 -0
- package/src/core/workspaces.ts +548 -46
- package/src/core.ts +1 -2
- package/src/core/ports/vcs-token-create-urls.ts +0 -4
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { hostingCliAvailable, parseGhIssue, pushRemote } from "./pr-create";
|
|
3
|
+
import { hostingApiHostMatches, vcsConfig } from "./vcs-config";
|
|
5
4
|
|
|
6
5
|
// Read-only issue fetches for context.read (GitHub + GitLab), mirroring the
|
|
7
|
-
// YouTrack triple shape.
|
|
8
|
-
// when installed (per-directory identity comes free); token files are the
|
|
9
|
-
// fallback and fail closed without one. No writes, no new secret files.
|
|
6
|
+
// YouTrack triple shape. Authenticated gh/glab serves the read in the target checkout.
|
|
10
7
|
|
|
11
8
|
export type TrackerIssueBody = {
|
|
12
9
|
id: string;
|
|
@@ -20,22 +17,6 @@ export type TrackerIssueFailure = {
|
|
|
20
17
|
kind: "creds" | "request" | "input";
|
|
21
18
|
};
|
|
22
19
|
|
|
23
|
-
export type TrackerRequest = (
|
|
24
|
-
url: string,
|
|
25
|
-
init: { method: string; headers: Record<string, string> },
|
|
26
|
-
) => Promise<{ status: number; stdout: string; stderr: string }>;
|
|
27
|
-
|
|
28
|
-
export const trackerRequest: TrackerRequest = async (url, init) => {
|
|
29
|
-
try {
|
|
30
|
-
const res = await fetch(url, { method: init.method, headers: init.headers });
|
|
31
|
-
const text = await res.text();
|
|
32
|
-
if (!res.ok) return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
|
|
33
|
-
return { status: 0, stdout: text, stderr: "" };
|
|
34
|
-
} catch (err) {
|
|
35
|
-
return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
20
|
/** Full repo path from an origin URL (scp-style, https, or bare); subgroups kept. */
|
|
40
21
|
export const repoPathFromRemote = (remote: string): string | null => {
|
|
41
22
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
@@ -57,17 +38,20 @@ export type TrackerCli = (
|
|
|
57
38
|
) => Promise<{ status: number; stdout: string; stderr: string }> | null;
|
|
58
39
|
|
|
59
40
|
export type TrackerDeps = {
|
|
60
|
-
creds?: (root: string) => TrackerCreds;
|
|
61
|
-
request?: TrackerRequest;
|
|
62
41
|
remote?: (root: string) => string | null;
|
|
63
42
|
cli?: TrackerCli;
|
|
43
|
+
host?: string;
|
|
64
44
|
};
|
|
65
45
|
|
|
66
46
|
/** Native CLI runner (gh/glab) when installed; null when absent. */
|
|
67
47
|
const defaultCli = (bin: string, available: boolean): TrackerCli | null => {
|
|
68
48
|
if (!available) return null;
|
|
69
49
|
return (args, root) => {
|
|
70
|
-
const out = spawnSync(bin, args, {
|
|
50
|
+
const out = spawnSync(bin, args, {
|
|
51
|
+
cwd: root,
|
|
52
|
+
encoding: "utf8",
|
|
53
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" },
|
|
54
|
+
});
|
|
71
55
|
return Promise.resolve({
|
|
72
56
|
status: out.status ?? 1,
|
|
73
57
|
stdout: out.stdout ?? "",
|
|
@@ -78,55 +62,34 @@ const defaultCli = (bin: string, available: boolean): TrackerCli | null => {
|
|
|
78
62
|
|
|
79
63
|
const tryCliTriple = async (
|
|
80
64
|
cli: TrackerCli | null | undefined,
|
|
65
|
+
name: string,
|
|
81
66
|
args: string[],
|
|
82
67
|
root: string,
|
|
83
68
|
id: string,
|
|
84
69
|
idKeys: string[],
|
|
85
70
|
bodyKeys: string[],
|
|
86
|
-
): Promise<TrackerIssueBody |
|
|
87
|
-
if (!cli) return
|
|
71
|
+
): Promise<{ data: TrackerIssueBody } | TrackerIssueFailure> => {
|
|
72
|
+
if (!cli) return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
88
73
|
const run = cli(args, root);
|
|
89
|
-
if (!run) return
|
|
74
|
+
if (!run) return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
90
75
|
const out = await run;
|
|
91
|
-
if (out.status !== 0)
|
|
76
|
+
if (out.status !== 0)
|
|
77
|
+
return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
|
|
92
78
|
try {
|
|
93
|
-
|
|
79
|
+
const triple = parseTriple(
|
|
80
|
+
JSON.parse(out.stdout) as Record<string, unknown>,
|
|
81
|
+
id,
|
|
82
|
+
idKeys,
|
|
83
|
+
bodyKeys,
|
|
84
|
+
);
|
|
85
|
+
return triple
|
|
86
|
+
? { data: triple }
|
|
87
|
+
: { error: `unexpected ${name} issue response`, kind: "request" };
|
|
94
88
|
} catch {
|
|
95
|
-
return
|
|
89
|
+
return { error: `invalid ${name} issue response`, kind: "request" };
|
|
96
90
|
}
|
|
97
91
|
};
|
|
98
92
|
|
|
99
|
-
const originRemote = (root: string): string | null => {
|
|
100
|
-
const out = spawnSync("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
|
|
101
|
-
return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
type TrackerCreds = { token: string; api: string } | { error: string };
|
|
105
|
-
|
|
106
|
-
const trackerCreds = (root: string, provider: "github" | "gitlab"): TrackerCreds => {
|
|
107
|
-
const cfg = vcsConfig("load", root) as Record<string, unknown>;
|
|
108
|
-
if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
|
|
109
|
-
return { error: `unconfigured ${provider} provider` };
|
|
110
|
-
let token = "";
|
|
111
|
-
try {
|
|
112
|
-
token = fs.readFileSync(cfg.tokenPath, "utf8").trim();
|
|
113
|
-
} catch {
|
|
114
|
-
return { error: `missing ${provider} token` };
|
|
115
|
-
}
|
|
116
|
-
if (!token) return { error: `empty ${provider} token file` };
|
|
117
|
-
if (provider === "gitlab") {
|
|
118
|
-
const gitlab = cfg.gitlab as Record<string, unknown> | undefined;
|
|
119
|
-
const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
|
|
120
|
-
return { token, api: apiUrl };
|
|
121
|
-
}
|
|
122
|
-
const github = cfg.github as Record<string, unknown> | undefined;
|
|
123
|
-
const host = String(github?.host ?? "github.com").toLowerCase();
|
|
124
|
-
return {
|
|
125
|
-
token,
|
|
126
|
-
api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`,
|
|
127
|
-
};
|
|
128
|
-
};
|
|
129
|
-
|
|
130
93
|
const parseTriple = (
|
|
131
94
|
parsed: Record<string, unknown>,
|
|
132
95
|
id: string,
|
|
@@ -148,7 +111,7 @@ const parseTriple = (
|
|
|
148
111
|
return { id: foundId, title, body: pick(bodyKeys), state };
|
|
149
112
|
};
|
|
150
113
|
|
|
151
|
-
/** Fetch a GitHub issue triple for context.read
|
|
114
|
+
/** Fetch a GitHub issue triple for context.read through the authenticated CLI. */
|
|
152
115
|
export const fetchGitHubIssueBody = async (
|
|
153
116
|
ref: string,
|
|
154
117
|
root: string,
|
|
@@ -156,46 +119,30 @@ export const fetchGitHubIssueBody = async (
|
|
|
156
119
|
): Promise<{ data: TrackerIssueBody } | TrackerIssueFailure> => {
|
|
157
120
|
const id = parseGhIssue(ref);
|
|
158
121
|
if (!/^\d+$/.test(id)) return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
|
|
159
|
-
const
|
|
122
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
123
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
160
124
|
if (!repo) return { error: "no origin remote to resolve the repository", kind: "input" };
|
|
161
|
-
const
|
|
125
|
+
const cfg = vcsConfig("load", root);
|
|
126
|
+
const host = (
|
|
127
|
+
deps.host ??
|
|
128
|
+
(cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ??
|
|
129
|
+
"github.com"
|
|
130
|
+
).toLowerCase();
|
|
131
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
132
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
133
|
+
const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
|
|
134
|
+
return tryCliTriple(
|
|
162
135
|
deps.cli ?? defaultCli("gh", hostingCliAvailable("github")),
|
|
163
|
-
|
|
136
|
+
"gh",
|
|
137
|
+
["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"],
|
|
164
138
|
root,
|
|
165
139
|
id,
|
|
166
140
|
["number"],
|
|
167
141
|
["body"],
|
|
168
142
|
);
|
|
169
|
-
if (viaCli) return { data: viaCli };
|
|
170
|
-
const credentials = (deps.creds ?? ((r) => trackerCreds(r, "github")))(root);
|
|
171
|
-
if ("error" in credentials) return { ...credentials, kind: "creds" as const };
|
|
172
|
-
const out = await (deps.request ?? trackerRequest)(
|
|
173
|
-
`${credentials.api}/repos/${repo}/issues/${id}`,
|
|
174
|
-
{
|
|
175
|
-
method: "GET",
|
|
176
|
-
headers: {
|
|
177
|
-
Authorization: `Bearer ${credentials.token}`,
|
|
178
|
-
Accept: "application/vnd.github+json",
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
);
|
|
182
|
-
if (out.status !== 0)
|
|
183
|
-
return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
|
|
184
|
-
try {
|
|
185
|
-
const triple = parseTriple(
|
|
186
|
-
JSON.parse(out.stdout) as Record<string, unknown>,
|
|
187
|
-
id,
|
|
188
|
-
["number"],
|
|
189
|
-
["body"],
|
|
190
|
-
);
|
|
191
|
-
if (!triple) return { error: "unexpected GitHub issue shape", kind: "request" };
|
|
192
|
-
return { data: triple };
|
|
193
|
-
} catch {
|
|
194
|
-
return { error: "invalid GitHub issue response", kind: "request" };
|
|
195
|
-
}
|
|
196
143
|
};
|
|
197
144
|
|
|
198
|
-
/** Fetch a GitLab issue triple for context.read
|
|
145
|
+
/** Fetch a GitLab issue triple for context.read through the authenticated CLI. */
|
|
199
146
|
export const fetchGitLabIssueBody = async (
|
|
200
147
|
ref: string,
|
|
201
148
|
root: string,
|
|
@@ -203,35 +150,25 @@ export const fetchGitLabIssueBody = async (
|
|
|
203
150
|
): Promise<{ data: TrackerIssueBody } | TrackerIssueFailure> => {
|
|
204
151
|
const id = parseGhIssue(ref);
|
|
205
152
|
if (!/^\d+$/.test(id)) return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
|
|
206
|
-
const
|
|
153
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
154
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
207
155
|
if (!repo) return { error: "no origin remote to resolve the project", kind: "input" };
|
|
208
|
-
const
|
|
156
|
+
const cfg = vcsConfig("load", root);
|
|
157
|
+
const host = (
|
|
158
|
+
deps.host ??
|
|
159
|
+
(cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ??
|
|
160
|
+
"gitlab.com"
|
|
161
|
+
).toLowerCase();
|
|
162
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
163
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
164
|
+
const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
|
|
165
|
+
return tryCliTriple(
|
|
209
166
|
deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")),
|
|
210
|
-
|
|
167
|
+
"glab",
|
|
168
|
+
["issue", "view", id, "-R", repoArg, "-F", "json"],
|
|
211
169
|
root,
|
|
212
170
|
id,
|
|
213
171
|
["iid"],
|
|
214
172
|
["description"],
|
|
215
173
|
);
|
|
216
|
-
if (viaCli) return { data: viaCli };
|
|
217
|
-
const credentials = (deps.creds ?? ((r) => trackerCreds(r, "gitlab")))(root);
|
|
218
|
-
if ("error" in credentials) return { ...credentials, kind: "creds" as const };
|
|
219
|
-
const out = await (deps.request ?? trackerRequest)(
|
|
220
|
-
`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`,
|
|
221
|
-
{ method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } },
|
|
222
|
-
);
|
|
223
|
-
if (out.status !== 0)
|
|
224
|
-
return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
|
|
225
|
-
try {
|
|
226
|
-
const triple = parseTriple(
|
|
227
|
-
JSON.parse(out.stdout) as Record<string, unknown>,
|
|
228
|
-
id,
|
|
229
|
-
["iid"],
|
|
230
|
-
["description"],
|
|
231
|
-
);
|
|
232
|
-
if (!triple) return { error: "unexpected GitLab issue shape", kind: "request" };
|
|
233
|
-
return { data: triple };
|
|
234
|
-
} catch {
|
|
235
|
-
return { error: "invalid GitLab issue response", kind: "request" };
|
|
236
|
-
}
|
|
237
174
|
};
|