@brainervirus/workit-core 0.6.1 → 0.7.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 +3 -7
- package/scripts/doctor-check.ts +20 -0
- package/scripts/install-cursor-plugin.sh +51 -28
- package/scripts/install-opencode-plugin.sh +19 -21
- package/scripts/rewrite-workspace-deps.ts +15 -9
- package/scripts/sync-runtime.sh +71 -19
- package/scripts/vendor-assets.ts +37 -0
- package/skills/wk-implement/SKILL.md +2 -2
- package/skills/wk-pr/SKILL.md +1 -1
- package/src/core/boundary.ts +27 -0
- package/src/core/branch-policy.ts +63 -0
- package/src/core/branch.ts +30 -16
- package/src/core/config.ts +193 -31
- package/src/core/docs-layout.ts +251 -0
- package/src/core/docs-migration.ts +639 -0
- package/src/core/docs-repo.ts +11 -9
- package/src/core/docs-validate.ts +18 -6
- package/src/core/doctor.ts +801 -0
- package/src/core/flow-state.ts +1579 -141
- package/src/core/git.ts +22 -5
- package/src/{tools/handoff.ts → core/handoff-tools.ts} +5 -57
- package/src/core/hygiene.ts +26 -12
- package/src/core/init.ts +43 -11
- package/src/core/logger.ts +321 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/ports/init-toolkit-status.ts +1 -1
- package/src/core/ports/vcs-verify-token.ts +1 -1
- package/src/core/ports/youtrack-api.ts +1 -1
- package/src/core/ports/youtrack-verify-token.ts +1 -1
- package/src/core/pr-create.ts +116 -21
- package/src/core/registration.ts +215 -0
- package/src/core/repo-context.ts +447 -0
- package/src/core/repo-tools.ts +23 -0
- package/src/core/safe-write.ts +22 -0
- package/src/core/scripts.ts +3 -44
- package/src/core/sdd.ts +45 -28
- package/src/core/setup-state.ts +54 -0
- package/src/core/setup.ts +1216 -0
- package/src/core/skill-manifests.ts +95 -0
- package/src/core/support-matrix.ts +12 -0
- package/src/core/sync-runtime.ts +348 -0
- package/src/core/templates.ts +2 -2
- package/src/core/vcs-config.ts +107 -37
- package/src/core/verify-project.ts +181 -0
- package/src/core/workspaces.ts +136 -17
- package/src/core/youtrack-tools.ts +228 -0
- package/src/core/youtrack.ts +125 -67
- package/templates/execution-contract.md +9 -7
- package/templates/superpowers-doc-contract.md +4 -3
- package/scripts/_shared/common.sh +0 -173
- package/scripts/changelog-context.sh +0 -42
- package/scripts/docs-refresh-context.sh +0 -40
- package/scripts/init/apply.sh +0 -5
- package/scripts/init/status.sh +0 -5
- package/scripts/init/toolkit-status.sh +0 -5
- package/scripts/pr-create.sh +0 -5
- package/scripts/pr-ready-context.sh +0 -88
- package/scripts/present/ascii-wireframe.sh +0 -5
- package/scripts/present/flow-diagram.sh +0 -5
- package/scripts/release-notes-context.sh +0 -40
- package/scripts/vcs/config.sh +0 -5
- package/scripts/vcs/merged-style.sh +0 -5
- package/scripts/vcs/token-create-urls.sh +0 -5
- package/scripts/vcs/verify-token.sh +0 -5
- package/scripts/verify-project.sh +0 -140
- package/scripts/youtrack/api.sh +0 -5
- package/scripts/youtrack/config.sh +0 -5
- package/scripts/youtrack/greeting.sh +0 -5
- package/scripts/youtrack/parse-duration.sh +0 -5
- package/scripts/youtrack/token-create-url.sh +0 -5
- package/scripts/youtrack/verify-token.sh +0 -5
- package/scripts/youtrack/work-date-ms.sh +0 -5
- package/src/tools/docs-repo.ts +0 -51
- package/src/tools/flow.ts +0 -99
- package/src/tools/index.ts +0 -22
- package/src/tools/present.ts +0 -49
- package/src/tools/repo.ts +0 -490
- package/src/tools/rules.ts +0 -30
- package/src/tools/sdd.ts +0 -216
- package/src/tools/templates.ts +0 -27
- package/src/tools/youtrack.ts +0 -423
package/src/tools/repo.ts
DELETED
|
@@ -1,490 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { realpathSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { tool, type ToolContext } from "@opencode-ai/plugin";
|
|
5
|
-
import { fail, gitRevisionParts, ok, resolveInside, run } from "../core";
|
|
6
|
-
import { changelogApply } from "../core/changelog";
|
|
7
|
-
import { gitContext } from "../core/git";
|
|
8
|
-
import { parseKeyValueLines, parseSections } from "../core/parse-sections";
|
|
9
|
-
import { parseVerifyOutput } from "../core/verify-parse";
|
|
10
|
-
import { branchSetup } from "../core/branch";
|
|
11
|
-
import {
|
|
12
|
-
configDir,
|
|
13
|
-
readConfig,
|
|
14
|
-
writeConfig,
|
|
15
|
-
type BranchPreset,
|
|
16
|
-
type ToolkitConfig,
|
|
17
|
-
} from "../core/config";
|
|
18
|
-
import { ensureProjectGitignore } from "../core/gitignore";
|
|
19
|
-
import { ensureHygieneFiles } from "../core/hygiene";
|
|
20
|
-
|
|
21
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
22
|
-
const scripts = path.join(packageRoot, "scripts");
|
|
23
|
-
type RunResult = ReturnType<typeof run>;
|
|
24
|
-
|
|
25
|
-
export type RepoRuntime = {
|
|
26
|
-
runScript(root: string, script: string, args: string[], env?: Record<string, string>): RunResult;
|
|
27
|
-
git(root: string, args: string[]): RunResult;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const defaultRuntime: RepoRuntime = {
|
|
31
|
-
runScript: (root, script, args, env) =>
|
|
32
|
-
run(root, "bash", [path.join(scripts, script), ...args], env),
|
|
33
|
-
git: (root, args) => run(root, "git", args),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
37
|
-
const diagnostics = ({ stdout, stderr, exitCode }: RunResult) => ({ stdout, stderr, exitCode });
|
|
38
|
-
const requireConfirmed = (confirmed: boolean) =>
|
|
39
|
-
confirmed === true ? null : output(fail("confirmed: true required"));
|
|
40
|
-
import { resolveBranchPolicy } from "../core/config";
|
|
41
|
-
const branchPolicy = () => resolveBranchPolicy(readConfig());
|
|
42
|
-
|
|
43
|
-
function scriptResult<T extends object>(result: RunResult, parse: (stdout: string) => T) {
|
|
44
|
-
if (result.exitCode !== 0) {
|
|
45
|
-
return fail(
|
|
46
|
-
result.stderr.trim() || result.stdout.trim() || "workflow script failed",
|
|
47
|
-
diagnostics(result),
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
return ok({
|
|
52
|
-
...parse(result.stdout),
|
|
53
|
-
exitCode: 0,
|
|
54
|
-
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
55
|
-
});
|
|
56
|
-
} catch (error) {
|
|
57
|
-
return fail(
|
|
58
|
-
error instanceof Error ? error.message : "workflow output parse failed",
|
|
59
|
-
diagnostics(result),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const json = (stdout: string) => JSON.parse(stdout.trim()) as Record<string, unknown>;
|
|
65
|
-
const legacyScriptResult = (result: RunResult) => {
|
|
66
|
-
let parsed: Record<string, unknown> | null = null;
|
|
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
|
-
);
|
|
81
|
-
if (!parsed) return fail("workflow output parse failed", diagnostics(result));
|
|
82
|
-
const { ok: _legacyOk, ...data } = parsed;
|
|
83
|
-
return ok({ ...data, exitCode: 0, ...(result.stderr ? { stderr: result.stderr } : {}) });
|
|
84
|
-
};
|
|
85
|
-
const optionalJson = (value: string | undefined) => {
|
|
86
|
-
if (!value?.trim()) return null;
|
|
87
|
-
try {
|
|
88
|
-
return JSON.parse(value);
|
|
89
|
-
} catch {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
const sections = (stdout: string) => parseSections(stdout) as Record<string, string>;
|
|
94
|
-
export const normalizeLegacyResult = (value: Record<string, unknown>) => {
|
|
95
|
-
if (value.error) return fail(String(value.error));
|
|
96
|
-
if (value.ok === false) return fail("legacy operation reported failure");
|
|
97
|
-
const { ok: _legacyOk, ...data } = value;
|
|
98
|
-
return ok(data);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const parsePr = (stdout: string) => {
|
|
102
|
-
const part = sections(stdout);
|
|
103
|
-
const repo = parseKeyValueLines(part.Repository ?? "", [
|
|
104
|
-
"branch",
|
|
105
|
-
"range",
|
|
106
|
-
"base_ref",
|
|
107
|
-
"merge_base",
|
|
108
|
-
"diff_range",
|
|
109
|
-
"range_mode",
|
|
110
|
-
"git_sync",
|
|
111
|
-
]);
|
|
112
|
-
return {
|
|
113
|
-
...repo,
|
|
114
|
-
commits: part.Commits ?? "",
|
|
115
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
116
|
-
files: part["Changed Files"] ?? "",
|
|
117
|
-
pr_template: part["PR Template"] ?? "",
|
|
118
|
-
vcs_config: optionalJson(part["VCS Config"]),
|
|
119
|
-
merged_pr_style: optionalJson(part["Merged PR Style"]),
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const parseChangelog = (stdout: string) => {
|
|
124
|
-
const part = sections(stdout);
|
|
125
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["branch", "range"]);
|
|
126
|
-
return {
|
|
127
|
-
...repo,
|
|
128
|
-
changelog_excerpt: part["Existing CHANGELOG.md"] ?? "",
|
|
129
|
-
rules: part["Keep a Changelog Rules"] ?? "",
|
|
130
|
-
commits: part.Commits ?? "",
|
|
131
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
132
|
-
files: part["Changed Files"] ?? "",
|
|
133
|
-
};
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const parseRelease = (stdout: string) => {
|
|
137
|
-
const part = sections(stdout);
|
|
138
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["requested", "range"]);
|
|
139
|
-
return {
|
|
140
|
-
...repo,
|
|
141
|
-
tags: part.Tags ?? "",
|
|
142
|
-
commits: part.Commits ?? "",
|
|
143
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
144
|
-
files: part["Changed Files"] ?? "",
|
|
145
|
-
release_files: part["Existing Release Files"] ?? "",
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const parseDocs = (stdout: string) => {
|
|
150
|
-
const part = sections(stdout);
|
|
151
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["branch", "range"]);
|
|
152
|
-
return {
|
|
153
|
-
...repo,
|
|
154
|
-
changed_files: part["Changed Files"] ?? "",
|
|
155
|
-
readme_preview: part["README Preview"] ?? "",
|
|
156
|
-
package_scripts: part["Package Scripts"] ?? "",
|
|
157
|
-
files: part["Documentation Files"] ?? "",
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
|
|
162
|
-
const validateRange = (root: string, value: string) => {
|
|
163
|
-
for (const revision of gitRevisionParts(value)) {
|
|
164
|
-
const resolved = runtime.git(root, [
|
|
165
|
-
"rev-parse",
|
|
166
|
-
"--verify",
|
|
167
|
-
"--quiet",
|
|
168
|
-
"--end-of-options",
|
|
169
|
-
`${revision}^{commit}`,
|
|
170
|
-
]);
|
|
171
|
-
if (resolved.exitCode !== 0) throw new Error(`invalid Git revision or range: ${value}`);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
const contextWithRange = (
|
|
175
|
-
root: string,
|
|
176
|
-
script: string,
|
|
177
|
-
value: string | undefined,
|
|
178
|
-
parse: (stdout: string) => Record<string, unknown>,
|
|
179
|
-
) => {
|
|
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
|
-
}
|
|
185
|
-
return output(scriptResult(runtime.runScript(root, script, value ? [value] : []), parse));
|
|
186
|
-
};
|
|
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));
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
workflow_toolkit_init_status: tool({
|
|
194
|
-
description: "Inspect toolkit initialization",
|
|
195
|
-
args: {},
|
|
196
|
-
execute: invoke("init/status.sh", json),
|
|
197
|
-
}),
|
|
198
|
-
workflow_toolkit_status: tool({
|
|
199
|
-
description: "Inspect toolkit and repository state",
|
|
200
|
-
args: {},
|
|
201
|
-
execute: invoke("init/toolkit-status.sh", json),
|
|
202
|
-
}),
|
|
203
|
-
workflow_git_context: tool({
|
|
204
|
-
description: "Read Git branch and change context",
|
|
205
|
-
args: { paths: tool.schema.array(tool.schema.string()).optional() },
|
|
206
|
-
execute: async ({ paths }, context) => output(ok(gitContext(context.directory, paths ?? []))),
|
|
207
|
-
}),
|
|
208
|
-
workflow_verify: tool({
|
|
209
|
-
description: "Discover and run repository verification",
|
|
210
|
-
args: { dry_run: tool.schema.boolean().optional() },
|
|
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
|
-
),
|
|
218
|
-
}),
|
|
219
|
-
workflow_pr_context: tool({
|
|
220
|
-
description: "Gather branch-exclusive PR context",
|
|
221
|
-
args: { range: tool.schema.string().optional() },
|
|
222
|
-
execute: async ({ range }, context) =>
|
|
223
|
-
contextWithRange(context.directory, "pr-ready-context.sh", range, parsePr),
|
|
224
|
-
}),
|
|
225
|
-
workflow_changelog_context: tool({
|
|
226
|
-
description: "Gather changelog context",
|
|
227
|
-
args: { range: tool.schema.string().optional() },
|
|
228
|
-
execute: async ({ range }, context) =>
|
|
229
|
-
contextWithRange(context.directory, "changelog-context.sh", range, parseChangelog),
|
|
230
|
-
}),
|
|
231
|
-
workflow_release_notes_context: tool({
|
|
232
|
-
description: "Gather release notes for an explicit range",
|
|
233
|
-
args: { range_or_tag: tool.schema.string() },
|
|
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
|
-
),
|
|
243
|
-
}),
|
|
244
|
-
workflow_docs_context: tool({
|
|
245
|
-
description: "Gather documentation refresh context",
|
|
246
|
-
args: { range: tool.schema.string().optional() },
|
|
247
|
-
execute: async ({ range }, context) =>
|
|
248
|
-
output(
|
|
249
|
-
scriptResult(
|
|
250
|
-
runtime.runScript(context.directory, "docs-refresh-context.sh", range ? [range] : []),
|
|
251
|
-
parseDocs,
|
|
252
|
-
),
|
|
253
|
-
),
|
|
254
|
-
}),
|
|
255
|
-
workflow_changelog_apply: tool({
|
|
256
|
-
description: "Apply confirmed Keep a Changelog entries to Unreleased",
|
|
257
|
-
args: {
|
|
258
|
-
confirmed: tool.schema.boolean(),
|
|
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(),
|
|
267
|
-
path: tool.schema.string().optional(),
|
|
268
|
-
normalize_only: tool.schema.boolean().optional(),
|
|
269
|
-
},
|
|
270
|
-
execute: async ({ confirmed, entries, path: changelogPath, normalize_only }, context) => {
|
|
271
|
-
const rejected = requireConfirmed(confirmed);
|
|
272
|
-
if (rejected) return rejected;
|
|
273
|
-
try {
|
|
274
|
-
changelogPath = resolveInside(context.directory, changelogPath ?? "CHANGELOG.md");
|
|
275
|
-
} catch (error) {
|
|
276
|
-
return output(fail(error instanceof Error ? error.message : "invalid changelog path"));
|
|
277
|
-
}
|
|
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
|
-
);
|
|
288
|
-
},
|
|
289
|
-
}),
|
|
290
|
-
workflow_branch_setup: tool({
|
|
291
|
-
description: "Apply a confirmed in-place feature or bugfix branch setup",
|
|
292
|
-
args: {
|
|
293
|
-
confirmed: tool.schema.boolean(),
|
|
294
|
-
action: tool.schema.enum(["setup", "reapply_stash"]).optional(),
|
|
295
|
-
sdd_dir: tool.schema.string().optional(),
|
|
296
|
-
target_branch: tool.schema.string().optional(),
|
|
297
|
-
stash: tool.schema.enum(["yes", "no"]).optional(),
|
|
298
|
-
},
|
|
299
|
-
execute: async ({ confirmed, action, sdd_dir, target_branch, stash }, context) => {
|
|
300
|
-
const rejected = requireConfirmed(confirmed);
|
|
301
|
-
if (rejected) return rejected;
|
|
302
|
-
let resolvedSdd = sdd_dir ?? "docs";
|
|
303
|
-
try {
|
|
304
|
-
resolvedSdd = resolveInside(context.directory, resolvedSdd);
|
|
305
|
-
} catch (error) {
|
|
306
|
-
return output(fail(error instanceof Error ? error.message : "invalid SDD path"));
|
|
307
|
-
}
|
|
308
|
-
const result = branchSetup({
|
|
309
|
-
action,
|
|
310
|
-
sdd_dir: resolvedSdd,
|
|
311
|
-
target_branch,
|
|
312
|
-
stash,
|
|
313
|
-
workspace_root: context.directory,
|
|
314
|
-
});
|
|
315
|
-
return output(
|
|
316
|
-
legacyScriptResult({
|
|
317
|
-
stdout: JSON.stringify(result),
|
|
318
|
-
stderr: "",
|
|
319
|
-
exitCode: result.error ? 1 : 0,
|
|
320
|
-
cwd: context.directory,
|
|
321
|
-
}),
|
|
322
|
-
);
|
|
323
|
-
},
|
|
324
|
-
}),
|
|
325
|
-
workflow_commit: tool({
|
|
326
|
-
description: "Commit the current index on a feature or bugfix branch without staging files",
|
|
327
|
-
args: { confirmed: tool.schema.boolean(), message: tool.schema.string() },
|
|
328
|
-
execute: async ({ confirmed, message }, context) => {
|
|
329
|
-
const rejected = requireConfirmed(confirmed);
|
|
330
|
-
if (rejected) return rejected;
|
|
331
|
-
const branch = runtime.git(context.directory, ["branch", "--show-current"]);
|
|
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
|
-
);
|
|
339
|
-
const name = branch.stdout.trim();
|
|
340
|
-
const pol = branchPolicy();
|
|
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
|
-
);
|
|
350
|
-
},
|
|
351
|
-
}),
|
|
352
|
-
workflow_pr_create: tool({
|
|
353
|
-
description: "Create a confirmed pull or merge request",
|
|
354
|
-
args: {
|
|
355
|
-
confirmed: tool.schema.boolean(),
|
|
356
|
-
title: tool.schema.string(),
|
|
357
|
-
body: tool.schema.string().optional(),
|
|
358
|
-
draft: tool.schema.boolean().optional(),
|
|
359
|
-
target_branch: tool.schema.string().optional(),
|
|
360
|
-
},
|
|
361
|
-
execute: async ({ confirmed, title, body, draft, target_branch }, context) => {
|
|
362
|
-
const rejected = requireConfirmed(confirmed);
|
|
363
|
-
if (rejected) return rejected;
|
|
364
|
-
const branch = runtime.git(context.directory, ["branch", "--show-current"]);
|
|
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
|
-
);
|
|
372
|
-
const name = branch.stdout.trim();
|
|
373
|
-
const pol = branchPolicy();
|
|
374
|
-
if (!pol.allowed.some((r) => r.test(name)) || name.endsWith("/")) {
|
|
375
|
-
return output(fail(`PR creation requires an allowed branch (current: ${name})`));
|
|
376
|
-
}
|
|
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
|
-
);
|
|
388
|
-
},
|
|
389
|
-
}),
|
|
390
|
-
workflow_toolkit_init_apply: tool({
|
|
391
|
-
description: "Apply a confirmed toolkit initialization action",
|
|
392
|
-
args: {
|
|
393
|
-
confirmed: tool.schema.boolean(),
|
|
394
|
-
action: tool.schema.enum([
|
|
395
|
-
"youtrack_scaffold",
|
|
396
|
-
"youtrack_json",
|
|
397
|
-
"youtrack_token_placeholder",
|
|
398
|
-
"vcs_scaffold",
|
|
399
|
-
"config",
|
|
400
|
-
"gitignore",
|
|
401
|
-
"hygiene",
|
|
402
|
-
]),
|
|
403
|
-
base_url: tool.schema.string().optional(),
|
|
404
|
-
default_mention: tool.schema.string().optional(),
|
|
405
|
-
meeting_issue: tool.schema.string().optional(),
|
|
406
|
-
vcs_provider: tool.schema.enum(["gitlab", "github"]).optional(),
|
|
407
|
-
vcs_target_branch: tool.schema.string().optional(),
|
|
408
|
-
locale: tool.schema.string().optional(),
|
|
409
|
-
locale_options: tool.schema.array(tool.schema.string()).optional(),
|
|
410
|
-
timezone: tool.schema.string().optional(),
|
|
411
|
-
branch_policy_preset: tool.schema
|
|
412
|
-
.enum(["gitflow", "github-flow", "trunk-based", "custom"])
|
|
413
|
-
.optional(),
|
|
414
|
-
branch_policy_allowed: tool.schema.array(tool.schema.string()).optional(),
|
|
415
|
-
branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
|
|
416
|
-
include_open_source: tool.schema.boolean().optional(),
|
|
417
|
-
},
|
|
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
|
-
) => {
|
|
437
|
-
const rejected = requireConfirmed(confirmed);
|
|
438
|
-
if (rejected) return rejected;
|
|
439
|
-
if (action === "hygiene") {
|
|
440
|
-
const result = ensureHygieneFiles(context.directory, {
|
|
441
|
-
confirmed,
|
|
442
|
-
includeOpenSource: include_open_source,
|
|
443
|
-
});
|
|
444
|
-
return output(result.ok ? ok(result) : fail(result.error));
|
|
445
|
-
}
|
|
446
|
-
if (action === "gitignore") {
|
|
447
|
-
const result = ensureProjectGitignore(context.directory, confirmed);
|
|
448
|
-
return output(result.ok ? ok(result) : fail(result.error));
|
|
449
|
-
}
|
|
450
|
-
if (action === "config") {
|
|
451
|
-
const LOCALE_RE = /^[a-z]{2,3}(-[A-Z]{2})?$/;
|
|
452
|
-
if (locale !== undefined && !LOCALE_RE.test(locale)) {
|
|
453
|
-
return output(
|
|
454
|
-
fail(`invalid locale: ${JSON.stringify(locale)} — expected BCP-47 like en or es-CL`),
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
const current = readConfig();
|
|
458
|
-
const next: ToolkitConfig = {
|
|
459
|
-
locale: locale ?? current.locale,
|
|
460
|
-
localeOptions: locale_options ?? current.localeOptions,
|
|
461
|
-
timezone: timezone ?? current.timezone,
|
|
462
|
-
branchPolicy: {
|
|
463
|
-
preset: (branch_policy_preset as BranchPreset) ?? current.branchPolicy.preset,
|
|
464
|
-
allowed: branch_policy_allowed ?? current.branchPolicy.allowed,
|
|
465
|
-
protected: branch_policy_protected ?? current.branchPolicy.protected,
|
|
466
|
-
},
|
|
467
|
-
};
|
|
468
|
-
writeConfig(next);
|
|
469
|
-
return output(
|
|
470
|
-
ok({ action: "config", path: path.join(configDir(), "config.json"), ...next }),
|
|
471
|
-
);
|
|
472
|
-
}
|
|
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
|
-
);
|
|
487
|
-
},
|
|
488
|
-
}),
|
|
489
|
-
};
|
|
490
|
-
}
|
package/src/tools/rules.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { fail, ok } from "../core";
|
|
3
|
-
import { listRules, writeRule, type CanonicalRule } from "../core/rules";
|
|
4
|
-
|
|
5
|
-
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
6
|
-
|
|
7
|
-
export function createRuleTools() {
|
|
8
|
-
return {
|
|
9
|
-
workflow_rule_list: tool({
|
|
10
|
-
description: "List canonical rules (config) with platforms and source",
|
|
11
|
-
args: {},
|
|
12
|
-
execute: async () => output(ok({ rules: listRules() })),
|
|
13
|
-
}),
|
|
14
|
-
workflow_rule_edit: tool({
|
|
15
|
-
description: "Write a canonical rule to the toolkit config dir (agent-assisted)",
|
|
16
|
-
args: {
|
|
17
|
-
name: tool.schema.string(),
|
|
18
|
-
description: tool.schema.string(),
|
|
19
|
-
platforms: tool.schema.array(tool.schema.enum(["cursor", "opencode"])),
|
|
20
|
-
body: tool.schema.string(),
|
|
21
|
-
confirmed: tool.schema.boolean(),
|
|
22
|
-
},
|
|
23
|
-
execute: async ({ name, description, platforms, body, confirmed }) => {
|
|
24
|
-
const rule: CanonicalRule = { name, description, platforms, body };
|
|
25
|
-
const result = writeRule(rule, confirmed);
|
|
26
|
-
return output(result.ok ? ok(result) : fail(result.error));
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
};
|
|
30
|
-
}
|