@brainervirus/workit-core 0.6.0 → 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 +10 -14
- package/scripts/doctor-check.ts +20 -0
- package/scripts/install-cursor-plugin.sh +51 -28
- package/scripts/install-opencode-plugin.sh +25 -22
- 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 +166 -57
- package/src/core/changelog.ts +17 -14
- package/src/core/config-guard.ts +9 -2
- package/src/core/config.ts +227 -32
- package/src/core/detector.ts +22 -11
- package/src/core/docs-layout.ts +251 -0
- package/src/core/docs-migration.ts +639 -0
- package/src/core/docs-repo.ts +58 -21
- package/src/core/docs-validate.ts +181 -43
- package/src/core/doctor.ts +801 -0
- package/src/core/flow-state.ts +1579 -137
- package/src/core/git.ts +22 -5
- package/src/core/gitignore.ts +11 -2
- package/src/core/handoff-context.ts +18 -5
- package/src/{tools/handoff.ts → core/handoff-tools.ts} +16 -63
- package/src/core/hygiene.ts +53 -17
- package/src/core/init.ts +128 -31
- package/src/core/logger.ts +321 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/parse-sections.ts +2 -2
- package/src/core/plan-tasks.ts +13 -3
- 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 +4 -2
- package/src/core/ports/youtrack-config.ts +1 -3
- package/src/core/ports/youtrack-verify-token.ts +1 -1
- package/src/core/pr-create.ts +156 -29
- package/src/core/present.ts +11 -2
- package/src/core/registration.ts +215 -0
- package/src/core/reminder.ts +1 -2
- package/src/core/repo-context.ts +447 -0
- package/src/core/repo-tool.ts +4 -1
- package/src/core/repo-tools.ts +23 -0
- package/src/core/rules.ts +10 -7
- package/src/core/safe-write.ts +22 -0
- package/src/core/scripts.ts +3 -39
- package/src/core/sdd.ts +56 -31
- 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 +16 -6
- package/src/core/vcs-config.ts +191 -62
- package/src/core/verify-parse.ts +4 -2
- 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 +320 -87
- package/src/core.ts +18 -3
- package/templates/execution-contract.md +9 -7
- package/templates/superpowers-doc-contract.md +5 -4
- package/scripts/_shared/common.sh +0 -158
- 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 -42
- package/src/tools/flow.ts +0 -88
- package/src/tools/index.ts +0 -22
- package/src/tools/present.ts +0 -45
- package/src/tools/repo.ts +0 -357
- package/src/tools/rules.ts +0 -30
- package/src/tools/sdd.ts +0 -189
- package/src/tools/templates.ts +0 -27
- package/src/tools/youtrack.ts +0 -360
package/src/tools/repo.ts
DELETED
|
@@ -1,357 +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 { configDir, readConfig, writeConfig, type BranchPreset, type ToolkitConfig } from "../core/config";
|
|
12
|
-
import { ensureProjectGitignore } from "../core/gitignore";
|
|
13
|
-
import { ensureHygieneFiles } from "../core/hygiene";
|
|
14
|
-
|
|
15
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
16
|
-
const scripts = path.join(packageRoot, "scripts");
|
|
17
|
-
type RunResult = ReturnType<typeof run>;
|
|
18
|
-
|
|
19
|
-
export type RepoRuntime = {
|
|
20
|
-
runScript(root: string, script: string, args: string[], env?: Record<string, string>): RunResult;
|
|
21
|
-
git(root: string, args: string[]): RunResult;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const defaultRuntime: RepoRuntime = {
|
|
25
|
-
runScript: (root, script, args, env) => run(root, "bash", [path.join(scripts, script), ...args], env),
|
|
26
|
-
git: (root, args) => run(root, "git", args),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
30
|
-
const diagnostics = ({ stdout, stderr, exitCode }: RunResult) => ({ stdout, stderr, exitCode });
|
|
31
|
-
const requireConfirmed = (confirmed: boolean) => confirmed === true ? null : output(fail("confirmed: true required"));
|
|
32
|
-
import { resolveBranchPolicy } from "../core/config";
|
|
33
|
-
const branchPolicy = () => resolveBranchPolicy(readConfig());
|
|
34
|
-
|
|
35
|
-
function scriptResult<T extends object>(result: RunResult, parse: (stdout: string) => T) {
|
|
36
|
-
if (result.exitCode !== 0) {
|
|
37
|
-
return fail(result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
return ok({ ...parse(result.stdout), exitCode: 0, ...(result.stderr ? { stderr: result.stderr } : {}) });
|
|
41
|
-
} catch (error) {
|
|
42
|
-
return fail(error instanceof Error ? error.message : "workflow output parse failed", diagnostics(result));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const json = (stdout: string) => JSON.parse(stdout.trim()) as Record<string, unknown>;
|
|
47
|
-
const legacyScriptResult = (result: RunResult) => {
|
|
48
|
-
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
|
-
);
|
|
58
|
-
if (!parsed) return fail("workflow output parse failed", diagnostics(result));
|
|
59
|
-
const { ok: _legacyOk, ...data } = parsed;
|
|
60
|
-
return ok({ ...data, exitCode: 0, ...(result.stderr ? { stderr: result.stderr } : {}) });
|
|
61
|
-
};
|
|
62
|
-
const optionalJson = (value: string | undefined) => {
|
|
63
|
-
if (!value?.trim()) return null;
|
|
64
|
-
try { return JSON.parse(value); } catch { return null; }
|
|
65
|
-
};
|
|
66
|
-
const sections = (stdout: string) => parseSections(stdout) as Record<string, string>;
|
|
67
|
-
export const normalizeLegacyResult = (value: Record<string, unknown>) => {
|
|
68
|
-
if (value.error) return fail(String(value.error));
|
|
69
|
-
if (value.ok === false) return fail("legacy operation reported failure");
|
|
70
|
-
const { ok: _legacyOk, ...data } = value;
|
|
71
|
-
return ok(data);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const parsePr = (stdout: string) => {
|
|
75
|
-
const part = sections(stdout);
|
|
76
|
-
const repo = parseKeyValueLines(part.Repository ?? "", [
|
|
77
|
-
"branch", "range", "base_ref", "merge_base", "diff_range", "range_mode", "git_sync",
|
|
78
|
-
]);
|
|
79
|
-
return {
|
|
80
|
-
...repo,
|
|
81
|
-
commits: part.Commits ?? "",
|
|
82
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
83
|
-
files: part["Changed Files"] ?? "",
|
|
84
|
-
pr_template: part["PR Template"] ?? "",
|
|
85
|
-
vcs_config: optionalJson(part["VCS Config"]),
|
|
86
|
-
merged_pr_style: optionalJson(part["Merged PR Style"]),
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const parseChangelog = (stdout: string) => {
|
|
91
|
-
const part = sections(stdout);
|
|
92
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["branch", "range"]);
|
|
93
|
-
return {
|
|
94
|
-
...repo,
|
|
95
|
-
changelog_excerpt: part["Existing CHANGELOG.md"] ?? "",
|
|
96
|
-
rules: part["Keep a Changelog Rules"] ?? "",
|
|
97
|
-
commits: part.Commits ?? "",
|
|
98
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
99
|
-
files: part["Changed Files"] ?? "",
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const parseRelease = (stdout: string) => {
|
|
104
|
-
const part = sections(stdout);
|
|
105
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["requested", "range"]);
|
|
106
|
-
return {
|
|
107
|
-
...repo,
|
|
108
|
-
tags: part.Tags ?? "",
|
|
109
|
-
commits: part.Commits ?? "",
|
|
110
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
111
|
-
files: part["Changed Files"] ?? "",
|
|
112
|
-
release_files: part["Existing Release Files"] ?? "",
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const parseDocs = (stdout: string) => {
|
|
117
|
-
const part = sections(stdout);
|
|
118
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["branch", "range"]);
|
|
119
|
-
return {
|
|
120
|
-
...repo,
|
|
121
|
-
changed_files: part["Changed Files"] ?? "",
|
|
122
|
-
readme_preview: part["README Preview"] ?? "",
|
|
123
|
-
package_scripts: part["Package Scripts"] ?? "",
|
|
124
|
-
files: part["Documentation Files"] ?? "",
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
export function createRepoTools(runtime: RepoRuntime = defaultRuntime) {
|
|
129
|
-
const validateRange = (root: string, value: string) => {
|
|
130
|
-
for (const revision of gitRevisionParts(value)) {
|
|
131
|
-
const resolved = runtime.git(root, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${revision}^{commit}`]);
|
|
132
|
-
if (resolved.exitCode !== 0) throw new Error(`invalid Git revision or range: ${value}`);
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
const contextWithRange = (
|
|
136
|
-
root: string, script: string, value: string | undefined,
|
|
137
|
-
parse: (stdout: string) => Record<string, unknown>,
|
|
138
|
-
) => {
|
|
139
|
-
try { if (value) validateRange(root, value); }
|
|
140
|
-
catch (error) { return output(fail(error instanceof Error ? error.message : "invalid Git revision or range")); }
|
|
141
|
-
return output(scriptResult(runtime.runScript(root, script, value ? [value] : []), parse));
|
|
142
|
-
};
|
|
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));
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
workflow_toolkit_init_status: tool({
|
|
148
|
-
description: "Inspect toolkit initialization", args: {}, execute: invoke("init/status.sh", json),
|
|
149
|
-
}),
|
|
150
|
-
workflow_toolkit_status: tool({
|
|
151
|
-
description: "Inspect toolkit and repository state", args: {}, execute: invoke("init/toolkit-status.sh", json),
|
|
152
|
-
}),
|
|
153
|
-
workflow_git_context: tool({
|
|
154
|
-
description: "Read Git branch and change context",
|
|
155
|
-
args: { paths: tool.schema.array(tool.schema.string()).optional() },
|
|
156
|
-
execute: async ({ paths }, context) => output(ok(gitContext(context.directory, paths ?? []))),
|
|
157
|
-
}),
|
|
158
|
-
workflow_verify: tool({
|
|
159
|
-
description: "Discover and run repository verification",
|
|
160
|
-
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
|
-
)),
|
|
164
|
-
}),
|
|
165
|
-
workflow_pr_context: tool({
|
|
166
|
-
description: "Gather branch-exclusive PR context",
|
|
167
|
-
args: { range: tool.schema.string().optional() },
|
|
168
|
-
execute: async ({ range }, context) => contextWithRange(context.directory, "pr-ready-context.sh", range, parsePr),
|
|
169
|
-
}),
|
|
170
|
-
workflow_changelog_context: tool({
|
|
171
|
-
description: "Gather changelog context",
|
|
172
|
-
args: { range: tool.schema.string().optional() },
|
|
173
|
-
execute: async ({ range }, context) => contextWithRange(context.directory, "changelog-context.sh", range, parseChangelog),
|
|
174
|
-
}),
|
|
175
|
-
workflow_release_notes_context: tool({
|
|
176
|
-
description: "Gather release notes for an explicit range",
|
|
177
|
-
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),
|
|
181
|
-
}),
|
|
182
|
-
workflow_docs_context: tool({
|
|
183
|
-
description: "Gather documentation refresh context",
|
|
184
|
-
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
|
-
)),
|
|
188
|
-
}),
|
|
189
|
-
workflow_changelog_apply: tool({
|
|
190
|
-
description: "Apply confirmed Keep a Changelog entries to Unreleased",
|
|
191
|
-
args: {
|
|
192
|
-
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(),
|
|
197
|
-
path: tool.schema.string().optional(),
|
|
198
|
-
normalize_only: tool.schema.boolean().optional(),
|
|
199
|
-
},
|
|
200
|
-
execute: async ({ confirmed, entries, path: changelogPath, normalize_only }, context) => {
|
|
201
|
-
const rejected = requireConfirmed(confirmed);
|
|
202
|
-
if (rejected) return rejected;
|
|
203
|
-
try {
|
|
204
|
-
changelogPath = resolveInside(context.directory, changelogPath ?? "CHANGELOG.md");
|
|
205
|
-
} catch (error) {
|
|
206
|
-
return output(fail(error instanceof Error ? error.message : "invalid changelog path"));
|
|
207
|
-
}
|
|
208
|
-
return output(normalizeLegacyResult(changelogApply({
|
|
209
|
-
entries, path: changelogPath, normalize_only, workspace_root: realpathSync(context.directory),
|
|
210
|
-
}) as Record<string, unknown>));
|
|
211
|
-
},
|
|
212
|
-
}),
|
|
213
|
-
workflow_branch_setup: tool({
|
|
214
|
-
description: "Apply a confirmed in-place feature or bugfix branch setup",
|
|
215
|
-
args: {
|
|
216
|
-
confirmed: tool.schema.boolean(),
|
|
217
|
-
action: tool.schema.enum(["setup", "reapply_stash"]).optional(),
|
|
218
|
-
sdd_dir: tool.schema.string().optional(),
|
|
219
|
-
target_branch: tool.schema.string().optional(),
|
|
220
|
-
stash: tool.schema.enum(["yes", "no"]).optional(),
|
|
221
|
-
},
|
|
222
|
-
execute: async ({ confirmed, action, sdd_dir, target_branch, stash }, context) => {
|
|
223
|
-
const rejected = requireConfirmed(confirmed);
|
|
224
|
-
if (rejected) return rejected;
|
|
225
|
-
let resolvedSdd = sdd_dir ?? "docs";
|
|
226
|
-
try {
|
|
227
|
-
resolvedSdd = resolveInside(context.directory, resolvedSdd);
|
|
228
|
-
} catch (error) {
|
|
229
|
-
return output(fail(error instanceof Error ? error.message : "invalid SDD path"));
|
|
230
|
-
}
|
|
231
|
-
const result = branchSetup({
|
|
232
|
-
action, sdd_dir: resolvedSdd, target_branch, stash, workspace_root: context.directory,
|
|
233
|
-
});
|
|
234
|
-
return output(legacyScriptResult({
|
|
235
|
-
stdout: JSON.stringify(result),
|
|
236
|
-
stderr: "",
|
|
237
|
-
exitCode: result.error ? 1 : 0,
|
|
238
|
-
cwd: context.directory,
|
|
239
|
-
}));
|
|
240
|
-
},
|
|
241
|
-
}),
|
|
242
|
-
workflow_commit: tool({
|
|
243
|
-
description: "Commit the current index on a feature or bugfix branch without staging files",
|
|
244
|
-
args: { confirmed: tool.schema.boolean(), message: tool.schema.string() },
|
|
245
|
-
execute: async ({ confirmed, message }, context) => {
|
|
246
|
-
const rejected = requireConfirmed(confirmed);
|
|
247
|
-
if (rejected) return rejected;
|
|
248
|
-
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
|
-
));
|
|
252
|
-
const name = branch.stdout.trim();
|
|
253
|
-
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() })));
|
|
258
|
-
},
|
|
259
|
-
}),
|
|
260
|
-
workflow_pr_create: tool({
|
|
261
|
-
description: "Create a confirmed pull or merge request",
|
|
262
|
-
args: {
|
|
263
|
-
confirmed: tool.schema.boolean(),
|
|
264
|
-
title: tool.schema.string(),
|
|
265
|
-
body: tool.schema.string().optional(),
|
|
266
|
-
draft: tool.schema.boolean().optional(),
|
|
267
|
-
target_branch: tool.schema.string().optional(),
|
|
268
|
-
},
|
|
269
|
-
execute: async ({ confirmed, title, body, draft, target_branch }, context) => {
|
|
270
|
-
const rejected = requireConfirmed(confirmed);
|
|
271
|
-
if (rejected) return rejected;
|
|
272
|
-
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
|
-
));
|
|
276
|
-
const name = branch.stdout.trim();
|
|
277
|
-
const pol = branchPolicy();
|
|
278
|
-
if (!pol.allowed.some((r) => r.test(name)) || name.endsWith("/")) {
|
|
279
|
-
return output(fail(`PR creation requires an allowed branch (current: ${name})`));
|
|
280
|
-
}
|
|
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
|
-
})));
|
|
288
|
-
},
|
|
289
|
-
}),
|
|
290
|
-
workflow_toolkit_init_apply: tool({
|
|
291
|
-
description: "Apply a confirmed toolkit initialization action",
|
|
292
|
-
args: {
|
|
293
|
-
confirmed: tool.schema.boolean(),
|
|
294
|
-
action: tool.schema.enum([
|
|
295
|
-
"youtrack_scaffold", "youtrack_json", "youtrack_token_placeholder", "vcs_scaffold", "config", "gitignore", "hygiene",
|
|
296
|
-
]),
|
|
297
|
-
base_url: tool.schema.string().optional(),
|
|
298
|
-
default_mention: tool.schema.string().optional(),
|
|
299
|
-
meeting_issue: tool.schema.string().optional(),
|
|
300
|
-
vcs_provider: tool.schema.enum(["gitlab", "github"]).optional(),
|
|
301
|
-
vcs_target_branch: tool.schema.string().optional(),
|
|
302
|
-
locale: tool.schema.string().optional(),
|
|
303
|
-
locale_options: tool.schema.array(tool.schema.string()).optional(),
|
|
304
|
-
timezone: tool.schema.string().optional(),
|
|
305
|
-
branch_policy_preset: tool.schema.enum(["gitflow", "github-flow", "trunk-based", "custom"]).optional(),
|
|
306
|
-
branch_policy_allowed: tool.schema.array(tool.schema.string()).optional(),
|
|
307
|
-
branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
|
|
308
|
-
include_open_source: tool.schema.boolean().optional(),
|
|
309
|
-
},
|
|
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) => {
|
|
315
|
-
const rejected = requireConfirmed(confirmed);
|
|
316
|
-
if (rejected) return rejected;
|
|
317
|
-
if (action === "hygiene") {
|
|
318
|
-
const result = ensureHygieneFiles(context.directory, { confirmed, includeOpenSource: include_open_source });
|
|
319
|
-
return output(result.ok ? ok(result) : fail(result.error));
|
|
320
|
-
}
|
|
321
|
-
if (action === "gitignore") {
|
|
322
|
-
const result = ensureProjectGitignore(context.directory, confirmed);
|
|
323
|
-
return output(result.ok ? ok(result) : fail(result.error));
|
|
324
|
-
}
|
|
325
|
-
if (action === "config") {
|
|
326
|
-
const LOCALE_RE = /^[a-z]{2,3}(-[A-Z]{2})?$/;
|
|
327
|
-
if (locale !== undefined && !LOCALE_RE.test(locale)) {
|
|
328
|
-
return output(fail(`invalid locale: ${JSON.stringify(locale)} — expected BCP-47 like en or es-CL`));
|
|
329
|
-
}
|
|
330
|
-
const current = readConfig();
|
|
331
|
-
const next: ToolkitConfig = {
|
|
332
|
-
locale: locale ?? current.locale,
|
|
333
|
-
localeOptions: locale_options ?? current.localeOptions,
|
|
334
|
-
timezone: timezone ?? current.timezone,
|
|
335
|
-
branchPolicy: {
|
|
336
|
-
preset: (branch_policy_preset as BranchPreset) ?? current.branchPolicy.preset,
|
|
337
|
-
allowed: branch_policy_allowed ?? current.branchPolicy.allowed,
|
|
338
|
-
protected: branch_policy_protected ?? current.branchPolicy.protected,
|
|
339
|
-
},
|
|
340
|
-
};
|
|
341
|
-
writeConfig(next);
|
|
342
|
-
return output(ok({ action: "config", path: path.join(configDir(), "config.json"), ...next }));
|
|
343
|
-
}
|
|
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
|
-
)));
|
|
354
|
-
},
|
|
355
|
-
}),
|
|
356
|
-
};
|
|
357
|
-
}
|
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
|
-
}
|
package/src/tools/sdd.ts
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { tool, type ToolContext } from "@opencode-ai/plugin";
|
|
4
|
-
import { fail, ok, resolveGitRevision, resolveInside } from "../core";
|
|
5
|
-
import { resolveBranch, docsBranch } from "../core/branch";
|
|
6
|
-
import { docsValidate } from "../core/docs-validate";
|
|
7
|
-
import { parsePlanTasks, resolveHandoffBranch } from "../core/plan-tasks";
|
|
8
|
-
import {
|
|
9
|
-
sddAppendProgress, sddContext, sddReviewPackage, sddTaskBrief,
|
|
10
|
-
} from "../core/sdd";
|
|
11
|
-
import { WorkflowStateStore } from "../state";
|
|
12
|
-
|
|
13
|
-
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
14
|
-
const requireConfirmed = (confirmed: boolean) => confirmed === true
|
|
15
|
-
? null
|
|
16
|
-
: output(fail("confirmed: true required"));
|
|
17
|
-
|
|
18
|
-
const relativePath = (root: string, candidate: string) => {
|
|
19
|
-
if (path.isAbsolute(candidate)) throw new Error("path must be repository-relative");
|
|
20
|
-
resolveInside(root, candidate);
|
|
21
|
-
return candidate;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const normalize = (value: Record<string, unknown>) => {
|
|
25
|
-
if (value.error) return fail(String(value.error));
|
|
26
|
-
if (value.ok === false) return fail("legacy operation reported failure");
|
|
27
|
-
const { ok: _legacyOk, ...data } = value;
|
|
28
|
-
return ok(data);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const invoke = (operation: () => Record<string, unknown>) => {
|
|
32
|
-
try {
|
|
33
|
-
return output(normalize(operation()));
|
|
34
|
-
} catch (error) {
|
|
35
|
-
return output(fail(error instanceof Error ? error.message : "workflow operation failed"));
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const planPaths = (root: string, planPath: string, suppliedSpecPath?: string) => {
|
|
40
|
-
const resolved = resolveInside(root, planPath);
|
|
41
|
-
const match = readFileSync(resolved, "utf8").match(/^\*\*Spec:\*\*\s*(?:`([^`]+)`|(\S+))/m);
|
|
42
|
-
const spec_path = suppliedSpecPath ?? match?.[1] ?? match?.[2] ?? "";
|
|
43
|
-
if (spec_path) relativePath(root, spec_path);
|
|
44
|
-
return {
|
|
45
|
-
spec_path,
|
|
46
|
-
plan_path: planPath,
|
|
47
|
-
sdd_dir: path.posix.join("docs", path.basename(path.dirname(planPath)), "sdd"),
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export function createSddTools(state: WorkflowStateStore) {
|
|
52
|
-
const record = (context: ToolContext, data: Record<string, unknown>) => state.set(context.sessionID, {
|
|
53
|
-
spec: String(data.spec_path ?? ""),
|
|
54
|
-
plan: String(data.plan_path ?? ""),
|
|
55
|
-
sdd: String(data.sdd_dir ?? ""),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
workflow_docs_branch: tool({
|
|
60
|
-
description: "Resolve branch for spec/plan authors: keep current feature|bugfix or create from develop",
|
|
61
|
-
args: {
|
|
62
|
-
plan_path: tool.schema.string().optional(),
|
|
63
|
-
kind: tool.schema.enum(["feature", "bugfix"]).optional(),
|
|
64
|
-
},
|
|
65
|
-
execute: async ({ plan_path, kind }, context) => invoke(() => {
|
|
66
|
-
if (plan_path) relativePath(context.directory, plan_path);
|
|
67
|
-
return docsBranch({
|
|
68
|
-
plan_path,
|
|
69
|
-
kind,
|
|
70
|
-
workspace_root: context.directory,
|
|
71
|
-
}) as Record<string, unknown>;
|
|
72
|
-
}),
|
|
73
|
-
}),
|
|
74
|
-
workflow_docs_validate: tool({
|
|
75
|
-
description: "Hard-fail validate spec/plan headers, link, branch, task order; returns quality findings (hard/warning)",
|
|
76
|
-
args: {
|
|
77
|
-
spec_path: tool.schema.string(),
|
|
78
|
-
plan_path: tool.schema.string(),
|
|
79
|
-
},
|
|
80
|
-
execute: async ({ spec_path, plan_path }, context) => invoke(() => {
|
|
81
|
-
relativePath(context.directory, spec_path);
|
|
82
|
-
relativePath(context.directory, plan_path);
|
|
83
|
-
const result = docsValidate({
|
|
84
|
-
spec_path,
|
|
85
|
-
plan_path,
|
|
86
|
-
workspace_root: context.directory,
|
|
87
|
-
}) as Record<string, unknown>;
|
|
88
|
-
if (result.error) return result;
|
|
89
|
-
if (result.ok === false) return result;
|
|
90
|
-
return result;
|
|
91
|
-
}),
|
|
92
|
-
}),
|
|
93
|
-
workflow_plan_tasks: tool({
|
|
94
|
-
description: "Parse top-level tasks from a workflow plan",
|
|
95
|
-
args: { plan_path: tool.schema.string(), spec_path: tool.schema.string().optional() },
|
|
96
|
-
execute: async ({ plan_path, spec_path }, context) => invoke(() => {
|
|
97
|
-
relativePath(context.directory, plan_path);
|
|
98
|
-
const paths = planPaths(context.directory, plan_path, spec_path);
|
|
99
|
-
const parsed = parsePlanTasks(plan_path, context.directory) as Record<string, unknown>;
|
|
100
|
-
if (parsed.error) return parsed;
|
|
101
|
-
const branch = paths.spec_path
|
|
102
|
-
? resolveHandoffBranch(paths.spec_path, plan_path, context.directory) as Record<string, unknown>
|
|
103
|
-
: {};
|
|
104
|
-
if (branch.error) return branch;
|
|
105
|
-
const data = { ...parsed, ...paths, ...branch };
|
|
106
|
-
record(context, data);
|
|
107
|
-
return data;
|
|
108
|
-
}),
|
|
109
|
-
}),
|
|
110
|
-
workflow_resolve_branch: tool({
|
|
111
|
-
description: "Resolve a branch from repository spec and plan metadata",
|
|
112
|
-
args: { spec_path: tool.schema.string(), plan_path: tool.schema.string() },
|
|
113
|
-
execute: async ({ spec_path, plan_path }, context) => invoke(() => {
|
|
114
|
-
relativePath(context.directory, spec_path);
|
|
115
|
-
relativePath(context.directory, plan_path);
|
|
116
|
-
return resolveBranch({ spec_path, plan_path, workspace_root: context.directory });
|
|
117
|
-
}),
|
|
118
|
-
}),
|
|
119
|
-
workflow_sdd_context: tool({
|
|
120
|
-
description: "Resolve the SDD workspace and progress ledger",
|
|
121
|
-
args: { plan_path: tool.schema.string() },
|
|
122
|
-
execute: async ({ plan_path }, context) => invoke(() => {
|
|
123
|
-
relativePath(context.directory, plan_path);
|
|
124
|
-
const parsed = sddContext({ slug: undefined, plan_path, workspace_root: context.directory }) as Record<string, unknown>;
|
|
125
|
-
if (parsed.error) return parsed;
|
|
126
|
-
const todos = Array.isArray(parsed.todos)
|
|
127
|
-
? parsed.todos.map((todo: Record<string, unknown>) => todo.status === "in_progress"
|
|
128
|
-
? { ...todo, status: "pending" }
|
|
129
|
-
: todo)
|
|
130
|
-
: [];
|
|
131
|
-
const data = { ...parsed, todos, ...planPaths(context.directory, plan_path), sdd_dir: parsed.sdd_dir };
|
|
132
|
-
record(context, data);
|
|
133
|
-
return data;
|
|
134
|
-
}),
|
|
135
|
-
}),
|
|
136
|
-
workflow_sdd_task_brief: tool({
|
|
137
|
-
description: "Write a confirmed task brief",
|
|
138
|
-
args: {
|
|
139
|
-
confirmed: tool.schema.boolean(),
|
|
140
|
-
sdd_dir: tool.schema.string(),
|
|
141
|
-
task_id: tool.schema.number(),
|
|
142
|
-
section_text: tool.schema.string(),
|
|
143
|
-
},
|
|
144
|
-
execute: async ({ confirmed, sdd_dir, task_id, section_text }, context) => {
|
|
145
|
-
const rejected = requireConfirmed(confirmed);
|
|
146
|
-
if (rejected) return rejected;
|
|
147
|
-
return invoke(() => {
|
|
148
|
-
relativePath(context.directory, sdd_dir);
|
|
149
|
-
return sddTaskBrief({ sdd_dir, task_id, section_text, workspace_root: context.directory });
|
|
150
|
-
});
|
|
151
|
-
},
|
|
152
|
-
}),
|
|
153
|
-
workflow_sdd_review_package: tool({
|
|
154
|
-
description: "Write a confirmed task review diff",
|
|
155
|
-
args: {
|
|
156
|
-
confirmed: tool.schema.boolean(),
|
|
157
|
-
sdd_dir: tool.schema.string(),
|
|
158
|
-
base_sha: tool.schema.string(),
|
|
159
|
-
head_sha: tool.schema.string(),
|
|
160
|
-
},
|
|
161
|
-
execute: async ({ confirmed, sdd_dir, base_sha, head_sha }, context) => {
|
|
162
|
-
const rejected = requireConfirmed(confirmed);
|
|
163
|
-
if (rejected) return rejected;
|
|
164
|
-
return invoke(() => {
|
|
165
|
-
relativePath(context.directory, sdd_dir);
|
|
166
|
-
resolveGitRevision(context.directory, base_sha);
|
|
167
|
-
resolveGitRevision(context.directory, head_sha);
|
|
168
|
-
return sddReviewPackage({ sdd_dir, base_sha, head_sha, workspace_root: context.directory });
|
|
169
|
-
});
|
|
170
|
-
},
|
|
171
|
-
}),
|
|
172
|
-
workflow_sdd_append_progress: tool({
|
|
173
|
-
description: "Append one confirmed validated SDD progress line",
|
|
174
|
-
args: {
|
|
175
|
-
confirmed: tool.schema.boolean(),
|
|
176
|
-
progress_path: tool.schema.string(),
|
|
177
|
-
line: tool.schema.string(),
|
|
178
|
-
},
|
|
179
|
-
execute: async ({ confirmed, progress_path, line }, context) => {
|
|
180
|
-
const rejected = requireConfirmed(confirmed);
|
|
181
|
-
if (rejected) return rejected;
|
|
182
|
-
return invoke(() => {
|
|
183
|
-
relativePath(context.directory, progress_path);
|
|
184
|
-
return sddAppendProgress({ progress_path, line, workspace_root: context.directory });
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
|
-
}),
|
|
188
|
-
};
|
|
189
|
-
}
|
package/src/tools/templates.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { fail, ok } from "../core";
|
|
3
|
-
import { listTemplates, writeTemplate, type TemplateName } from "../core/templates";
|
|
4
|
-
|
|
5
|
-
const output = (value: unknown) => JSON.stringify(value, null, 2);
|
|
6
|
-
|
|
7
|
-
export function createTemplateTools() {
|
|
8
|
-
return {
|
|
9
|
-
workflow_template_list: tool({
|
|
10
|
-
description: "List editable templates (issue-update, greeting, headers) with their source",
|
|
11
|
-
args: {},
|
|
12
|
-
execute: async () => output(ok({ templates: listTemplates() })),
|
|
13
|
-
}),
|
|
14
|
-
workflow_template_edit: tool({
|
|
15
|
-
description: "Write an edited template to the toolkit config dir (agent-assisted)",
|
|
16
|
-
args: {
|
|
17
|
-
name: tool.schema.enum(["issue-update", "greeting", "headers"]),
|
|
18
|
-
content: tool.schema.string(),
|
|
19
|
-
confirmed: tool.schema.boolean(),
|
|
20
|
-
},
|
|
21
|
-
execute: async ({ name, content, confirmed }) => {
|
|
22
|
-
const result = writeTemplate(name as TemplateName, content, confirmed);
|
|
23
|
-
return output(result.ok ? ok(result) : fail(result.error));
|
|
24
|
-
},
|
|
25
|
-
}),
|
|
26
|
-
};
|
|
27
|
-
}
|