@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/tools/handoff.ts
CHANGED
|
@@ -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 }) =>
|
|
41
|
-
|
|
42
|
-
|
|
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) =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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({
|
|
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:
|
|
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(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
}
|
package/src/tools/present.ts
CHANGED
|
@@ -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(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 {
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
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({
|
|
51
|
+
return ok({
|
|
52
|
+
...parse(result.stdout),
|
|
53
|
+
exitCode: 0,
|
|
54
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
55
|
+
});
|
|
41
56
|
} catch (error) {
|
|
42
|
-
return fail(
|
|
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 {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 {
|
|
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",
|
|
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, [
|
|
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,
|
|
175
|
+
root: string,
|
|
176
|
+
script: string,
|
|
177
|
+
value: string | undefined,
|
|
137
178
|
parse: (stdout: string) => Record<string, unknown>,
|
|
138
179
|
) => {
|
|
139
|
-
try {
|
|
140
|
-
|
|
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 =
|
|
144
|
-
|
|
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",
|
|
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",
|
|
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) =>
|
|
162
|
-
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
179
|
-
|
|
180
|
-
|
|
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) =>
|
|
186
|
-
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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(
|
|
209
|
-
|
|
210
|
-
|
|
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,
|
|
309
|
+
action,
|
|
310
|
+
sdd_dir: resolvedSdd,
|
|
311
|
+
target_branch,
|
|
312
|
+
stash,
|
|
313
|
+
workspace_root: context.directory,
|
|
233
314
|
});
|
|
234
|
-
return output(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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)
|
|
250
|
-
|
|
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()))
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
(
|
|
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)
|
|
274
|
-
|
|
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(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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",
|
|
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
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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, {
|
|
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(
|
|
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(
|
|
469
|
+
return output(
|
|
470
|
+
ok({ action: "config", path: path.join(configDir(), "config.json"), ...next }),
|
|
471
|
+
);
|
|
343
472
|
}
|
|
344
|
-
const env = Object.fromEntries(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
};
|