@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.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/skills/workit-babysit/SKILL.md +20 -26
  3. package/skills/workit-behavioral-tdd/SKILL.md +0 -5
  4. package/skills/workit-blast-radius/SKILL.md +0 -5
  5. package/skills/workit-challenge/SKILL.md +30 -47
  6. package/skills/workit-debug/SKILL.md +5 -27
  7. package/skills/workit-deslop/SKILL.md +0 -5
  8. package/skills/workit-diagram/SKILL.md +0 -5
  9. package/skills/workit-green-run/SKILL.md +0 -5
  10. package/skills/workit-handoff/SKILL.md +2 -8
  11. package/skills/workit-implement/SKILL.md +14 -20
  12. package/skills/workit-mockup/SKILL.md +0 -5
  13. package/skills/workit-plan/SKILL.md +30 -63
  14. package/skills/workit-review/SKILL.md +0 -5
  15. package/skills/workit-steer/SKILL.md +27 -27
  16. package/src/core/authority.ts +31 -8
  17. package/src/core/auto-approval.ts +44 -32
  18. package/src/core/branch.ts +293 -42
  19. package/src/core/config-conversion.ts +4 -4
  20. package/src/core/config-guard.ts +1 -7
  21. package/src/core/config.ts +1 -1
  22. package/src/core/cutover.ts +1207 -140
  23. package/src/core/doctor.ts +155 -82
  24. package/src/core/external-action-effects.ts +1102 -209
  25. package/src/core/external-action.ts +230 -103
  26. package/src/core/init.ts +25 -115
  27. package/src/core/legacy-ownership.ts +47 -0
  28. package/src/core/methods.ts +56 -52
  29. package/src/core/ports/pr-create.ts +23 -15
  30. package/src/core/pr-create.ts +374 -84
  31. package/src/core/registration.ts +0 -15
  32. package/src/core/repo-tools.ts +0 -1
  33. package/src/core/route-intent.ts +34 -82
  34. package/src/core/safe-write.ts +66 -1
  35. package/src/core/setup-state.ts +7 -0
  36. package/src/core/setup.ts +175 -82
  37. package/src/core/task-contract.ts +16 -3
  38. package/src/core/task-engine.ts +136 -31
  39. package/src/core/task-evaluation.ts +3 -5
  40. package/src/core/task-store.ts +159 -1
  41. package/src/core/tracker-issues.ts +55 -118
  42. package/src/core/vcs-config.ts +178 -187
  43. package/src/core/workers.ts +17 -0
  44. package/src/core/workspaces.ts +548 -46
  45. package/src/core.ts +1 -2
  46. 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 { vcsConfig } from "./vcs-config";
4
- import { hostingCliAvailable, parseGhIssue } from "./pr-create";
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. CLI-first: the native gh/glab CLI serves the read
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, { cwd: root, encoding: "utf8" });
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 | null> => {
87
- if (!cli) return null;
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 null;
74
+ if (!run) return { error: `${name} CLI is unavailable`, kind: "creds" };
90
75
  const out = await run;
91
- if (out.status !== 0) return null;
76
+ if (out.status !== 0)
77
+ return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
92
78
  try {
93
- return parseTriple(JSON.parse(out.stdout) as Record<string, unknown>, id, idKeys, bodyKeys);
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 null;
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: gh CLI first, token fallback (fail-closed). */
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 repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
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 viaCli = await tryCliTriple(
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
- ["issue", "view", id, "--repo", repo, "--json", "number,title,body,state"],
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: glab CLI first, token fallback (fail-closed). */
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 repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
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 viaCli = await tryCliTriple(
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
- ["issue", "view", id, "-R", repo, "-F", "json"],
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
  };