@brainervirus/workit-core 0.5.6 → 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 (43) hide show
  1. package/README.md +4 -17
  2. package/package.json +9 -9
  3. package/scripts/_shared/common.sh +18 -3
  4. package/scripts/install-opencode-plugin.sh +14 -9
  5. package/scripts/lib/config-dir.sh +26 -0
  6. package/scripts/sync-runtime.sh +4 -1
  7. package/src/core/branch.ts +143 -48
  8. package/src/core/changelog.ts +17 -14
  9. package/src/core/config-guard.ts +9 -2
  10. package/src/core/config.ts +95 -15
  11. package/src/core/detector.ts +22 -11
  12. package/src/core/docs-repo.ts +50 -15
  13. package/src/core/docs-validate.ts +163 -37
  14. package/src/core/flow-state.ts +6 -2
  15. package/src/core/gitignore.ts +11 -2
  16. package/src/core/handoff-context.ts +18 -5
  17. package/src/core/hygiene.ts +27 -5
  18. package/src/core/init.ts +86 -21
  19. package/src/core/parse-sections.ts +2 -2
  20. package/src/core/plan-tasks.ts +13 -3
  21. package/src/core/ports/youtrack-api.ts +3 -1
  22. package/src/core/ports/youtrack-config.ts +1 -3
  23. package/src/core/pr-create.ts +47 -15
  24. package/src/core/present.ts +11 -2
  25. package/src/core/reminder.ts +1 -2
  26. package/src/core/repo-tool.ts +4 -1
  27. package/src/core/rules.ts +10 -7
  28. package/src/core/scripts.ts +7 -2
  29. package/src/core/sdd.ts +11 -3
  30. package/src/core/templates.ts +14 -4
  31. package/src/core/vcs-config.ts +94 -36
  32. package/src/core/verify-parse.ts +4 -2
  33. package/src/core/workspaces.ts +2 -2
  34. package/src/core/youtrack.ts +233 -58
  35. package/src/core.ts +18 -3
  36. package/src/tools/docs-repo.ts +12 -3
  37. package/src/tools/flow.ts +24 -13
  38. package/src/tools/handoff.ts +28 -23
  39. package/src/tools/present.ts +14 -10
  40. package/src/tools/repo.ts +220 -87
  41. package/src/tools/sdd.ts +93 -66
  42. package/src/tools/youtrack.ts +119 -52
  43. package/templates/superpowers-doc-contract.md +1 -1
@@ -37,10 +37,11 @@ export const adaptPluginHandoffClient = (client: PluginInput["client"]): Handoff
37
37
  promptAsync: (input) => client.session.promptAsync(input),
38
38
  },
39
39
  tui: {
40
- selectSession: ({ body, query }) => client.tui.publish({
41
- body: { type: "tui.session.select", properties: { sessionID: body.sessionID } } as never,
42
- query,
43
- }),
40
+ selectSession: ({ body, query }) =>
41
+ client.tui.publish({
42
+ body: { type: "tui.session.select", properties: { sessionID: body.sessionID } } as never,
43
+ query,
44
+ }),
44
45
  },
45
46
  });
46
47
 
@@ -58,11 +59,12 @@ type HandoffData = {
58
59
  stage?: "create" | "seed" | "select";
59
60
  };
60
61
 
61
- const message = (error: unknown) => error instanceof Error
62
- ? error.message
63
- : typeof error === "object" && error !== null && "message" in error
64
- ? String(error.message)
65
- : String(error);
62
+ const message = (error: unknown) =>
63
+ error instanceof Error
64
+ ? error.message
65
+ : typeof error === "object" && error !== null && "message" in error
66
+ ? String(error.message)
67
+ : String(error);
66
68
  const apiError = (response: ApiResponse<unknown> | void) => response?.error;
67
69
 
68
70
  export async function handoffSession(
@@ -121,21 +123,22 @@ export const buildHandoffPrompt = (root: string, message: string): HandoffContex
121
123
  path.dirname(fileURLToPath(import.meta.url)),
122
124
  "../../templates/execution-contract.md",
123
125
  );
124
- const contract = buildHandoffContract({ root, spec: resolved.spec, plan: resolved.plan, templatePath });
126
+ const contract = buildHandoffContract({
127
+ root,
128
+ spec: resolved.spec,
129
+ plan: resolved.plan,
130
+ templatePath,
131
+ });
125
132
  if ("error" in contract) return { error: contract.error };
126
133
  const sdd = `docs/${path.basename(path.dirname(resolved.plan))}/sdd`;
127
134
  return { prompt: contract.prompt, spec: resolved.spec, plan: resolved.plan, sdd };
128
135
  };
129
136
 
130
-
131
-
132
- export function createHandoffTools(
133
- client: HandoffClient,
134
- state: WorkflowStateStore,
135
- ) {
137
+ export function createHandoffTools(client: HandoffClient, state: WorkflowStateStore) {
136
138
  return {
137
139
  workflow_handoff_session: tool({
138
- description: "Create, seed, and select a continuation session; --stay in the message skips selection",
140
+ description:
141
+ "Create, seed, and select a continuation session; --stay in the message skips selection",
139
142
  args: { message: tool.schema.string() },
140
143
  execute: async ({ message: userMessage }, context) => {
141
144
  const built = buildHandoffPrompt(context.directory, userMessage);
@@ -145,12 +148,14 @@ export function createHandoffTools(
145
148
  const gate = assertFlowGates(context.directory, active.plan);
146
149
  if (!gate.ok) return output(fail(gate.error));
147
150
  state.set(context.sessionID, { spec: active.spec, plan: active.plan, sdd: active.sdd });
148
- return output(await handoffSession(client, {
149
- directory: context.directory,
150
- title: `Continue ${path.basename(path.dirname(active.plan))}`,
151
- prompt: active.prompt,
152
- stay: /(?:^|\s)--stay(?:\s|$)/.test(userMessage),
153
- }));
151
+ return output(
152
+ await handoffSession(client, {
153
+ directory: context.directory,
154
+ title: `Continue ${path.basename(path.dirname(active.plan))}`,
155
+ prompt: active.prompt,
156
+ stay: /(?:^|\s)--stay(?:\s|$)/.test(userMessage),
157
+ }),
158
+ );
154
159
  } catch (error) {
155
160
  return output(fail(message(error)));
156
161
  }
@@ -24,16 +24,20 @@ export function createPresentTools() {
24
24
  args: {
25
25
  title: tool.schema.string().optional(),
26
26
  direction: tool.schema.enum(["TD", "LR", "BT", "RL"]).optional(),
27
- nodes: tool.schema.array(tool.schema.object({
28
- id: tool.schema.string(),
29
- label: tool.schema.string(),
30
- shape: tool.schema.string().optional(),
31
- })),
32
- edges: tool.schema.array(tool.schema.object({
33
- from: tool.schema.string(),
34
- to: tool.schema.string(),
35
- label: tool.schema.string().optional(),
36
- })),
27
+ nodes: tool.schema.array(
28
+ tool.schema.object({
29
+ id: tool.schema.string(),
30
+ label: tool.schema.string(),
31
+ shape: tool.schema.string().optional(),
32
+ }),
33
+ ),
34
+ edges: tool.schema.array(
35
+ tool.schema.object({
36
+ from: tool.schema.string(),
37
+ to: tool.schema.string(),
38
+ label: tool.schema.string().optional(),
39
+ }),
40
+ ),
37
41
  },
38
42
  execute: async (spec) => {
39
43
  const result = flowDiagram(spec);
package/src/tools/repo.ts CHANGED
@@ -8,7 +8,13 @@ import { gitContext } from "../core/git";
8
8
  import { parseKeyValueLines, parseSections } from "../core/parse-sections";
9
9
  import { parseVerifyOutput } from "../core/verify-parse";
10
10
  import { branchSetup } from "../core/branch";
11
- import { configDir, readConfig, writeConfig, type BranchPreset, type ToolkitConfig } from "../core/config";
11
+ import {
12
+ configDir,
13
+ readConfig,
14
+ writeConfig,
15
+ type BranchPreset,
16
+ type ToolkitConfig,
17
+ } from "../core/config";
12
18
  import { ensureProjectGitignore } from "../core/gitignore";
13
19
  import { ensureHygieneFiles } from "../core/hygiene";
14
20
 
@@ -22,46 +28,67 @@ export type RepoRuntime = {
22
28
  };
23
29
 
24
30
  const defaultRuntime: RepoRuntime = {
25
- runScript: (root, script, args, env) => run(root, "bash", [path.join(scripts, script), ...args], env),
31
+ runScript: (root, script, args, env) =>
32
+ run(root, "bash", [path.join(scripts, script), ...args], env),
26
33
  git: (root, args) => run(root, "git", args),
27
34
  };
28
35
 
29
36
  const output = (value: unknown) => JSON.stringify(value, null, 2);
30
37
  const diagnostics = ({ stdout, stderr, exitCode }: RunResult) => ({ stdout, stderr, exitCode });
31
- const requireConfirmed = (confirmed: boolean) => confirmed === true ? null : output(fail("confirmed: true required"));
38
+ const requireConfirmed = (confirmed: boolean) =>
39
+ confirmed === true ? null : output(fail("confirmed: true required"));
32
40
  import { resolveBranchPolicy } from "../core/config";
33
41
  const branchPolicy = () => resolveBranchPolicy(readConfig());
34
42
 
35
43
  function scriptResult<T extends object>(result: RunResult, parse: (stdout: string) => T) {
36
44
  if (result.exitCode !== 0) {
37
- return fail(result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
45
+ return fail(
46
+ result.stderr.trim() || result.stdout.trim() || "workflow script failed",
47
+ diagnostics(result),
48
+ );
38
49
  }
39
50
  try {
40
- return ok({ ...parse(result.stdout), exitCode: 0, ...(result.stderr ? { stderr: result.stderr } : {}) });
51
+ return ok({
52
+ ...parse(result.stdout),
53
+ exitCode: 0,
54
+ ...(result.stderr ? { stderr: result.stderr } : {}),
55
+ });
41
56
  } catch (error) {
42
- return fail(error instanceof Error ? error.message : "workflow output parse failed", diagnostics(result));
57
+ return fail(
58
+ error instanceof Error ? error.message : "workflow output parse failed",
59
+ diagnostics(result),
60
+ );
43
61
  }
44
62
  }
45
63
 
46
64
  const json = (stdout: string) => JSON.parse(stdout.trim()) as Record<string, unknown>;
47
65
  const legacyScriptResult = (result: RunResult) => {
48
66
  let parsed: Record<string, unknown> | null = null;
49
- try { parsed = json(result.stdout); } catch { /* handled below */ }
50
- if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false) return fail(
51
- parsed?.error
52
- ? String(parsed.error)
53
- : parsed?.ok === false
54
- ? "legacy operation reported failure"
55
- : result.stderr.trim() || result.stdout.trim() || "workflow script failed",
56
- diagnostics(result),
57
- );
67
+ try {
68
+ parsed = json(result.stdout);
69
+ } catch {
70
+ /* handled below */
71
+ }
72
+ if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false)
73
+ return fail(
74
+ parsed?.error
75
+ ? String(parsed.error)
76
+ : parsed?.ok === false
77
+ ? "legacy operation reported failure"
78
+ : result.stderr.trim() || result.stdout.trim() || "workflow script failed",
79
+ diagnostics(result),
80
+ );
58
81
  if (!parsed) return fail("workflow output parse failed", diagnostics(result));
59
82
  const { ok: _legacyOk, ...data } = parsed;
60
83
  return ok({ ...data, exitCode: 0, ...(result.stderr ? { stderr: result.stderr } : {}) });
61
84
  };
62
85
  const optionalJson = (value: string | undefined) => {
63
86
  if (!value?.trim()) return null;
64
- try { return JSON.parse(value); } catch { return null; }
87
+ try {
88
+ return JSON.parse(value);
89
+ } catch {
90
+ return null;
91
+ }
65
92
  };
66
93
  const sections = (stdout: string) => parseSections(stdout) as Record<string, string>;
67
94
  export const normalizeLegacyResult = (value: Record<string, unknown>) => {
@@ -74,7 +101,13 @@ export const normalizeLegacyResult = (value: Record<string, unknown>) => {
74
101
  const parsePr = (stdout: string) => {
75
102
  const part = sections(stdout);
76
103
  const repo = parseKeyValueLines(part.Repository ?? "", [
77
- "branch", "range", "base_ref", "merge_base", "diff_range", "range_mode", "git_sync",
104
+ "branch",
105
+ "range",
106
+ "base_ref",
107
+ "merge_base",
108
+ "diff_range",
109
+ "range_mode",
110
+ "git_sync",
78
111
  ]);
79
112
  return {
80
113
  ...repo,
@@ -128,27 +161,44 @@ const parseDocs = (stdout: string) => {
128
161
  export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
129
162
  const validateRange = (root: string, value: string) => {
130
163
  for (const revision of gitRevisionParts(value)) {
131
- const resolved = runtime.git(root, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${revision}^{commit}`]);
164
+ const resolved = runtime.git(root, [
165
+ "rev-parse",
166
+ "--verify",
167
+ "--quiet",
168
+ "--end-of-options",
169
+ `${revision}^{commit}`,
170
+ ]);
132
171
  if (resolved.exitCode !== 0) throw new Error(`invalid Git revision or range: ${value}`);
133
172
  }
134
173
  };
135
174
  const contextWithRange = (
136
- root: string, script: string, value: string | undefined,
175
+ root: string,
176
+ script: string,
177
+ value: string | undefined,
137
178
  parse: (stdout: string) => Record<string, unknown>,
138
179
  ) => {
139
- try { if (value) validateRange(root, value); }
140
- catch (error) { return output(fail(error instanceof Error ? error.message : "invalid Git revision or range")); }
180
+ try {
181
+ if (value) validateRange(root, value);
182
+ } catch (error) {
183
+ return output(fail(error instanceof Error ? error.message : "invalid Git revision or range"));
184
+ }
141
185
  return output(scriptResult(runtime.runScript(root, script, value ? [value] : []), parse));
142
186
  };
143
- const invoke = (script: string, parse: (stdout: string) => Record<string, unknown>, args: string[] = []) =>
144
- async (_input: unknown, context: ToolContext) => output(scriptResult(runtime.runScript(context.directory, script, args), parse));
187
+ const invoke =
188
+ (script: string, parse: (stdout: string) => Record<string, unknown>, args: string[] = []) =>
189
+ async (_input: unknown, context: ToolContext) =>
190
+ output(scriptResult(runtime.runScript(context.directory, script, args), parse));
145
191
 
146
192
  return {
147
193
  workflow_toolkit_init_status: tool({
148
- description: "Inspect toolkit initialization", args: {}, execute: invoke("init/status.sh", json),
194
+ description: "Inspect toolkit initialization",
195
+ args: {},
196
+ execute: invoke("init/status.sh", json),
149
197
  }),
150
198
  workflow_toolkit_status: tool({
151
- description: "Inspect toolkit and repository state", args: {}, execute: invoke("init/toolkit-status.sh", json),
199
+ description: "Inspect toolkit and repository state",
200
+ args: {},
201
+ execute: invoke("init/toolkit-status.sh", json),
152
202
  }),
153
203
  workflow_git_context: tool({
154
204
  description: "Read Git branch and change context",
@@ -158,42 +208,62 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
158
208
  workflow_verify: tool({
159
209
  description: "Discover and run repository verification",
160
210
  args: { dry_run: tool.schema.boolean().optional() },
161
- execute: async ({ dry_run }, context) => output(scriptResult(
162
- runtime.runScript(context.directory, "verify-project.sh", dry_run ? ["--dry-run"] : []), parseVerifyOutput,
163
- )),
211
+ execute: async ({ dry_run }, context) =>
212
+ output(
213
+ scriptResult(
214
+ runtime.runScript(context.directory, "verify-project.sh", dry_run ? ["--dry-run"] : []),
215
+ parseVerifyOutput,
216
+ ),
217
+ ),
164
218
  }),
165
219
  workflow_pr_context: tool({
166
220
  description: "Gather branch-exclusive PR context",
167
221
  args: { range: tool.schema.string().optional() },
168
- execute: async ({ range }, context) => contextWithRange(context.directory, "pr-ready-context.sh", range, parsePr),
222
+ execute: async ({ range }, context) =>
223
+ contextWithRange(context.directory, "pr-ready-context.sh", range, parsePr),
169
224
  }),
170
225
  workflow_changelog_context: tool({
171
226
  description: "Gather changelog context",
172
227
  args: { range: tool.schema.string().optional() },
173
- execute: async ({ range }, context) => contextWithRange(context.directory, "changelog-context.sh", range, parseChangelog),
228
+ execute: async ({ range }, context) =>
229
+ contextWithRange(context.directory, "changelog-context.sh", range, parseChangelog),
174
230
  }),
175
231
  workflow_release_notes_context: tool({
176
232
  description: "Gather release notes for an explicit range",
177
233
  args: { range_or_tag: tool.schema.string() },
178
- execute: async ({ range_or_tag }, context) => !range_or_tag.trim()
179
- ? output(fail("release tag or range required"))
180
- : contextWithRange(context.directory, "release-notes-context.sh", range_or_tag, parseRelease),
234
+ execute: async ({ range_or_tag }, context) =>
235
+ !range_or_tag.trim()
236
+ ? output(fail("release tag or range required"))
237
+ : contextWithRange(
238
+ context.directory,
239
+ "release-notes-context.sh",
240
+ range_or_tag,
241
+ parseRelease,
242
+ ),
181
243
  }),
182
244
  workflow_docs_context: tool({
183
245
  description: "Gather documentation refresh context",
184
246
  args: { range: tool.schema.string().optional() },
185
- execute: async ({ range }, context) => output(scriptResult(
186
- runtime.runScript(context.directory, "docs-refresh-context.sh", range ? [range] : []), parseDocs,
187
- )),
247
+ execute: async ({ range }, context) =>
248
+ output(
249
+ scriptResult(
250
+ runtime.runScript(context.directory, "docs-refresh-context.sh", range ? [range] : []),
251
+ parseDocs,
252
+ ),
253
+ ),
188
254
  }),
189
255
  workflow_changelog_apply: tool({
190
256
  description: "Apply confirmed Keep a Changelog entries to Unreleased",
191
257
  args: {
192
258
  confirmed: tool.schema.boolean(),
193
- entries: tool.schema.union([
194
- tool.schema.record(tool.schema.string(), tool.schema.array(tool.schema.string())),
195
- tool.schema.array(tool.schema.object({ category: tool.schema.string(), text: tool.schema.string() })),
196
- ]).optional(),
259
+ entries: tool.schema
260
+ .union([
261
+ tool.schema.record(tool.schema.string(), tool.schema.array(tool.schema.string())),
262
+ tool.schema.array(
263
+ tool.schema.object({ category: tool.schema.string(), text: tool.schema.string() }),
264
+ ),
265
+ ])
266
+ .optional(),
197
267
  path: tool.schema.string().optional(),
198
268
  normalize_only: tool.schema.boolean().optional(),
199
269
  },
@@ -205,9 +275,16 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
205
275
  } catch (error) {
206
276
  return output(fail(error instanceof Error ? error.message : "invalid changelog path"));
207
277
  }
208
- return output(normalizeLegacyResult(changelogApply({
209
- entries, path: changelogPath, normalize_only, workspace_root: realpathSync(context.directory),
210
- }) as Record<string, unknown>));
278
+ return output(
279
+ normalizeLegacyResult(
280
+ changelogApply({
281
+ entries,
282
+ path: changelogPath,
283
+ normalize_only,
284
+ workspace_root: realpathSync(context.directory),
285
+ }) as Record<string, unknown>,
286
+ ),
287
+ );
211
288
  },
212
289
  }),
213
290
  workflow_branch_setup: tool({
@@ -229,14 +306,20 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
229
306
  return output(fail(error instanceof Error ? error.message : "invalid SDD path"));
230
307
  }
231
308
  const result = branchSetup({
232
- action, sdd_dir: resolvedSdd, target_branch, stash, workspace_root: context.directory,
309
+ action,
310
+ sdd_dir: resolvedSdd,
311
+ target_branch,
312
+ stash,
313
+ workspace_root: context.directory,
233
314
  });
234
- return output(legacyScriptResult({
235
- stdout: JSON.stringify(result),
236
- stderr: "",
237
- exitCode: result.error ? 1 : 0,
238
- cwd: context.directory,
239
- }));
315
+ return output(
316
+ legacyScriptResult({
317
+ stdout: JSON.stringify(result),
318
+ stderr: "",
319
+ exitCode: result.error ? 1 : 0,
320
+ cwd: context.directory,
321
+ }),
322
+ );
240
323
  },
241
324
  }),
242
325
  workflow_commit: tool({
@@ -246,15 +329,24 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
246
329
  const rejected = requireConfirmed(confirmed);
247
330
  if (rejected) return rejected;
248
331
  const branch = runtime.git(context.directory, ["branch", "--show-current"]);
249
- if (branch.exitCode !== 0) return output(fail(
250
- branch.stderr.trim() || branch.stdout.trim() || "unable to read current branch", diagnostics(branch),
251
- ));
332
+ if (branch.exitCode !== 0)
333
+ return output(
334
+ fail(
335
+ branch.stderr.trim() || branch.stdout.trim() || "unable to read current branch",
336
+ diagnostics(branch),
337
+ ),
338
+ );
252
339
  const name = branch.stdout.trim();
253
340
  const pol = branchPolicy();
254
- if (pol.protected.has(name.toLowerCase())) return output(fail(`cannot commit on protected branch ${name}`));
255
- if (!pol.allowed.some((r) => r.test(name)) || name.endsWith("/")) return output(fail(`commit requires an allowed branch (current: ${name})`));
256
- return output(scriptResult(runtime.git(context.directory, ["commit", "-m", message]),
257
- (stdout) => ({ stdout: stdout.trim() })));
341
+ if (pol.protected.has(name.toLowerCase()))
342
+ return output(fail(`cannot commit on protected branch ${name}`));
343
+ if (!pol.allowed.some((r) => r.test(name)) || name.endsWith("/"))
344
+ return output(fail(`commit requires an allowed branch (current: ${name})`));
345
+ return output(
346
+ scriptResult(runtime.git(context.directory, ["commit", "-m", message]), (stdout) => ({
347
+ stdout: stdout.trim(),
348
+ })),
349
+ );
258
350
  },
259
351
  }),
260
352
  workflow_pr_create: tool({
@@ -270,21 +362,29 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
270
362
  const rejected = requireConfirmed(confirmed);
271
363
  if (rejected) return rejected;
272
364
  const branch = runtime.git(context.directory, ["branch", "--show-current"]);
273
- if (branch.exitCode !== 0) return output(fail(
274
- branch.stderr.trim() || branch.stdout.trim() || "unable to read current branch", diagnostics(branch),
275
- ));
365
+ if (branch.exitCode !== 0)
366
+ return output(
367
+ fail(
368
+ branch.stderr.trim() || branch.stdout.trim() || "unable to read current branch",
369
+ diagnostics(branch),
370
+ ),
371
+ );
276
372
  const name = branch.stdout.trim();
277
373
  const pol = branchPolicy();
278
374
  if (!pol.allowed.some((r) => r.test(name)) || name.endsWith("/")) {
279
375
  return output(fail(`PR creation requires an allowed branch (current: ${name})`));
280
376
  }
281
- return output(legacyScriptResult(runtime.runScript(context.directory, "pr-create.sh", [], {
282
- WF_PR_TITLE: title,
283
- WF_PR_BODY: body ?? "",
284
- WF_PR_CONFIRMED: "true",
285
- WF_PR_DRAFT: draft ? "true" : "false",
286
- WF_PR_TARGET: target_branch ?? "",
287
- })));
377
+ return output(
378
+ legacyScriptResult(
379
+ runtime.runScript(context.directory, "pr-create.sh", [], {
380
+ WF_PR_TITLE: title,
381
+ WF_PR_BODY: body ?? "",
382
+ WF_PR_CONFIRMED: "true",
383
+ WF_PR_DRAFT: draft ? "true" : "false",
384
+ WF_PR_TARGET: target_branch ?? "",
385
+ }),
386
+ ),
387
+ );
288
388
  },
289
389
  }),
290
390
  workflow_toolkit_init_apply: tool({
@@ -292,7 +392,13 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
292
392
  args: {
293
393
  confirmed: tool.schema.boolean(),
294
394
  action: tool.schema.enum([
295
- "youtrack_scaffold", "youtrack_json", "youtrack_token_placeholder", "vcs_scaffold", "config", "gitignore", "hygiene",
395
+ "youtrack_scaffold",
396
+ "youtrack_json",
397
+ "youtrack_token_placeholder",
398
+ "vcs_scaffold",
399
+ "config",
400
+ "gitignore",
401
+ "hygiene",
296
402
  ]),
297
403
  base_url: tool.schema.string().optional(),
298
404
  default_mention: tool.schema.string().optional(),
@@ -302,20 +408,39 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
302
408
  locale: tool.schema.string().optional(),
303
409
  locale_options: tool.schema.array(tool.schema.string()).optional(),
304
410
  timezone: tool.schema.string().optional(),
305
- branch_policy_preset: tool.schema.enum(["gitflow", "github-flow", "trunk-based", "custom"]).optional(),
411
+ branch_policy_preset: tool.schema
412
+ .enum(["gitflow", "github-flow", "trunk-based", "custom"])
413
+ .optional(),
306
414
  branch_policy_allowed: tool.schema.array(tool.schema.string()).optional(),
307
415
  branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
308
416
  include_open_source: tool.schema.boolean().optional(),
309
417
  },
310
- execute: async ({
311
- confirmed, action, base_url, default_mention, meeting_issue, vcs_provider, vcs_target_branch,
312
- locale, locale_options, timezone, branch_policy_preset, branch_policy_allowed, branch_policy_protected,
313
- include_open_source,
314
- }, context) => {
418
+ execute: async (
419
+ {
420
+ confirmed,
421
+ action,
422
+ base_url,
423
+ default_mention,
424
+ meeting_issue,
425
+ vcs_provider,
426
+ vcs_target_branch,
427
+ locale,
428
+ locale_options,
429
+ timezone,
430
+ branch_policy_preset,
431
+ branch_policy_allowed,
432
+ branch_policy_protected,
433
+ include_open_source,
434
+ },
435
+ context,
436
+ ) => {
315
437
  const rejected = requireConfirmed(confirmed);
316
438
  if (rejected) return rejected;
317
439
  if (action === "hygiene") {
318
- const result = ensureHygieneFiles(context.directory, { confirmed, includeOpenSource: include_open_source });
440
+ const result = ensureHygieneFiles(context.directory, {
441
+ confirmed,
442
+ includeOpenSource: include_open_source,
443
+ });
319
444
  return output(result.ok ? ok(result) : fail(result.error));
320
445
  }
321
446
  if (action === "gitignore") {
@@ -325,7 +450,9 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
325
450
  if (action === "config") {
326
451
  const LOCALE_RE = /^[a-z]{2,3}(-[A-Z]{2})?$/;
327
452
  if (locale !== undefined && !LOCALE_RE.test(locale)) {
328
- return output(fail(`invalid locale: ${JSON.stringify(locale)} — expected BCP-47 like en or es-CL`));
453
+ return output(
454
+ fail(`invalid locale: ${JSON.stringify(locale)} — expected BCP-47 like en or es-CL`),
455
+ );
329
456
  }
330
457
  const current = readConfig();
331
458
  const next: ToolkitConfig = {
@@ -339,18 +466,24 @@ export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
339
466
  },
340
467
  };
341
468
  writeConfig(next);
342
- return output(ok({ action: "config", path: path.join(configDir(), "config.json"), ...next }));
469
+ return output(
470
+ ok({ action: "config", path: path.join(configDir(), "config.json"), ...next }),
471
+ );
343
472
  }
344
- const env = Object.fromEntries(Object.entries({
345
- WORKFLOW_YT_BASE_URL: base_url,
346
- WORKFLOW_YT_MENTION: default_mention,
347
- WORKFLOW_YT_MEETING_ISSUE: meeting_issue,
348
- WORKFLOW_VCS_PROVIDER: vcs_provider,
349
- WORKFLOW_VCS_TARGET_BRANCH: vcs_target_branch,
350
- }).filter((entry): entry is [string, string] => entry[1] !== undefined));
351
- return output(legacyScriptResult(runtime.runScript(
352
- context.directory, "init/apply.sh", [action, "true"], env,
353
- )));
473
+ const env = Object.fromEntries(
474
+ Object.entries({
475
+ WORKFLOW_YT_BASE_URL: base_url,
476
+ WORKFLOW_YT_MENTION: default_mention,
477
+ WORKFLOW_YT_MEETING_ISSUE: meeting_issue,
478
+ WORKFLOW_VCS_PROVIDER: vcs_provider,
479
+ WORKFLOW_VCS_TARGET_BRANCH: vcs_target_branch,
480
+ }).filter((entry): entry is [string, string] => entry[1] !== undefined),
481
+ );
482
+ return output(
483
+ legacyScriptResult(
484
+ runtime.runScript(context.directory, "init/apply.sh", [action, "true"], env),
485
+ ),
486
+ );
354
487
  },
355
488
  }),
356
489
  };