@brainervirus/workit-opencode 2.1.0 → 2.1.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/dist/plugin.js +1482 -2131
- package/package.json +2 -2
package/dist/plugin.js
CHANGED
|
@@ -14,8 +14,8 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// packages/workit-opencode/src/v1/server.ts
|
|
17
|
-
import { existsSync as
|
|
18
|
-
import
|
|
17
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
18
|
+
import path25 from "node:path";
|
|
19
19
|
|
|
20
20
|
// packages/workit-core/src/core.ts
|
|
21
21
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -11103,9 +11103,6 @@ function vcsCliIdentity(cwd) {
|
|
|
11103
11103
|
return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
|
|
11104
11104
|
}
|
|
11105
11105
|
}
|
|
11106
|
-
async function vcsVerifyToken() {
|
|
11107
|
-
return vcsCliIdentity();
|
|
11108
|
-
}
|
|
11109
11106
|
function mergedPrStyle(limit = 6, cwd) {
|
|
11110
11107
|
const cfg = vcsConfig("load", cwd);
|
|
11111
11108
|
if (!cfg.ok)
|
|
@@ -27836,2257 +27833,1611 @@ function tool(input) {
|
|
|
27836
27833
|
return input;
|
|
27837
27834
|
}
|
|
27838
27835
|
tool.schema = exports_external2;
|
|
27839
|
-
// packages/workit-
|
|
27840
|
-
|
|
27841
|
-
|
|
27842
|
-
|
|
27843
|
-
|
|
27844
|
-
|
|
27845
|
-
|
|
27846
|
-
|
|
27847
|
-
|
|
27836
|
+
// packages/workit-opencode/src/shared/init-apply.ts
|
|
27837
|
+
import path20 from "node:path";
|
|
27838
|
+
|
|
27839
|
+
// packages/workit-core/src/core/gitignore.ts
|
|
27840
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
27841
|
+
import path15 from "node:path";
|
|
27842
|
+
var GITIGNORE_ENTRIES = [
|
|
27843
|
+
"# workit: SDD working state (never commit)",
|
|
27844
|
+
"docs/*/sdd/",
|
|
27845
|
+
"",
|
|
27846
|
+
"# OS / editor cruft",
|
|
27847
|
+
".DS_Store",
|
|
27848
|
+
"Thumbs.db",
|
|
27849
|
+
"*.swp",
|
|
27850
|
+
".idea/",
|
|
27851
|
+
".vscode/",
|
|
27852
|
+
".env",
|
|
27853
|
+
"node_modules/",
|
|
27854
|
+
"dist/",
|
|
27855
|
+
"*.log",
|
|
27856
|
+
".cache/"
|
|
27857
|
+
];
|
|
27858
|
+
var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
27859
|
+
if (!confirmed)
|
|
27860
|
+
return { ok: false, error: "confirmed: true required" };
|
|
27861
|
+
const file = path15.join(workspaceRoot, ".gitignore");
|
|
27862
|
+
const existing = existsSync8(file) ? readFileSync7(file, "utf8") : "";
|
|
27863
|
+
const existingLines = new Set(existing.split(`
|
|
27864
|
+
`).map((l) => l.trim()).filter(Boolean));
|
|
27865
|
+
const added = [];
|
|
27866
|
+
const append = [];
|
|
27867
|
+
for (const entry of GITIGNORE_ENTRIES) {
|
|
27868
|
+
if (entry.trim() === "" || existingLines.has(entry.trim()))
|
|
27869
|
+
continue;
|
|
27870
|
+
append.push(entry);
|
|
27871
|
+
added.push(entry);
|
|
27848
27872
|
}
|
|
27849
|
-
|
|
27850
|
-
|
|
27851
|
-
|
|
27852
|
-
|
|
27853
|
-
|
|
27854
|
-
`)
|
|
27855
|
-
|
|
27856
|
-
|
|
27857
|
-
|
|
27858
|
-
|
|
27859
|
-
}
|
|
27860
|
-
}
|
|
27873
|
+
if (append.length) {
|
|
27874
|
+
const separator = existing && !existing.endsWith(`
|
|
27875
|
+
`) ? `
|
|
27876
|
+
` : "";
|
|
27877
|
+
writeFileSync3(file, existing + separator + (existing ? `
|
|
27878
|
+
` : "") + append.join(`
|
|
27879
|
+
`) + `
|
|
27880
|
+
`, "utf8");
|
|
27881
|
+
} else if (!existsSync8(file)) {
|
|
27882
|
+
writeFileSync3(file, "", "utf8");
|
|
27861
27883
|
}
|
|
27862
|
-
return
|
|
27863
|
-
}
|
|
27884
|
+
return { ok: true, path: file, added };
|
|
27885
|
+
};
|
|
27864
27886
|
|
|
27865
|
-
// packages/workit-core/src/core/
|
|
27866
|
-
|
|
27867
|
-
|
|
27868
|
-
const parts = stdout.split(/\n## /);
|
|
27869
|
-
for (const part of parts.slice(1)) {
|
|
27870
|
-
const nl = part.indexOf(`
|
|
27871
|
-
`);
|
|
27872
|
-
const label = part.slice(0, nl).trim();
|
|
27873
|
-
const body = part.slice(nl + 1);
|
|
27874
|
-
const cmdMatch = body.match(/^command: (.+)$/m);
|
|
27875
|
-
const statusMatch = body.match(/^status: (pass|fail|skipped)(?: \((.+)\))?/m);
|
|
27876
|
-
if (cmdMatch && statusMatch) {
|
|
27877
|
-
commands.push({
|
|
27878
|
-
label,
|
|
27879
|
-
command: cmdMatch[1].trim(),
|
|
27880
|
-
status: statusMatch[1],
|
|
27881
|
-
reason: statusMatch[2]?.trim()
|
|
27882
|
-
});
|
|
27883
|
-
}
|
|
27884
|
-
}
|
|
27885
|
-
const summaryMatch = stdout.match(/# Summary[\s\S]*?passed: (\d+)[\s\S]*?failed: (\d+)[\s\S]*?skipped: (\d+)/);
|
|
27886
|
-
const passed = summaryMatch ? Number(summaryMatch[1]) : 0;
|
|
27887
|
-
const failed = summaryMatch ? Number(summaryMatch[2]) : 0;
|
|
27888
|
-
const skipped = summaryMatch ? Number(summaryMatch[3]) : 0;
|
|
27889
|
-
return { passed, failed, skipped, commands };
|
|
27890
|
-
}
|
|
27887
|
+
// packages/workit-core/src/core/hygiene.ts
|
|
27888
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
|
|
27889
|
+
import path16 from "node:path";
|
|
27891
27890
|
|
|
27892
|
-
// packages/workit-core/src/core/
|
|
27893
|
-
|
|
27894
|
-
|
|
27895
|
-
|
|
27896
|
-
|
|
27891
|
+
// packages/workit-core/src/core/scripts.ts
|
|
27892
|
+
var PLUGIN_ROOT = packageRoot();
|
|
27893
|
+
var ASSET_ROOT = assetRoot();
|
|
27894
|
+
var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
27895
|
+
|
|
27896
|
+
// packages/workit-core/src/core/hygiene.ts
|
|
27897
|
+
var repoRoot = assetRoot();
|
|
27898
|
+
var templatesDir = () => path16.join(repoRoot, "templates", "hygiene");
|
|
27899
|
+
var packageJson = (root) => {
|
|
27900
|
+
if (!existsSync9(path16.join(root, "package.json")))
|
|
27901
|
+
return null;
|
|
27897
27902
|
try {
|
|
27898
|
-
|
|
27899
|
-
return true;
|
|
27903
|
+
return JSON.parse(readFileSync8(path16.join(root, "package.json"), "utf8"));
|
|
27900
27904
|
} catch {
|
|
27901
|
-
return
|
|
27905
|
+
return null;
|
|
27902
27906
|
}
|
|
27903
|
-
}
|
|
27904
|
-
var
|
|
27905
|
-
if (
|
|
27906
|
-
return false;
|
|
27907
|
-
try {
|
|
27908
|
-
resolveGitRevision(cwd, value);
|
|
27907
|
+
};
|
|
27908
|
+
var isOpenSource = (root) => {
|
|
27909
|
+
if (existsSync9(path16.join(root, "LICENSE")))
|
|
27909
27910
|
return true;
|
|
27910
|
-
|
|
27911
|
-
|
|
27912
|
-
|
|
27911
|
+
const pkg = packageJson(root);
|
|
27912
|
+
if (pkg && !pkg.private)
|
|
27913
|
+
return true;
|
|
27914
|
+
return path16.basename(root) === "workflow-toolkit";
|
|
27913
27915
|
};
|
|
27914
|
-
var
|
|
27915
|
-
|
|
27916
|
-
|
|
27917
|
-
|
|
27918
|
-
|
|
27919
|
-
|
|
27920
|
-
|
|
27921
|
-
|
|
27922
|
-
|
|
27923
|
-
|
|
27924
|
-
|
|
27925
|
-
|
|
27926
|
-
|
|
27927
|
-
};
|
|
27916
|
+
var licenseHolder = (root) => {
|
|
27917
|
+
const pkg = packageJson(root);
|
|
27918
|
+
if (pkg) {
|
|
27919
|
+
const author = pkg.author;
|
|
27920
|
+
if (typeof author === "string" && author.trim())
|
|
27921
|
+
return author.trim();
|
|
27922
|
+
if (author && typeof author === "object") {
|
|
27923
|
+
const name = author.name;
|
|
27924
|
+
if (typeof name === "string" && name.trim())
|
|
27925
|
+
return name.trim();
|
|
27926
|
+
}
|
|
27927
|
+
}
|
|
27928
|
+
return "";
|
|
27928
27929
|
};
|
|
27929
|
-
var
|
|
27930
|
-
const
|
|
27931
|
-
|
|
27930
|
+
var planHygieneFiles = (root, opts = {}) => {
|
|
27931
|
+
const openSource = opts.includeOpenSource ?? isOpenSource(root);
|
|
27932
|
+
const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"];
|
|
27933
|
+
if (openSource)
|
|
27934
|
+
files.push("LICENSE", "CONTRIBUTING.md");
|
|
27935
|
+
const planned = [];
|
|
27936
|
+
for (const file of files) {
|
|
27937
|
+
if (existsSync9(path16.join(root, file)))
|
|
27938
|
+
continue;
|
|
27939
|
+
const tpl = path16.join(templatesDir(), file);
|
|
27940
|
+
if (!existsSync9(tpl))
|
|
27941
|
+
continue;
|
|
27942
|
+
const content = readFileSync8(tpl, "utf8").replace(/<PROJECT>/g, path16.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
27943
|
+
planned.push({ path: path16.join(root, file), content });
|
|
27944
|
+
}
|
|
27945
|
+
return planned;
|
|
27932
27946
|
};
|
|
27933
|
-
var
|
|
27934
|
-
|
|
27935
|
-
|
|
27947
|
+
var ensureHygieneFiles = (root, opts) => {
|
|
27948
|
+
if (!opts.confirmed)
|
|
27949
|
+
return { ok: false, error: "confirmed: true required" };
|
|
27950
|
+
const created = [];
|
|
27951
|
+
for (const planned of planHygieneFiles(root, opts)) {
|
|
27952
|
+
writeFileSync4(planned.path, planned.content, "utf8");
|
|
27953
|
+
created.push(path16.basename(planned.path));
|
|
27954
|
+
}
|
|
27955
|
+
return { ok: true, created };
|
|
27936
27956
|
};
|
|
27937
|
-
|
|
27938
|
-
|
|
27939
|
-
|
|
27940
|
-
|
|
27941
|
-
|
|
27942
|
-
|
|
27943
|
-
|
|
27944
|
-
|
|
27945
|
-
|
|
27946
|
-
|
|
27947
|
-
|
|
27957
|
+
|
|
27958
|
+
// packages/workit-core/src/core/init.ts
|
|
27959
|
+
import fs9 from "node:fs";
|
|
27960
|
+
import path19 from "node:path";
|
|
27961
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27962
|
+
|
|
27963
|
+
// packages/workit-core/src/core/safe-write.ts
|
|
27964
|
+
import {
|
|
27965
|
+
closeSync as closeSync2,
|
|
27966
|
+
fchmodSync,
|
|
27967
|
+
fsyncSync as fsyncSync2,
|
|
27968
|
+
lstatSync as lstatSync4,
|
|
27969
|
+
mkdirSync as mkdirSync4,
|
|
27970
|
+
openSync as openSync2,
|
|
27971
|
+
renameSync as renameSync2,
|
|
27972
|
+
unlinkSync as unlinkSync3,
|
|
27973
|
+
writeFileSync as writeFileSync5
|
|
27974
|
+
} from "node:fs";
|
|
27975
|
+
function writeFileExclusive(file, content, mode) {
|
|
27976
|
+
try {
|
|
27977
|
+
writeFileSync5(file, content, { encoding: "utf8", flag: "wx", mode });
|
|
27978
|
+
return "created";
|
|
27979
|
+
} catch (err) {
|
|
27980
|
+
if (err.code === "EEXIST")
|
|
27981
|
+
return "preserved";
|
|
27982
|
+
throw err;
|
|
27948
27983
|
}
|
|
27949
|
-
|
|
27984
|
+
}
|
|
27985
|
+
|
|
27986
|
+
// packages/workit-core/src/core/setup.ts
|
|
27987
|
+
import {
|
|
27988
|
+
copyFileSync as copyFileSync2,
|
|
27989
|
+
cpSync as cpSync2,
|
|
27990
|
+
existsSync as existsSync10,
|
|
27991
|
+
lstatSync as lstatSync5,
|
|
27992
|
+
mkdirSync as mkdirSync5,
|
|
27993
|
+
mkdtempSync,
|
|
27994
|
+
readFileSync as readFileSync9,
|
|
27995
|
+
readdirSync as readdirSync6,
|
|
27996
|
+
realpathSync as realpathSync9,
|
|
27997
|
+
renameSync as renameSync3,
|
|
27998
|
+
rmSync,
|
|
27999
|
+
statSync as statSync3,
|
|
28000
|
+
writeFileSync as writeFileSync6
|
|
28001
|
+
} from "node:fs";
|
|
28002
|
+
import path17 from "node:path";
|
|
28003
|
+
import { isDeepStrictEqual } from "node:util";
|
|
28004
|
+
|
|
28005
|
+
// packages/workit-core/src/core/branch-policy.ts
|
|
28006
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
28007
|
+
var PREFIXES = {
|
|
28008
|
+
feature: "feature/*",
|
|
28009
|
+
bugfix: "bugfix/*",
|
|
28010
|
+
release: "release/*",
|
|
28011
|
+
hotfix: "hotfix/*"
|
|
27950
28012
|
};
|
|
27951
|
-
var
|
|
27952
|
-
|
|
27953
|
-
|
|
27954
|
-
|
|
27955
|
-
|
|
27956
|
-
|
|
28013
|
+
var detectBranchPolicy = (workspaceRoot) => {
|
|
28014
|
+
const branchExists = (name) => {
|
|
28015
|
+
const r = spawnSync4("git", ["branch", "--list", name], {
|
|
28016
|
+
cwd: workspaceRoot,
|
|
28017
|
+
encoding: "utf8"
|
|
28018
|
+
});
|
|
28019
|
+
return r.status === 0 && (r.stdout ?? "").trim() !== "";
|
|
28020
|
+
};
|
|
28021
|
+
const develop = branchExists("develop");
|
|
28022
|
+
const main = branchExists("main");
|
|
28023
|
+
const master = branchExists("master");
|
|
28024
|
+
const root = main ? "main" : master ? "master" : null;
|
|
28025
|
+
if (develop) {
|
|
27957
28026
|
return {
|
|
27958
|
-
|
|
27959
|
-
|
|
28027
|
+
preset: "gitflow",
|
|
28028
|
+
developBranch: "develop",
|
|
28029
|
+
integration: "merge",
|
|
28030
|
+
protected: root ? [root, "develop"] : ["develop"],
|
|
28031
|
+
allowed: [...PRESETS.gitflow.allowed],
|
|
28032
|
+
prefixes: PREFIXES
|
|
27960
28033
|
};
|
|
27961
28034
|
}
|
|
27962
|
-
if (
|
|
27963
|
-
if (branch === "unknown") {
|
|
27964
|
-
return {
|
|
27965
|
-
ok: false,
|
|
27966
|
-
error: `not in a git repository at ${repoRoot(cwd)} — open the target repository as the OpenCode session directory`
|
|
27967
|
-
};
|
|
27968
|
-
}
|
|
28035
|
+
if (root === "main") {
|
|
27969
28036
|
return {
|
|
27970
|
-
|
|
27971
|
-
|
|
28037
|
+
preset: "github-flow",
|
|
28038
|
+
developBranch: null,
|
|
28039
|
+
integration: "pr",
|
|
28040
|
+
protected: [root],
|
|
28041
|
+
allowed: ["*"],
|
|
28042
|
+
prefixes: PREFIXES
|
|
27972
28043
|
};
|
|
27973
28044
|
}
|
|
27974
|
-
|
|
27975
|
-
if (resolved.ok === false) {
|
|
27976
|
-
return { ok: false, error: String(resolved.error) };
|
|
27977
|
-
}
|
|
27978
|
-
const base = String(resolved.defaultTargetBranch ?? "");
|
|
27979
|
-
if (!base || runGit(cwd, ["check-ref-format", "--branch", base]).exitCode !== 0) {
|
|
27980
|
-
return { ok: false, error: `invalid configured PR target branch ${base}` };
|
|
27981
|
-
}
|
|
27982
|
-
for (const ref of [`origin/${base}`, base]) {
|
|
27983
|
-
if (runGit(cwd, ["rev-parse", "--verify", ref]).exitCode !== 0)
|
|
27984
|
-
continue;
|
|
27985
|
-
const mb = runGit(cwd, ["merge-base", ref, "HEAD"]);
|
|
27986
|
-
if (mb.exitCode !== 0 || !mb.stdout)
|
|
27987
|
-
continue;
|
|
28045
|
+
if (root === "master") {
|
|
27988
28046
|
return {
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
|
|
27992
|
-
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
}
|
|
28047
|
+
preset: "trunk-based",
|
|
28048
|
+
developBranch: null,
|
|
28049
|
+
integration: "pr",
|
|
28050
|
+
protected: [root],
|
|
28051
|
+
allowed: ["*"],
|
|
28052
|
+
prefixes: PREFIXES
|
|
27996
28053
|
};
|
|
27997
28054
|
}
|
|
27998
28055
|
return {
|
|
27999
|
-
|
|
28000
|
-
|
|
28056
|
+
preset: "gitflow",
|
|
28057
|
+
developBranch: null,
|
|
28058
|
+
integration: "merge",
|
|
28059
|
+
protected: [],
|
|
28060
|
+
allowed: [],
|
|
28061
|
+
prefixes: PREFIXES
|
|
28001
28062
|
};
|
|
28002
|
-
}
|
|
28003
|
-
var printSection = (title) => `
|
|
28004
|
-
## ${title}
|
|
28005
|
-
|
|
28006
|
-
`;
|
|
28007
|
-
var probeCaseInsensitive = (dir, wanted) => {
|
|
28008
|
-
try {
|
|
28009
|
-
for (const entry of readdirSync6(dir)) {
|
|
28010
|
-
if (entry.toLowerCase() === wanted.toLowerCase())
|
|
28011
|
-
return entry;
|
|
28012
|
-
}
|
|
28013
|
-
} catch {}
|
|
28014
|
-
return null;
|
|
28015
28063
|
};
|
|
28016
|
-
var findPrTemplate = (cwd) => {
|
|
28017
|
-
for (const rel of [
|
|
28018
|
-
".gitlab/merge_request_templates/Default.md",
|
|
28019
|
-
".gitlab/merge_request_templates/default.md",
|
|
28020
|
-
".gitlab/merge_request_templates/merge_request_template.md",
|
|
28021
|
-
".github/PULL_REQUEST_TEMPLATE.md",
|
|
28022
|
-
".github/pull_request_template.md",
|
|
28023
|
-
".github/PULL_REQUEST_TEMPLATE/pull_request_template.md",
|
|
28024
|
-
"docs/PULL_REQUEST_TEMPLATE.md",
|
|
28025
|
-
"PULL_REQUEST_TEMPLATE.md"
|
|
28026
|
-
]) {
|
|
28027
|
-
const parts = rel.split("/");
|
|
28028
|
-
const base = parts[parts.length - 1];
|
|
28029
|
-
const dir = parts.length > 1 ? path15.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
28030
|
-
const actual = probeCaseInsensitive(dir, base);
|
|
28031
|
-
if (actual)
|
|
28032
|
-
return [...parts.slice(0, -1), actual].join("/");
|
|
28033
|
-
}
|
|
28034
|
-
return null;
|
|
28035
|
-
};
|
|
28036
|
-
var fallbackPrTemplate = () => `## Summary
|
|
28037
|
-
-
|
|
28038
28064
|
|
|
28039
|
-
|
|
28040
|
-
|
|
28041
|
-
var
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
|
|
28046
|
-
};
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
28051
|
-
|
|
28052
|
-
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
|
|
28056
|
-
|
|
28057
|
-
|
|
28058
|
-
|
|
28059
|
-
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
|
|
28065
|
+
// packages/workit-core/src/core/registration.ts
|
|
28066
|
+
var CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest";
|
|
28067
|
+
var CURSOR_HOOK_RUN_COMMAND = `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-hook`;
|
|
28068
|
+
function cursorHooksEntry(_packageDir) {
|
|
28069
|
+
return {
|
|
28070
|
+
command: `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-session-start`,
|
|
28071
|
+
args: []
|
|
28072
|
+
};
|
|
28073
|
+
}
|
|
28074
|
+
|
|
28075
|
+
// packages/workit-core/src/core/doctor.ts
|
|
28076
|
+
var canonicalCursorHook = cursorHooksEntry("").command;
|
|
28077
|
+
var INSTALLER_REQUIRED = new Set([
|
|
28078
|
+
"runtime",
|
|
28079
|
+
"assets",
|
|
28080
|
+
"launcher",
|
|
28081
|
+
"utility",
|
|
28082
|
+
"stale_pin",
|
|
28083
|
+
"stale_install",
|
|
28084
|
+
"duplicate_registration",
|
|
28085
|
+
"malformed_config"
|
|
28086
|
+
]);
|
|
28087
|
+
// packages/workit-core/src/core/setup.ts
|
|
28088
|
+
var YT_OVERRIDES = [
|
|
28089
|
+
{ envKey: "WORKFLOW_YT_BASE_URL", affects: "youtrack.json baseUrl" },
|
|
28090
|
+
{ envKey: "WORKFLOW_YT_TOKEN_FILE", affects: "youtrack.json tokenFile" },
|
|
28091
|
+
{ envKey: "WORKFLOW_YT_TIMEZONE", affects: "youtrack.json timezone" },
|
|
28092
|
+
{ envKey: "WORKFLOW_YT_MENTION", affects: "youtrack.json defaultMention" },
|
|
28093
|
+
{ envKey: "WORKFLOW_YT_MEETING_ISSUE", affects: "youtrack.json meetingIssue" },
|
|
28094
|
+
{ envKey: "WORKFLOW_YT_WEB_MEETING_ISSUE", affects: "youtrack.json web meeting issue" }
|
|
28095
|
+
];
|
|
28096
|
+
var VCS_OVERRIDES = [
|
|
28097
|
+
{ envKey: "WORKFLOW_VCS_PROVIDER", affects: "vcs.json provider" },
|
|
28098
|
+
{ envKey: "WORKFLOW_VCS_TARGET_BRANCH", affects: "vcs.json defaultTargetBranch" },
|
|
28099
|
+
{ envKey: "WORKFLOW_GITLAB_HOST", affects: "vcs.json gitlab.host" },
|
|
28100
|
+
{ envKey: "WORKFLOW_GITLAB_API_URL", affects: "vcs.json gitlab.apiUrl" },
|
|
28101
|
+
{ envKey: "WORKFLOW_GITHUB_HOST", affects: "vcs.json github.host" }
|
|
28102
|
+
];
|
|
28103
|
+
var SETUP_OVERRIDES = [...YT_OVERRIDES, ...VCS_OVERRIDES];
|
|
28104
|
+
var readFileSafe = (p) => {
|
|
28063
28105
|
try {
|
|
28064
|
-
|
|
28065
|
-
return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
|
|
28106
|
+
return readFileSync9(p, "utf8");
|
|
28066
28107
|
} catch {
|
|
28067
|
-
return
|
|
28108
|
+
return null;
|
|
28068
28109
|
}
|
|
28069
28110
|
};
|
|
28070
|
-
|
|
28071
|
-
const
|
|
28072
|
-
const
|
|
28073
|
-
const
|
|
28074
|
-
|
|
28075
|
-
|
|
28076
|
-
|
|
28111
|
+
function applyWorkspaceBranchPolicy(opts) {
|
|
28112
|
+
const { workspace_root, env = process.env } = opts;
|
|
28113
|
+
const dir = path17.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
28114
|
+
const {
|
|
28115
|
+
status,
|
|
28116
|
+
path: wsPath,
|
|
28117
|
+
entries,
|
|
28118
|
+
document,
|
|
28119
|
+
revision,
|
|
28120
|
+
error: workspacesError
|
|
28121
|
+
} = readWorkspacesResult(dir);
|
|
28122
|
+
if (status === "malformed" || status === "invalid")
|
|
28123
|
+
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
28124
|
+
const detection = detectBranchPolicy(workspace_root);
|
|
28125
|
+
const name = String(env.WORKFLOW_BP_NAME ?? path17.basename(workspace_root));
|
|
28126
|
+
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
28127
|
+
const policy = {
|
|
28128
|
+
preset: detection.preset,
|
|
28129
|
+
developBranch: env.WORKFLOW_BP_DEVELOP ?? detection.developBranch ?? undefined,
|
|
28130
|
+
prefixes: detection.prefixes,
|
|
28131
|
+
allowed: detection.allowed,
|
|
28132
|
+
protected: detection.protected,
|
|
28133
|
+
integration
|
|
28134
|
+
};
|
|
28135
|
+
const glob = `${workspace_root.replace(/[\\/]+$/, "")}/**`;
|
|
28136
|
+
if (!validateWorkspaceGlob(glob).ok)
|
|
28137
|
+
return { ok: false, error: `invalid workspace glob: ${glob}` };
|
|
28138
|
+
let existing;
|
|
28139
|
+
try {
|
|
28140
|
+
existing = resolveWorkspaceFrom(workspace_root, dir);
|
|
28141
|
+
} catch (error) {
|
|
28142
|
+
const selectedName = env.WORKFLOW_BP_NAME?.trim();
|
|
28143
|
+
if (!selectedName)
|
|
28144
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
28077
28145
|
try {
|
|
28078
|
-
|
|
28079
|
-
} catch {
|
|
28080
|
-
return
|
|
28146
|
+
existing = resolveWorkspaceFrom(workspace_root, dir, selectedName);
|
|
28147
|
+
} catch (selectionError) {
|
|
28148
|
+
return {
|
|
28149
|
+
ok: false,
|
|
28150
|
+
error: selectionError instanceof Error ? selectionError.message : String(selectionError)
|
|
28151
|
+
};
|
|
28081
28152
|
}
|
|
28082
|
-
|
|
28083
|
-
|
|
28084
|
-
|
|
28085
|
-
|
|
28086
|
-
|
|
28087
|
-
|
|
28088
|
-
|
|
28089
|
-
|
|
28153
|
+
}
|
|
28154
|
+
if (existing?.branchPolicy && isDeepStrictEqual(existing.branchPolicy, policy)) {
|
|
28155
|
+
return {
|
|
28156
|
+
ok: true,
|
|
28157
|
+
status: "already-configured",
|
|
28158
|
+
workspace: existing,
|
|
28159
|
+
policy,
|
|
28160
|
+
config_path: wsPath
|
|
28161
|
+
};
|
|
28162
|
+
}
|
|
28163
|
+
const next = existing ? entries.map((w) => w.name === existing?.name ? { ...w, branchPolicy: policy } : w) : [...entries, { name, glob, branchPolicy: policy }];
|
|
28164
|
+
if (!existing && entries.some((entry) => entry.name === name))
|
|
28165
|
+
return { ok: false, error: `workspace name ${JSON.stringify(name)} is already configured` };
|
|
28166
|
+
const proposed = validateWorkspacesDocument({ ...document, workspaces: next }, wsPath);
|
|
28167
|
+
if (proposed.status !== "valid")
|
|
28168
|
+
return { ok: false, error: proposed.error };
|
|
28169
|
+
if (workspacesRevision(readFileSafe(wsPath)) !== revision)
|
|
28170
|
+
return {
|
|
28171
|
+
ok: false,
|
|
28172
|
+
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
28173
|
+
};
|
|
28174
|
+
mkdirSync5(path17.dirname(wsPath), { recursive: true });
|
|
28175
|
+
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
28176
|
+
try {
|
|
28177
|
+
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
28178
|
+
`, "utf8");
|
|
28179
|
+
if (workspacesRevision(readFileSafe(wsPath)) !== revision) {
|
|
28180
|
+
rmSync(tmp, { force: true });
|
|
28181
|
+
return {
|
|
28182
|
+
ok: false,
|
|
28183
|
+
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
28184
|
+
};
|
|
28090
28185
|
}
|
|
28186
|
+
renameSync3(tmp, wsPath);
|
|
28187
|
+
} catch (error) {
|
|
28188
|
+
try {
|
|
28189
|
+
rmSync(tmp, { force: true });
|
|
28190
|
+
} catch {}
|
|
28191
|
+
return {
|
|
28192
|
+
ok: false,
|
|
28193
|
+
error: `failed to write ${wsPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
28194
|
+
};
|
|
28195
|
+
}
|
|
28196
|
+
return {
|
|
28197
|
+
ok: true,
|
|
28198
|
+
status: existing ? "updated" : "configured",
|
|
28199
|
+
workspace: existing ? { ...existing, branchPolicy: policy } : { name, glob, branchPolicy: policy },
|
|
28200
|
+
policy,
|
|
28201
|
+
config_path: wsPath
|
|
28091
28202
|
};
|
|
28092
|
-
|
|
28093
|
-
|
|
28094
|
-
|
|
28095
|
-
|
|
28096
|
-
|
|
28097
|
-
|
|
28098
|
-
|
|
28099
|
-
|
|
28100
|
-
|
|
28101
|
-
|
|
28203
|
+
}
|
|
28204
|
+
|
|
28205
|
+
// packages/workit-core/src/core/youtrack.ts
|
|
28206
|
+
import fs8 from "node:fs";
|
|
28207
|
+
import path18 from "node:path";
|
|
28208
|
+
|
|
28209
|
+
// packages/workit-core/src/core/templates.ts
|
|
28210
|
+
var repoRoot2 = assetRoot();
|
|
28211
|
+
|
|
28212
|
+
// packages/workit-core/src/core/youtrack.ts
|
|
28213
|
+
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
28214
|
+
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
28215
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
|
|
28216
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
|
|
28217
|
+
var youTrackTokenModeOk = (p) => {
|
|
28218
|
+
if (process.platform === "win32")
|
|
28219
|
+
return true;
|
|
28220
|
+
const mode = fs8.statSync(p).mode & 511;
|
|
28221
|
+
return mode === 384;
|
|
28102
28222
|
};
|
|
28103
|
-
function
|
|
28104
|
-
const
|
|
28105
|
-
if (
|
|
28106
|
-
return
|
|
28107
|
-
let stdout = "";
|
|
28108
|
-
const autoRange = range === undefined;
|
|
28109
|
-
let ctx = null;
|
|
28110
|
-
if (autoRange) {
|
|
28111
|
-
ctx = resolvePrBranchContext(cwd);
|
|
28112
|
-
if (!ctx.ok)
|
|
28113
|
-
return { stdout: "", stderr: `ERROR: ${ctx.error}
|
|
28114
|
-
`, exitCode: 1, cwd };
|
|
28115
|
-
range = ctx.value.range;
|
|
28116
|
-
}
|
|
28117
|
-
stdout += printSection("Repository");
|
|
28118
|
-
stdout += `root: ${cwd}
|
|
28119
|
-
`;
|
|
28120
|
-
stdout += `branch: ${currentBranch(cwd)}
|
|
28121
|
-
`;
|
|
28122
|
-
stdout += `range: ${range}
|
|
28123
|
-
`;
|
|
28124
|
-
if (autoRange && ctx?.ok) {
|
|
28125
|
-
stdout += `base_ref: ${ctx.value.baseRef}
|
|
28126
|
-
`;
|
|
28127
|
-
stdout += `merge_base: ${ctx.value.mergeBase}
|
|
28128
|
-
`;
|
|
28129
|
-
stdout += `diff_range: ${ctx.value.diffRange}
|
|
28130
|
-
`;
|
|
28131
|
-
stdout += `range_mode: branch-exclusive
|
|
28132
|
-
`;
|
|
28133
|
-
if (process.env.PR_SYNC_NOTES)
|
|
28134
|
-
stdout += `git_sync: ${process.env.PR_SYNC_NOTES}
|
|
28135
|
-
`;
|
|
28136
|
-
}
|
|
28137
|
-
const diffRange = autoRange && ctx?.ok ? ctx.value.diffRange : range ?? "";
|
|
28138
|
-
stdout += printSection("Working Tree");
|
|
28139
|
-
const status = runGit(cwd, ["status", "--short"]);
|
|
28140
|
-
stdout += status.stdout + (status.stderr ? `
|
|
28141
|
-
${status.stderr}` : "");
|
|
28142
|
-
stdout += `
|
|
28143
|
-
`;
|
|
28144
|
-
stdout += printSection("Commits");
|
|
28145
|
-
stdout += commitLogForRange(cwd, range ?? "");
|
|
28146
|
-
stdout += `
|
|
28147
|
-
`;
|
|
28148
|
-
stdout += printSection("Diff Stat");
|
|
28149
|
-
stdout += diffStatForRange(cwd, diffRange);
|
|
28150
|
-
stdout += `
|
|
28151
|
-
`;
|
|
28152
|
-
stdout += printSection("Changed Files");
|
|
28153
|
-
stdout += changedFilesForRange(cwd, diffRange);
|
|
28154
|
-
stdout += `
|
|
28155
|
-
`;
|
|
28156
|
-
stdout += printSection("PR Template");
|
|
28157
|
-
const template = findPrTemplate(cwd);
|
|
28158
|
-
if (template) {
|
|
28159
|
-
stdout += `template_path: ${template}
|
|
28160
|
-
|
|
28161
|
-
`;
|
|
28162
|
-
stdout += readTrimmed(path15.join(cwd, template), 220);
|
|
28163
|
-
} else {
|
|
28164
|
-
stdout += `template_path: none
|
|
28165
|
-
|
|
28166
|
-
`;
|
|
28167
|
-
stdout += fallbackPrTemplate();
|
|
28168
|
-
}
|
|
28169
|
-
stdout += `
|
|
28170
|
-
`;
|
|
28171
|
-
stdout += printSection("Recent Validation Signals");
|
|
28172
|
-
if (existsSync8(path15.join(cwd, "package.json"))) {
|
|
28173
|
-
stdout += `package.json detected. Common scripts:
|
|
28174
|
-
`;
|
|
28175
|
-
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
28176
|
-
if (found.length)
|
|
28177
|
-
stdout += found.join(`
|
|
28178
|
-
`) + `
|
|
28179
|
-
`;
|
|
28180
|
-
}
|
|
28181
|
-
if (existsSync8(path15.join(cwd, "Cargo.toml")) || existsSync8(path15.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
28182
|
-
stdout += `Rust project detected.
|
|
28183
|
-
`;
|
|
28223
|
+
function readYouTrackConfig(required) {
|
|
28224
|
+
const cfgPath = youTrackConfigPath();
|
|
28225
|
+
if (!fs8.existsSync(cfgPath)) {
|
|
28226
|
+
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
28184
28227
|
}
|
|
28185
|
-
|
|
28186
|
-
|
|
28187
|
-
|
|
28188
|
-
|
|
28189
|
-
|
|
28190
|
-
stdout += `provider: ${String(vc.provider ?? "unconfigured")}
|
|
28191
|
-
`;
|
|
28192
|
-
} else {
|
|
28193
|
-
stdout += `vcs: unreadable (malformed) — ${String(vc.error ?? "cannot read vcs.json")}
|
|
28194
|
-
`;
|
|
28228
|
+
let parsed;
|
|
28229
|
+
try {
|
|
28230
|
+
parsed = JSON.parse(fs8.readFileSync(cfgPath, "utf8"));
|
|
28231
|
+
} catch {
|
|
28232
|
+
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
28195
28233
|
}
|
|
28196
|
-
if (!
|
|
28197
|
-
|
|
28198
|
-
`;
|
|
28234
|
+
if (!isConfigObject(parsed)) {
|
|
28235
|
+
return { error: `${cfgPath} is not a JSON object`, path: cfgPath };
|
|
28199
28236
|
}
|
|
28200
|
-
|
|
28201
|
-
const style = mergedPrStyle(6, cwd);
|
|
28202
|
-
stdout += JSON.stringify(style, null, 2) + `
|
|
28203
|
-
`;
|
|
28204
|
-
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
28237
|
+
return { config: parsed, path: cfgPath };
|
|
28205
28238
|
}
|
|
28206
|
-
var
|
|
28207
|
-
|
|
28208
|
-
|
|
28209
|
-
|
|
28210
|
-
|
|
28211
|
-
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
|
|
28215
|
-
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
28216
|
-
return invalidRange(cwd);
|
|
28217
|
-
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
28218
|
-
if (!isResolvableContextRange(cwd, resolvedRange))
|
|
28219
|
-
return invalidRange(cwd);
|
|
28220
|
-
let stdout = "";
|
|
28221
|
-
stdout += printSection("Repository");
|
|
28222
|
-
stdout += `root: ${cwd}
|
|
28223
|
-
`;
|
|
28224
|
-
stdout += `branch: ${currentBranch(cwd)}
|
|
28225
|
-
`;
|
|
28226
|
-
stdout += `range: ${resolvedRange}
|
|
28227
|
-
`;
|
|
28228
|
-
stdout += printSection("Keep a Changelog Rules");
|
|
28229
|
-
stdout += CHANGELOG_RULES + `
|
|
28230
|
-
`;
|
|
28231
|
-
stdout += printSection("Existing CHANGELOG.md");
|
|
28232
|
-
const changelogPath = path15.join(cwd, "CHANGELOG.md");
|
|
28233
|
-
if (existsSync8(changelogPath)) {
|
|
28234
|
-
stdout += readTrimmed(changelogPath, 260) + `
|
|
28235
|
-
`;
|
|
28236
|
-
} else {
|
|
28237
|
-
stdout += `CHANGELOG.md not found.
|
|
28238
|
-
`;
|
|
28239
|
+
var readYouTrackContextConfig = () => {
|
|
28240
|
+
const cfgPath = youTrackReadOnlyConfigPath();
|
|
28241
|
+
if (!fs8.existsSync(cfgPath))
|
|
28242
|
+
return { data: {} };
|
|
28243
|
+
let parsed;
|
|
28244
|
+
try {
|
|
28245
|
+
parsed = JSON.parse(fs8.readFileSync(cfgPath, "utf8"));
|
|
28246
|
+
} catch {
|
|
28247
|
+
return { error: "YouTrack configuration is unavailable" };
|
|
28239
28248
|
}
|
|
28240
|
-
|
|
28241
|
-
|
|
28242
|
-
|
|
28243
|
-
|
|
28244
|
-
|
|
28245
|
-
|
|
28246
|
-
|
|
28247
|
-
|
|
28248
|
-
|
|
28249
|
-
|
|
28249
|
+
if (!isConfigObject(parsed))
|
|
28250
|
+
return { error: "YouTrack configuration is unavailable" };
|
|
28251
|
+
return { data: readOnlyYouTrackConfig(parsed) };
|
|
28252
|
+
};
|
|
28253
|
+
function tzParts(date, tz) {
|
|
28254
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
28255
|
+
timeZone: tz,
|
|
28256
|
+
year: "numeric",
|
|
28257
|
+
month: "2-digit",
|
|
28258
|
+
day: "2-digit",
|
|
28259
|
+
hour: "2-digit",
|
|
28260
|
+
minute: "2-digit",
|
|
28261
|
+
hourCycle: "h23"
|
|
28262
|
+
}).formatToParts(date);
|
|
28263
|
+
const map = Object.fromEntries(parts.map((p) => [p.type, p.value]));
|
|
28264
|
+
return { y: map.year, m: map.month, d: map.day, hour: map.hour, minute: map.minute };
|
|
28250
28265
|
}
|
|
28251
|
-
function
|
|
28252
|
-
const
|
|
28253
|
-
|
|
28254
|
-
|
|
28255
|
-
|
|
28256
|
-
|
|
28257
|
-
return
|
|
28258
|
-
|
|
28259
|
-
|
|
28260
|
-
|
|
28261
|
-
|
|
28262
|
-
stdout += `branch: ${currentBranch(cwd)}
|
|
28263
|
-
`;
|
|
28264
|
-
stdout += `range: ${resolvedRange}
|
|
28265
|
-
`;
|
|
28266
|
-
stdout += printSection("Changed Files");
|
|
28267
|
-
stdout += changedFilesForRange(cwd, resolvedRange) + `
|
|
28268
|
-
`;
|
|
28269
|
-
stdout += printSection("Documentation Files");
|
|
28270
|
-
stdout += documentationFiles(cwd).join(`
|
|
28271
|
-
`) + `
|
|
28272
|
-
`;
|
|
28273
|
-
stdout += printSection("README Preview");
|
|
28274
|
-
const readme = path15.join(cwd, "README.md");
|
|
28275
|
-
if (existsSync8(readme)) {
|
|
28276
|
-
stdout += readTrimmed(readme, 220) + `
|
|
28277
|
-
`;
|
|
28278
|
-
} else {
|
|
28279
|
-
stdout += `README.md not found.
|
|
28280
|
-
`;
|
|
28266
|
+
function youTrackGreeting(configOverride) {
|
|
28267
|
+
const cfgPath = configOverride ?? youTrackConfigPath();
|
|
28268
|
+
let parsed;
|
|
28269
|
+
try {
|
|
28270
|
+
parsed = JSON.parse(fs8.readFileSync(cfgPath, "utf8"));
|
|
28271
|
+
} catch {
|
|
28272
|
+
return {
|
|
28273
|
+
stdout: "",
|
|
28274
|
+
exitCode: 1,
|
|
28275
|
+
stderr: fs8.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
|
|
28276
|
+
};
|
|
28281
28277
|
}
|
|
28282
|
-
|
|
28283
|
-
|
|
28284
|
-
if (existsSync8(pkgPath)) {
|
|
28285
|
-
try {
|
|
28286
|
-
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
28287
|
-
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
28288
|
-
`;
|
|
28289
|
-
} catch {}
|
|
28290
|
-
} else {
|
|
28291
|
-
stdout += `package.json not found.
|
|
28292
|
-
`;
|
|
28278
|
+
if (!isConfigObject(parsed)) {
|
|
28279
|
+
return { stdout: "", exitCode: 1, stderr: `${cfgPath} is not a JSON object` };
|
|
28293
28280
|
}
|
|
28294
|
-
|
|
28295
|
-
|
|
28296
|
-
|
|
28297
|
-
|
|
28298
|
-
|
|
28299
|
-
|
|
28300
|
-
|
|
28281
|
+
const config = parsed;
|
|
28282
|
+
try {
|
|
28283
|
+
const tz = String(config.timezone ?? "America/Santiago");
|
|
28284
|
+
const now = new Date;
|
|
28285
|
+
const { y, m, d, hour, minute } = tzParts(now, tz);
|
|
28286
|
+
const cutoff = String(config.greetingCutoff ?? "12:00").split(":");
|
|
28287
|
+
const cutoffHour = Number(cutoff[0]);
|
|
28288
|
+
const cutoffMinute = Number(cutoff[1] ?? 0);
|
|
28289
|
+
const greetings = config.greetings ?? {};
|
|
28290
|
+
const isMorning = Number(hour) < cutoffHour || Number(hour) === cutoffHour && Number(minute) < cutoffMinute;
|
|
28291
|
+
const greeting = isMorning ? greetings.morning ?? "buenos días" : greetings.afternoon ?? "buenas tardes";
|
|
28292
|
+
const mention = String(config.defaultMention ?? "Alejandra.Flores");
|
|
28293
|
+
return { stdout: `@${mention} Hola, ${greeting}.
|
|
28294
|
+
`, exitCode: 0, stderr: "" };
|
|
28295
|
+
} catch (err) {
|
|
28296
|
+
return {
|
|
28297
|
+
stdout: "",
|
|
28298
|
+
exitCode: 1,
|
|
28299
|
+
stderr: err instanceof Error ? err.message : "greeting failed"
|
|
28300
|
+
};
|
|
28301
28301
|
}
|
|
28302
|
-
if (!isResolvableContextRange(cwd, rangeOrTag))
|
|
28303
|
-
return invalidRange(cwd);
|
|
28304
|
-
const resolvedRange = rangeArgOrDefault(rangeOrTag, cwd);
|
|
28305
|
-
if (!isResolvableContextRange(cwd, resolvedRange))
|
|
28306
|
-
return invalidRange(cwd);
|
|
28307
|
-
let stdout = "";
|
|
28308
|
-
stdout += printSection("Repository");
|
|
28309
|
-
stdout += `root: ${cwd}
|
|
28310
|
-
`;
|
|
28311
|
-
stdout += `branch: ${currentBranch(cwd)}
|
|
28312
|
-
`;
|
|
28313
|
-
stdout += `requested: ${rangeOrTag}
|
|
28314
|
-
`;
|
|
28315
|
-
stdout += `range: ${resolvedRange}
|
|
28316
|
-
`;
|
|
28317
|
-
stdout += printSection("Tags");
|
|
28318
|
-
const tags = runGit(cwd, ["tag", "--sort=-creatordate"]);
|
|
28319
|
-
if (tags.exitCode === 0)
|
|
28320
|
-
stdout += tags.stdout.split(`
|
|
28321
|
-
`).slice(0, 20).join(`
|
|
28322
|
-
`) + `
|
|
28323
|
-
`;
|
|
28324
|
-
stdout += printSection("Commits");
|
|
28325
|
-
stdout += commitLogForRange(cwd, resolvedRange) + `
|
|
28326
|
-
`;
|
|
28327
|
-
stdout += printSection("Diff Stat");
|
|
28328
|
-
stdout += diffStatForRange(cwd, resolvedRange) + `
|
|
28329
|
-
`;
|
|
28330
|
-
stdout += printSection("Changed Files");
|
|
28331
|
-
stdout += changedFilesForRange(cwd, resolvedRange) + `
|
|
28332
|
-
`;
|
|
28333
|
-
stdout += printSection("Existing Release Files");
|
|
28334
|
-
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
28335
|
-
if (existsSync8(path15.join(cwd, rel)))
|
|
28336
|
-
stdout += rel + `
|
|
28337
|
-
`;
|
|
28338
|
-
}
|
|
28339
|
-
stdout += printSection("Release Notes Draft");
|
|
28340
|
-
stdout += `## Release notes (${resolvedRange})
|
|
28341
|
-
|
|
28342
|
-
`;
|
|
28343
|
-
const commits = commitLogForRange(cwd, resolvedRange).split(`
|
|
28344
|
-
`).map((line) => line.trim()).filter(Boolean);
|
|
28345
|
-
stdout += commits.length ? `${commits.map((line) => `- ${line}`).join(`
|
|
28346
|
-
`)}
|
|
28347
|
-
` : `- No commits in range.
|
|
28348
|
-
`;
|
|
28349
|
-
stdout += `
|
|
28350
|
-
### Changed files
|
|
28351
|
-
|
|
28352
|
-
`;
|
|
28353
|
-
const files = changedFilesForRange(cwd, resolvedRange).split(`
|
|
28354
|
-
`).map((line) => line.trim()).filter(Boolean);
|
|
28355
|
-
stdout += files.length ? `${files.map((line) => `- ${line}`).join(`
|
|
28356
|
-
`)}
|
|
28357
|
-
` : `- None.
|
|
28358
|
-
`;
|
|
28359
|
-
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
28360
28302
|
}
|
|
28361
|
-
|
|
28362
|
-
|
|
28363
|
-
|
|
28364
|
-
|
|
28365
|
-
|
|
28366
|
-
|
|
28367
|
-
|
|
28368
|
-
|
|
28369
|
-
|
|
28370
|
-
|
|
28371
|
-
|
|
28372
|
-
|
|
28373
|
-
|
|
28374
|
-
|
|
28375
|
-
|
|
28376
|
-
|
|
28377
|
-
|
|
28378
|
-
|
|
28379
|
-
|
|
28380
|
-
|
|
28381
|
-
|
|
28382
|
-
|
|
28383
|
-
}
|
|
28303
|
+
function youTrackParseDuration(text) {
|
|
28304
|
+
const lower = String(text).toLowerCase().trim();
|
|
28305
|
+
let total = 0;
|
|
28306
|
+
for (const match of lower.matchAll(/(\d+)\s*h/g))
|
|
28307
|
+
total += Number(match[1]) * 60;
|
|
28308
|
+
for (const match of lower.matchAll(/(\d+)\s*m/g))
|
|
28309
|
+
total += Number(match[1]);
|
|
28310
|
+
if (total === 0 && /^\d+$/.test(lower))
|
|
28311
|
+
total = Number(lower);
|
|
28312
|
+
if (total <= 0)
|
|
28313
|
+
return { error: "could not parse duration" };
|
|
28314
|
+
return { data: { minutes: total, text: String(text).trim() } };
|
|
28315
|
+
}
|
|
28316
|
+
var youTrackToken = () => {
|
|
28317
|
+
const loaded = readYouTrackConfig(true);
|
|
28318
|
+
if ("error" in loaded)
|
|
28319
|
+
return loaded;
|
|
28320
|
+
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
28321
|
+
const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
|
|
28322
|
+
if (!tokenPath || !fs8.existsSync(tokenPath))
|
|
28323
|
+
return { error: "missing youtrack.token" };
|
|
28324
|
+
if (!youTrackTokenModeOk(tokenPath))
|
|
28325
|
+
return { error: "youtrack.token mode must be 0600" };
|
|
28326
|
+
const token = fs8.readFileSync(tokenPath, "utf8").trim();
|
|
28327
|
+
if (!token)
|
|
28328
|
+
return { error: "empty token file" };
|
|
28329
|
+
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
28330
|
+
return {
|
|
28331
|
+
error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
|
|
28332
|
+
};
|
|
28384
28333
|
}
|
|
28385
|
-
|
|
28334
|
+
const base = String(loaded.config.baseUrl ?? "").replace(/\/+$/, "");
|
|
28335
|
+
if (!base)
|
|
28336
|
+
return { error: "baseUrl missing in config" };
|
|
28337
|
+
return { token, base };
|
|
28386
28338
|
};
|
|
28387
|
-
|
|
28388
|
-
const
|
|
28389
|
-
|
|
28390
|
-
|
|
28339
|
+
async function youTrackRequest(url, init) {
|
|
28340
|
+
const headers = {
|
|
28341
|
+
Authorization: `Bearer ${init.token}`,
|
|
28342
|
+
Accept: "application/json"
|
|
28343
|
+
};
|
|
28344
|
+
let body;
|
|
28345
|
+
if (init.body !== undefined) {
|
|
28346
|
+
headers["Content-Type"] = "application/json";
|
|
28347
|
+
body = JSON.stringify(init.body);
|
|
28348
|
+
}
|
|
28391
28349
|
try {
|
|
28392
|
-
const
|
|
28393
|
-
|
|
28394
|
-
|
|
28395
|
-
|
|
28350
|
+
const res = await fetch(url, { method: init.method, headers, body });
|
|
28351
|
+
const text = await res.text();
|
|
28352
|
+
if (!res.ok) {
|
|
28353
|
+
return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
|
|
28354
|
+
}
|
|
28355
|
+
return { status: 0, stdout: text, stderr: "" };
|
|
28356
|
+
} catch (err) {
|
|
28357
|
+
return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
|
|
28396
28358
|
}
|
|
28397
|
-
}
|
|
28398
|
-
|
|
28399
|
-
|
|
28400
|
-
|
|
28401
|
-
|
|
28402
|
-
|
|
28403
|
-
try {
|
|
28404
|
-
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
28405
|
-
if (typeof pkg.packageManager === "string" && pkg.packageManager.startsWith("pnpm")) {
|
|
28406
|
-
return "pnpm";
|
|
28407
|
-
}
|
|
28408
|
-
} catch {}
|
|
28359
|
+
}
|
|
28360
|
+
function youTrackTokenCreateUrl() {
|
|
28361
|
+
const tokenName = process.env.WORKFLOW_YT_TOKEN_NAME ?? "workit";
|
|
28362
|
+
const loaded = readYouTrackConfig(false);
|
|
28363
|
+
if ("error" in loaded) {
|
|
28364
|
+
return { data: { error: loaded.error } };
|
|
28409
28365
|
}
|
|
28410
|
-
|
|
28411
|
-
|
|
28412
|
-
|
|
28413
|
-
|
|
28414
|
-
|
|
28415
|
-
const
|
|
28416
|
-
|
|
28417
|
-
|
|
28418
|
-
|
|
28419
|
-
const
|
|
28420
|
-
|
|
28421
|
-
|
|
28422
|
-
|
|
28423
|
-
|
|
28424
|
-
|
|
28425
|
-
|
|
28426
|
-
|
|
28427
|
-
|
|
28428
|
-
|
|
28429
|
-
|
|
28430
|
-
|
|
28431
|
-
`
|
|
28432
|
-
|
|
28433
|
-
|
|
28434
|
-
|
|
28435
|
-
|
|
28436
|
-
const so = (r.stdout ?? "").trimEnd();
|
|
28437
|
-
if (so)
|
|
28438
|
-
lines.push(so + `
|
|
28439
|
-
`);
|
|
28440
|
-
const se = (r.stderr ?? "").trimEnd();
|
|
28441
|
-
if (se)
|
|
28442
|
-
stderrParts.push(se);
|
|
28443
|
-
if (r.status === 0) {
|
|
28444
|
-
lines.push(`
|
|
28445
|
-
status: pass
|
|
28446
|
-
`);
|
|
28447
|
-
passed += 1;
|
|
28448
|
-
} else {
|
|
28449
|
-
lines.push(`
|
|
28450
|
-
status: fail (exit ${r.status})
|
|
28451
|
-
`);
|
|
28452
|
-
failed += 1;
|
|
28366
|
+
const config = loaded.config;
|
|
28367
|
+
const defaults = config.tokenDefaults ?? {};
|
|
28368
|
+
const name = String(defaults.name ?? tokenName);
|
|
28369
|
+
const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
|
|
28370
|
+
const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
|
|
28371
|
+
const base = String(config.baseUrl ?? "https://enghouseamg.youtrack.cloud").replace(/\/+$/, "");
|
|
28372
|
+
const tokenFile = String(config.tokenFile ?? path18.join(path18.dirname(loaded.path), "youtrack.token"));
|
|
28373
|
+
const tab = String(defaults.profileTab ?? "account-security");
|
|
28374
|
+
const createUrl = `${base}/users/me?${new URLSearchParams({ tab })}`;
|
|
28375
|
+
const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
|
|
28376
|
+
return {
|
|
28377
|
+
data: {
|
|
28378
|
+
tokenName: name,
|
|
28379
|
+
tokenDescription: desc,
|
|
28380
|
+
scopes,
|
|
28381
|
+
tokenFile: path18.resolve(tokenFile),
|
|
28382
|
+
createUrl,
|
|
28383
|
+
docsUrl,
|
|
28384
|
+
prefillSupported: false,
|
|
28385
|
+
steps: [
|
|
28386
|
+
"Profile → Account Security → **New token** (or open createUrl)",
|
|
28387
|
+
`Name: **${name}**`,
|
|
28388
|
+
`Scope: **${scopes.join(", ")}** only — remove other services`,
|
|
28389
|
+
"**Create token** → copy immediately (shown once)",
|
|
28390
|
+
"Paste into token file → save → `/wk-status`"
|
|
28391
|
+
]
|
|
28453
28392
|
}
|
|
28454
28393
|
};
|
|
28455
|
-
|
|
28456
|
-
|
|
28457
|
-
|
|
28458
|
-
|
|
28459
|
-
|
|
28460
|
-
|
|
28461
|
-
|
|
28462
|
-
};
|
|
28463
|
-
lines.push(`# Verification Context
|
|
28464
|
-
`);
|
|
28465
|
-
lines.push(`
|
|
28466
|
-
root: ${cwd}
|
|
28467
|
-
`);
|
|
28468
|
-
lines.push(`dry_run: ${String(dryRun)}
|
|
28469
|
-
`);
|
|
28470
|
-
const pkgPath = path16.join(cwd, "package.json");
|
|
28471
|
-
if (existsSync9(pkgPath)) {
|
|
28472
|
-
const runner = packageRunner(cwd);
|
|
28473
|
-
for (const script of ["lint", "format:check", "test", "build"]) {
|
|
28474
|
-
if (hasScript(cwd, script)) {
|
|
28475
|
-
if (runner === "npm")
|
|
28476
|
-
runCheck(script, "npm", ["run", script]);
|
|
28477
|
-
else
|
|
28478
|
-
runCheck(script, runner, [script]);
|
|
28479
|
-
} else {
|
|
28480
|
-
skipCheck(script, `package.json has no ${script} script`);
|
|
28481
|
-
}
|
|
28482
|
-
}
|
|
28483
|
-
} else {
|
|
28484
|
-
skipCheck("javascript", "package.json not found");
|
|
28485
|
-
}
|
|
28486
|
-
const cargoManifest = existsSync9(path16.join(cwd, "Cargo.toml")) ? "Cargo.toml" : existsSync9(path16.join(cwd, "src-tauri/Cargo.toml")) ? "src-tauri/Cargo.toml" : "";
|
|
28487
|
-
if (cargoManifest) {
|
|
28488
|
-
runCheck("cargo fmt", "cargo", ["fmt", "--manifest-path", cargoManifest, "--", "--check"]);
|
|
28489
|
-
runCheck("cargo clippy", "cargo", [
|
|
28490
|
-
"clippy",
|
|
28491
|
-
"--manifest-path",
|
|
28492
|
-
cargoManifest,
|
|
28493
|
-
"--all-targets",
|
|
28494
|
-
"--",
|
|
28495
|
-
"-D",
|
|
28496
|
-
"warnings"
|
|
28497
|
-
]);
|
|
28498
|
-
runCheck("cargo test", "cargo", ["test", "--manifest-path", cargoManifest, "--all-targets"]);
|
|
28499
|
-
} else {
|
|
28500
|
-
skipCheck("rust", "Cargo.toml not found");
|
|
28394
|
+
}
|
|
28395
|
+
function parseIssueRef(input) {
|
|
28396
|
+
const trimmed = String(input ?? "").trim();
|
|
28397
|
+
if (!trimmed)
|
|
28398
|
+
return { error: "empty issue reference" };
|
|
28399
|
+
if (ISSUE_RE.test(trimmed)) {
|
|
28400
|
+
return { issueId: trimmed, source: "id" };
|
|
28501
28401
|
}
|
|
28502
|
-
|
|
28503
|
-
|
|
28504
|
-
|
|
28505
|
-
else
|
|
28506
|
-
skipCheck("pytest", "pytest not available");
|
|
28507
|
-
if (commandOnPath("ruff"))
|
|
28508
|
-
runCheck("ruff check", "ruff", ["check", "."]);
|
|
28509
|
-
else
|
|
28510
|
-
skipCheck("ruff check", "ruff not available");
|
|
28402
|
+
const fromPath = trimmed.match(/\/(?:issue|issues)\/([A-Z]+-\d+)/i);
|
|
28403
|
+
if (fromPath && ISSUE_RE.test(fromPath[1])) {
|
|
28404
|
+
return { issueId: fromPath[1], source: "url" };
|
|
28511
28405
|
}
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
28515
|
-
`);
|
|
28516
|
-
lines.push(`command: grep -qi "## [Unreleased]" CHANGELOG.md
|
|
28517
|
-
|
|
28518
|
-
`);
|
|
28519
|
-
const changelogPath = path16.join(cwd, "CHANGELOG.md");
|
|
28520
|
-
if (dryRun) {
|
|
28521
|
-
lines.push(`status: skipped (dry run)
|
|
28522
|
-
`);
|
|
28523
|
-
skipped += 1;
|
|
28524
|
-
} else if (existsSync9(changelogPath)) {
|
|
28525
|
-
if (/## \[Unreleased\]/i.test(readFileSync8(changelogPath, "utf8"))) {
|
|
28526
|
-
lines.push(`status: pass
|
|
28527
|
-
`);
|
|
28528
|
-
passed += 1;
|
|
28529
|
-
} else {
|
|
28530
|
-
lines.push(`status: fail (missing ## [Unreleased])
|
|
28531
|
-
`);
|
|
28532
|
-
failed += 1;
|
|
28533
|
-
}
|
|
28534
|
-
} else {
|
|
28535
|
-
lines.push(`status: fail (missing CHANGELOG.md)
|
|
28536
|
-
`);
|
|
28537
|
-
failed += 1;
|
|
28406
|
+
const anywhere = trimmed.match(/([A-Z]+-\d+)/);
|
|
28407
|
+
if (anywhere && ISSUE_RE.test(anywhere[1])) {
|
|
28408
|
+
return { issueId: anywhere[1], source: "url" };
|
|
28538
28409
|
}
|
|
28539
|
-
|
|
28540
|
-
# Summary
|
|
28541
|
-
|
|
28542
|
-
`);
|
|
28543
|
-
lines.push(`passed: ${passed}
|
|
28544
|
-
`);
|
|
28545
|
-
lines.push(`failed: ${failed}
|
|
28546
|
-
`);
|
|
28547
|
-
lines.push(`skipped: ${skipped}
|
|
28548
|
-
`);
|
|
28549
|
-
return {
|
|
28550
|
-
stdout: lines.join(""),
|
|
28551
|
-
stderr: stderrParts.join(`
|
|
28552
|
-
`),
|
|
28553
|
-
exitCode: failed > 0 ? 1 : 0,
|
|
28554
|
-
cwd
|
|
28555
|
-
};
|
|
28410
|
+
return { error: `could not parse issue id from: ${trimmed}` };
|
|
28556
28411
|
}
|
|
28557
|
-
|
|
28558
|
-
|
|
28559
|
-
|
|
28560
|
-
|
|
28561
|
-
|
|
28562
|
-
|
|
28563
|
-
|
|
28564
|
-
|
|
28565
|
-
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
|
|
28569
|
-
|
|
28570
|
-
|
|
28571
|
-
|
|
28572
|
-
|
|
28573
|
-
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
|
|
28578
|
-
writeFileSync as writeFileSync3
|
|
28579
|
-
} from "node:fs";
|
|
28580
|
-
function writeFileExclusive(file, content, mode) {
|
|
28581
|
-
try {
|
|
28582
|
-
writeFileSync3(file, content, { encoding: "utf8", flag: "wx", mode });
|
|
28583
|
-
return "created";
|
|
28584
|
-
} catch (err) {
|
|
28585
|
-
if (err.code === "EEXIST")
|
|
28586
|
-
return "preserved";
|
|
28587
|
-
throw err;
|
|
28412
|
+
var contextScripts = {
|
|
28413
|
+
config: readYouTrackContextConfig,
|
|
28414
|
+
greeting: () => youTrackGreeting(youTrackReadOnlyConfigPath()),
|
|
28415
|
+
parseDuration: (text) => youTrackParseDuration(text),
|
|
28416
|
+
api: () => ({ error: "YouTrack context is read-only" })
|
|
28417
|
+
};
|
|
28418
|
+
function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
28419
|
+
const root = resolveWorkspaceRoot(workspace_root);
|
|
28420
|
+
for (const rel of [spec_path, plan_path].filter(Boolean)) {
|
|
28421
|
+
let full;
|
|
28422
|
+
try {
|
|
28423
|
+
full = resolveInside(root, rel);
|
|
28424
|
+
} catch {
|
|
28425
|
+
continue;
|
|
28426
|
+
}
|
|
28427
|
+
if (!fs8.existsSync(full))
|
|
28428
|
+
continue;
|
|
28429
|
+
const text = fs8.readFileSync(full, "utf8");
|
|
28430
|
+
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
28431
|
+
if (m)
|
|
28432
|
+
return m[1];
|
|
28588
28433
|
}
|
|
28434
|
+
return null;
|
|
28589
28435
|
}
|
|
28590
|
-
|
|
28591
|
-
|
|
28592
|
-
|
|
28593
|
-
|
|
28594
|
-
|
|
28595
|
-
|
|
28596
|
-
|
|
28597
|
-
|
|
28598
|
-
|
|
28599
|
-
|
|
28600
|
-
|
|
28601
|
-
|
|
28602
|
-
|
|
28603
|
-
|
|
28604
|
-
|
|
28605
|
-
|
|
28606
|
-
|
|
28607
|
-
|
|
28608
|
-
|
|
28609
|
-
|
|
28610
|
-
|
|
28611
|
-
|
|
28612
|
-
var
|
|
28613
|
-
|
|
28614
|
-
|
|
28615
|
-
|
|
28616
|
-
|
|
28617
|
-
|
|
28618
|
-
|
|
28619
|
-
|
|
28620
|
-
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
}
|
|
28624
|
-
return r.status === 0 && (r.stdout ?? "").trim() !== "";
|
|
28436
|
+
function meetingOptionsFromConfig(cfg) {
|
|
28437
|
+
const base = (cfg.baseUrl || "").replace(/\/$/, "");
|
|
28438
|
+
if (cfg.meetingIssues && typeof cfg.meetingIssues === "object") {
|
|
28439
|
+
return Object.entries(cfg.meetingIssues).map(([key, item]) => ({
|
|
28440
|
+
key,
|
|
28441
|
+
issue: item.issue,
|
|
28442
|
+
label: item.label ?? item.issue,
|
|
28443
|
+
workItemText: item.workItemText ?? "Reuniones",
|
|
28444
|
+
url: item.url ?? (base && item.issue ? `${base}/issue/${item.issue}` : null)
|
|
28445
|
+
}));
|
|
28446
|
+
}
|
|
28447
|
+
const issue = cfg.meetingIssue;
|
|
28448
|
+
return [
|
|
28449
|
+
{
|
|
28450
|
+
key: "general",
|
|
28451
|
+
issue,
|
|
28452
|
+
label: "General meetings",
|
|
28453
|
+
workItemText: "Reuniones",
|
|
28454
|
+
url: base && issue ? `${base}/issue/${issue}` : null
|
|
28455
|
+
}
|
|
28456
|
+
];
|
|
28457
|
+
}
|
|
28458
|
+
var readOnlyYouTrackConfig = (cfg) => {
|
|
28459
|
+
const publicUrl = (value) => {
|
|
28460
|
+
if (typeof value !== "string")
|
|
28461
|
+
return;
|
|
28462
|
+
try {
|
|
28463
|
+
const url = new URL(value);
|
|
28464
|
+
if (url.username || url.password)
|
|
28465
|
+
return `${url.protocol}//${url.host}${url.pathname}${url.search}${url.hash}`;
|
|
28466
|
+
return value;
|
|
28467
|
+
} catch {
|
|
28468
|
+
return;
|
|
28469
|
+
}
|
|
28625
28470
|
};
|
|
28626
|
-
const
|
|
28627
|
-
const
|
|
28628
|
-
|
|
28629
|
-
|
|
28630
|
-
|
|
28631
|
-
|
|
28632
|
-
|
|
28633
|
-
|
|
28634
|
-
|
|
28635
|
-
|
|
28636
|
-
|
|
28637
|
-
|
|
28638
|
-
|
|
28471
|
+
const safe = {};
|
|
28472
|
+
const baseUrl = publicUrl(cfg.baseUrl);
|
|
28473
|
+
if (baseUrl)
|
|
28474
|
+
safe.baseUrl = baseUrl;
|
|
28475
|
+
for (const key of ["timezone", "greetingCutoff", "defaultMention", "meetingIssue"])
|
|
28476
|
+
if (typeof cfg[key] === "string")
|
|
28477
|
+
safe[key] = cfg[key];
|
|
28478
|
+
if (cfg.greetings && typeof cfg.greetings === "object" && !Array.isArray(cfg.greetings)) {
|
|
28479
|
+
const greetings = {};
|
|
28480
|
+
for (const key of ["morning", "afternoon"])
|
|
28481
|
+
if (typeof cfg.greetings[key] === "string")
|
|
28482
|
+
greetings[key] = cfg.greetings[key];
|
|
28483
|
+
if (Object.keys(greetings).length)
|
|
28484
|
+
safe.greetings = greetings;
|
|
28639
28485
|
}
|
|
28640
|
-
if (
|
|
28486
|
+
if (cfg.meetingIssues && typeof cfg.meetingIssues === "object" && !Array.isArray(cfg.meetingIssues)) {
|
|
28487
|
+
safe.meetingIssues = Object.fromEntries(Object.entries(cfg.meetingIssues).flatMap(([key, value]) => {
|
|
28488
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
28489
|
+
return [];
|
|
28490
|
+
const item = value;
|
|
28491
|
+
const safeItem = {};
|
|
28492
|
+
for (const field of ["issue", "label", "workItemText"])
|
|
28493
|
+
if (typeof item[field] === "string")
|
|
28494
|
+
safeItem[field] = item[field];
|
|
28495
|
+
const url = publicUrl(item.url);
|
|
28496
|
+
if (url)
|
|
28497
|
+
safeItem.url = url;
|
|
28498
|
+
return [[key, safeItem]];
|
|
28499
|
+
}));
|
|
28500
|
+
}
|
|
28501
|
+
return safe;
|
|
28502
|
+
};
|
|
28503
|
+
function context({
|
|
28504
|
+
spec_path,
|
|
28505
|
+
plan_path,
|
|
28506
|
+
issue_id,
|
|
28507
|
+
issue_url,
|
|
28508
|
+
issue_ref,
|
|
28509
|
+
mode,
|
|
28510
|
+
workspace_root
|
|
28511
|
+
}, scripts = contextScripts) {
|
|
28512
|
+
const cfg = scripts.config();
|
|
28513
|
+
if (cfg.error)
|
|
28514
|
+
return { error: cfg.error };
|
|
28515
|
+
const greeting = scripts.greeting();
|
|
28516
|
+
if (greeting.exitCode !== 0) {
|
|
28517
|
+
return { error: (greeting.stderr || greeting.stdout || "greeting failed").trim() };
|
|
28518
|
+
}
|
|
28519
|
+
const safeConfig = readOnlyYouTrackConfig(cfg.data);
|
|
28520
|
+
const meetingOptions = meetingOptionsFromConfig(safeConfig);
|
|
28521
|
+
if (mode === "meetings" && !issue_id && !issue_url && !issue_ref) {
|
|
28641
28522
|
return {
|
|
28642
|
-
|
|
28643
|
-
|
|
28644
|
-
|
|
28645
|
-
|
|
28646
|
-
|
|
28647
|
-
|
|
28523
|
+
config: safeConfig,
|
|
28524
|
+
greeting: greeting.stdout.trim(),
|
|
28525
|
+
mode: "meetings",
|
|
28526
|
+
requiresMeetingChoice: true,
|
|
28527
|
+
meetingOptions,
|
|
28528
|
+
issueId: null
|
|
28648
28529
|
};
|
|
28649
28530
|
}
|
|
28650
|
-
|
|
28531
|
+
let issue = issue_id;
|
|
28532
|
+
if (!issue && (issue_url || issue_ref)) {
|
|
28533
|
+
const parsed = parseIssueRef(issue_url ?? issue_ref);
|
|
28534
|
+
if ("error" in parsed)
|
|
28535
|
+
return { error: parsed.error };
|
|
28536
|
+
issue = parsed.issueId;
|
|
28537
|
+
}
|
|
28538
|
+
if (!issue && mode === "meetings")
|
|
28539
|
+
issue = meetingOptions[0]?.issue ?? safeConfig.meetingIssue;
|
|
28540
|
+
if (!issue)
|
|
28541
|
+
issue = resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) ?? undefined;
|
|
28542
|
+
if (!issue || !ISSUE_RE.test(issue)) {
|
|
28651
28543
|
return {
|
|
28652
|
-
|
|
28653
|
-
|
|
28654
|
-
integration: "pr",
|
|
28655
|
-
protected: [root],
|
|
28656
|
-
allowed: ["*"],
|
|
28657
|
-
prefixes: PREFIXES
|
|
28544
|
+
error: "invalid or missing issue id — pass issue_url, issue_id, or spec/plan with **YouTrack:**",
|
|
28545
|
+
requiresIssueInput: true
|
|
28658
28546
|
};
|
|
28659
28547
|
}
|
|
28548
|
+
const base = (safeConfig.baseUrl || "").replace(/\/$/, "");
|
|
28549
|
+
const issueUrl = base ? `${base}/issue/${issue}` : null;
|
|
28550
|
+
const selectedMeeting = meetingOptions.find((m) => m.issue === issue);
|
|
28660
28551
|
return {
|
|
28661
|
-
|
|
28662
|
-
|
|
28663
|
-
|
|
28664
|
-
|
|
28665
|
-
|
|
28666
|
-
|
|
28552
|
+
config: safeConfig,
|
|
28553
|
+
greeting: greeting.stdout.trim(),
|
|
28554
|
+
issueId: issue,
|
|
28555
|
+
issueUrl,
|
|
28556
|
+
mode: mode ?? (selectedMeeting ? "meetings" : "task"),
|
|
28557
|
+
meetingOptions,
|
|
28558
|
+
workItemText: selectedMeeting?.workItemText ?? null
|
|
28667
28559
|
};
|
|
28668
|
-
}
|
|
28669
|
-
|
|
28670
|
-
|
|
28671
|
-
|
|
28672
|
-
|
|
28673
|
-
|
|
28674
|
-
|
|
28675
|
-
|
|
28676
|
-
|
|
28677
|
-
|
|
28678
|
-
|
|
28679
|
-
|
|
28680
|
-
|
|
28681
|
-
|
|
28682
|
-
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
|
|
28690
|
-
|
|
28691
|
-
|
|
28692
|
-
|
|
28693
|
-
const existing = existsSync10(file) ? readFileSync9(file, "utf8") : "";
|
|
28694
|
-
const existingLines = new Set(existing.split(`
|
|
28695
|
-
`).map((l) => l.trim()).filter(Boolean));
|
|
28696
|
-
const added = [];
|
|
28697
|
-
const append = [];
|
|
28698
|
-
for (const entry of GITIGNORE_ENTRIES) {
|
|
28699
|
-
if (entry.trim() === "" || existingLines.has(entry.trim()))
|
|
28700
|
-
continue;
|
|
28701
|
-
append.push(entry);
|
|
28702
|
-
added.push(entry);
|
|
28703
|
-
}
|
|
28704
|
-
if (append.length) {
|
|
28705
|
-
const separator = existing && !existing.endsWith(`
|
|
28706
|
-
`) ? `
|
|
28707
|
-
` : "";
|
|
28708
|
-
writeFileSync4(file, existing + separator + (existing ? `
|
|
28709
|
-
` : "") + append.join(`
|
|
28710
|
-
`) + `
|
|
28711
|
-
`, "utf8");
|
|
28712
|
-
} else if (!existsSync10(file)) {
|
|
28713
|
-
writeFileSync4(file, "", "utf8");
|
|
28560
|
+
}
|
|
28561
|
+
var fetchYouTrackIssueBody = async (issue, creds = youTrackToken, request = youTrackRequest) => {
|
|
28562
|
+
const credentials = creds();
|
|
28563
|
+
if ("error" in credentials)
|
|
28564
|
+
return { error: credentials.error, kind: "creds" };
|
|
28565
|
+
const { token, base } = credentials;
|
|
28566
|
+
const out = await request(`${base}/api/issues/${encodeURIComponent(issue)}?fields=idReadable,summary,description,customFields(name,value(name))`, { method: "GET", token });
|
|
28567
|
+
if (out.status !== 0)
|
|
28568
|
+
return { error: out.stderr || "YouTrack issue fetch failed", kind: "request" };
|
|
28569
|
+
try {
|
|
28570
|
+
const parsed = JSON.parse(out.stdout);
|
|
28571
|
+
const customFields = Array.isArray(parsed.customFields) ? parsed.customFields : [];
|
|
28572
|
+
const stateField = customFields.find((field) => typeof field === "object" && field !== null && field.name === "State");
|
|
28573
|
+
const stateValue = stateField?.value;
|
|
28574
|
+
const stateName = typeof stateValue === "object" && stateValue !== null ? stateValue.name : undefined;
|
|
28575
|
+
return {
|
|
28576
|
+
data: {
|
|
28577
|
+
idReadable: String(parsed.idReadable ?? issue),
|
|
28578
|
+
summary: String(parsed.summary ?? ""),
|
|
28579
|
+
description: typeof parsed.description === "string" ? parsed.description : null,
|
|
28580
|
+
state: typeof stateValue === "string" ? stateValue : typeof stateName === "string" && stateName ? stateName : null
|
|
28581
|
+
}
|
|
28582
|
+
};
|
|
28583
|
+
} catch {
|
|
28584
|
+
return { error: "invalid JSON from YouTrack API", kind: "request" };
|
|
28714
28585
|
}
|
|
28715
|
-
return { ok: true, path: file, added };
|
|
28716
28586
|
};
|
|
28717
28587
|
|
|
28718
|
-
// packages/workit-core/src/core/
|
|
28719
|
-
|
|
28720
|
-
|
|
28721
|
-
var repoRoot2 = assetRoot();
|
|
28722
|
-
var templatesDir = () => path18.join(repoRoot2, "templates", "hygiene");
|
|
28723
|
-
var packageJson = (root) => {
|
|
28724
|
-
if (!existsSync11(path18.join(root, "package.json")))
|
|
28725
|
-
return null;
|
|
28588
|
+
// packages/workit-core/src/core/init.ts
|
|
28589
|
+
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
|
|
28590
|
+
var readJson = (p) => {
|
|
28726
28591
|
try {
|
|
28727
|
-
|
|
28592
|
+
const parsed = JSON.parse(fs9.readFileSync(p, "utf8"));
|
|
28593
|
+
return isConfigObject(parsed) ? parsed : null;
|
|
28728
28594
|
} catch {
|
|
28729
28595
|
return null;
|
|
28730
28596
|
}
|
|
28731
28597
|
};
|
|
28732
|
-
var
|
|
28733
|
-
|
|
28734
|
-
|
|
28735
|
-
|
|
28736
|
-
|
|
28737
|
-
|
|
28738
|
-
|
|
28739
|
-
|
|
28740
|
-
|
|
28741
|
-
|
|
28742
|
-
|
|
28743
|
-
|
|
28744
|
-
|
|
28745
|
-
|
|
28746
|
-
|
|
28747
|
-
|
|
28748
|
-
if (typeof name === "string" && name.trim())
|
|
28749
|
-
return name.trim();
|
|
28598
|
+
var resolveInitProvider = (cwd) => {
|
|
28599
|
+
const env = process.env.WORKFLOW_VCS_PROVIDER?.trim();
|
|
28600
|
+
if (env)
|
|
28601
|
+
return env.toLowerCase();
|
|
28602
|
+
const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
28603
|
+
try {
|
|
28604
|
+
const out = spawnSync5("git", ["remote", "get-url", "origin"], {
|
|
28605
|
+
cwd: root,
|
|
28606
|
+
encoding: "utf8"
|
|
28607
|
+
});
|
|
28608
|
+
if (out.status === 0) {
|
|
28609
|
+
const url = (out.stdout ?? "").trim();
|
|
28610
|
+
if (/github\.com[:/]/.test(url))
|
|
28611
|
+
return "github";
|
|
28612
|
+
if (/gitlab\.com[:/]/.test(url))
|
|
28613
|
+
return "gitlab";
|
|
28750
28614
|
}
|
|
28751
|
-
}
|
|
28752
|
-
return
|
|
28753
|
-
};
|
|
28754
|
-
var planHygieneFiles = (root, opts = {}) => {
|
|
28755
|
-
const openSource = opts.includeOpenSource ?? isOpenSource(root);
|
|
28756
|
-
const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"];
|
|
28757
|
-
if (openSource)
|
|
28758
|
-
files.push("LICENSE", "CONTRIBUTING.md");
|
|
28759
|
-
const planned = [];
|
|
28760
|
-
for (const file of files) {
|
|
28761
|
-
if (existsSync11(path18.join(root, file)))
|
|
28762
|
-
continue;
|
|
28763
|
-
const tpl = path18.join(templatesDir(), file);
|
|
28764
|
-
if (!existsSync11(tpl))
|
|
28765
|
-
continue;
|
|
28766
|
-
const content = readFileSync10(tpl, "utf8").replace(/<PROJECT>/g, path18.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
28767
|
-
planned.push({ path: path18.join(root, file), content });
|
|
28768
|
-
}
|
|
28769
|
-
return planned;
|
|
28615
|
+
} catch {}
|
|
28616
|
+
return null;
|
|
28770
28617
|
};
|
|
28771
|
-
var
|
|
28772
|
-
|
|
28773
|
-
|
|
28774
|
-
|
|
28775
|
-
|
|
28776
|
-
|
|
28777
|
-
|
|
28618
|
+
var youtrackJsonContent = (dir) => ({
|
|
28619
|
+
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://enghouseamg.youtrack.cloud",
|
|
28620
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path19.join(dir, "youtrack.token"),
|
|
28621
|
+
timezone: process.env.WORKFLOW_YT_TIMEZONE ?? "America/Santiago",
|
|
28622
|
+
locale: "es-CL",
|
|
28623
|
+
defaultMention: process.env.WORKFLOW_YT_MENTION ?? "Alejandra.Flores",
|
|
28624
|
+
greetings: { morning: "buenos días", afternoon: "buenas tardes" },
|
|
28625
|
+
greetingCutoff: "12:00",
|
|
28626
|
+
meetingIssue: process.env.WORKFLOW_YT_MEETING_ISSUE ?? "IRPT-12",
|
|
28627
|
+
meetingIssues: {
|
|
28628
|
+
general: {
|
|
28629
|
+
issue: process.env.WORKFLOW_YT_MEETING_ISSUE ?? "IRPT-12",
|
|
28630
|
+
label: "General meetings (Reuniones internas Team IRP)",
|
|
28631
|
+
workItemText: "Reuniones"
|
|
28632
|
+
},
|
|
28633
|
+
web: {
|
|
28634
|
+
issue: process.env.WORKFLOW_YT_WEB_MEETING_ISSUE ?? "NSXFT-21",
|
|
28635
|
+
label: "Web meetings",
|
|
28636
|
+
workItemText: "Reuniones web",
|
|
28637
|
+
url: "https://enghouseamg.youtrack.cloud/projects/NSXFT/issues/NSXFT-21"
|
|
28638
|
+
}
|
|
28639
|
+
},
|
|
28640
|
+
commentHeader: "# Actualización",
|
|
28641
|
+
attachmentsHeaderImages: "## Adjunto capturas",
|
|
28642
|
+
attachmentsHeaderFiles: "## Archivos adjuntos",
|
|
28643
|
+
attachmentsHeaderMixed: "## Adjuntos",
|
|
28644
|
+
tokenDefaults: {
|
|
28645
|
+
name: "workit",
|
|
28646
|
+
description: "OpenCode workit — /wk-issue-update and /wk-meetings",
|
|
28647
|
+
scopes: ["YouTrack"],
|
|
28648
|
+
profileTab: "account-security"
|
|
28778
28649
|
}
|
|
28779
|
-
|
|
28780
|
-
|
|
28781
|
-
|
|
28782
|
-
// packages/workit-core/src/core/registration.ts
|
|
28783
|
-
var CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest";
|
|
28784
|
-
var CURSOR_HOOK_RUN_COMMAND = `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-hook`;
|
|
28785
|
-
function cursorHooksEntry(_packageDir) {
|
|
28650
|
+
});
|
|
28651
|
+
var vcsJsonContent = () => {
|
|
28652
|
+
const provider = resolveInitProvider();
|
|
28786
28653
|
return {
|
|
28787
|
-
|
|
28788
|
-
|
|
28654
|
+
...provider ? { provider } : {},
|
|
28655
|
+
defaultTargetBranch: process.env.WORKFLOW_VCS_TARGET_BRANCH ?? "develop",
|
|
28656
|
+
gitlab: {
|
|
28657
|
+
host: process.env.WORKFLOW_GITLAB_HOST ?? "gitlab.com",
|
|
28658
|
+
apiUrl: process.env.WORKFLOW_GITLAB_API_URL ?? "https://gitlab.com/api/v4"
|
|
28659
|
+
},
|
|
28660
|
+
github: {
|
|
28661
|
+
host: process.env.WORKFLOW_GITHUB_HOST ?? "github.com"
|
|
28662
|
+
},
|
|
28663
|
+
pr: { squashOnMerge: true, removeSourceBranch: true, pushBranch: true, confirmSkip: true }
|
|
28789
28664
|
};
|
|
28790
|
-
}
|
|
28791
|
-
|
|
28792
|
-
// packages/workit-core/src/core/doctor.ts
|
|
28793
|
-
var canonicalCursorHook = cursorHooksEntry("").command;
|
|
28794
|
-
var INSTALLER_REQUIRED = new Set([
|
|
28795
|
-
"runtime",
|
|
28796
|
-
"assets",
|
|
28797
|
-
"launcher",
|
|
28798
|
-
"utility",
|
|
28799
|
-
"stale_pin",
|
|
28800
|
-
"stale_install",
|
|
28801
|
-
"duplicate_registration",
|
|
28802
|
-
"malformed_config"
|
|
28803
|
-
]);
|
|
28804
|
-
// packages/workit-core/src/core/setup.ts
|
|
28805
|
-
var YT_OVERRIDES = [
|
|
28806
|
-
{ envKey: "WORKFLOW_YT_BASE_URL", affects: "youtrack.json baseUrl" },
|
|
28807
|
-
{ envKey: "WORKFLOW_YT_TOKEN_FILE", affects: "youtrack.json tokenFile" },
|
|
28808
|
-
{ envKey: "WORKFLOW_YT_TIMEZONE", affects: "youtrack.json timezone" },
|
|
28809
|
-
{ envKey: "WORKFLOW_YT_MENTION", affects: "youtrack.json defaultMention" },
|
|
28810
|
-
{ envKey: "WORKFLOW_YT_MEETING_ISSUE", affects: "youtrack.json meetingIssue" },
|
|
28811
|
-
{ envKey: "WORKFLOW_YT_WEB_MEETING_ISSUE", affects: "youtrack.json web meeting issue" }
|
|
28812
|
-
];
|
|
28813
|
-
var VCS_OVERRIDES = [
|
|
28814
|
-
{ envKey: "WORKFLOW_VCS_PROVIDER", affects: "vcs.json provider" },
|
|
28815
|
-
{ envKey: "WORKFLOW_VCS_TARGET_BRANCH", affects: "vcs.json defaultTargetBranch" },
|
|
28816
|
-
{ envKey: "WORKFLOW_GITLAB_HOST", affects: "vcs.json gitlab.host" },
|
|
28817
|
-
{ envKey: "WORKFLOW_GITLAB_API_URL", affects: "vcs.json gitlab.apiUrl" },
|
|
28818
|
-
{ envKey: "WORKFLOW_GITHUB_HOST", affects: "vcs.json github.host" }
|
|
28819
|
-
];
|
|
28820
|
-
var SETUP_OVERRIDES = [...YT_OVERRIDES, ...VCS_OVERRIDES];
|
|
28821
|
-
var readFileSafe = (p) => {
|
|
28822
|
-
try {
|
|
28823
|
-
return readFileSync11(p, "utf8");
|
|
28824
|
-
} catch {
|
|
28825
|
-
return null;
|
|
28826
|
-
}
|
|
28827
28665
|
};
|
|
28828
|
-
function
|
|
28829
|
-
const
|
|
28830
|
-
|
|
28831
|
-
|
|
28832
|
-
|
|
28833
|
-
|
|
28834
|
-
|
|
28835
|
-
document,
|
|
28836
|
-
revision,
|
|
28837
|
-
error: workspacesError
|
|
28838
|
-
} = readWorkspacesResult(dir);
|
|
28839
|
-
if (status === "malformed" || status === "invalid")
|
|
28840
|
-
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
28841
|
-
const detection = detectBranchPolicy(workspace_root);
|
|
28842
|
-
const name = String(env.WORKFLOW_BP_NAME ?? path19.basename(workspace_root));
|
|
28843
|
-
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
28844
|
-
const policy = {
|
|
28845
|
-
preset: detection.preset,
|
|
28846
|
-
developBranch: env.WORKFLOW_BP_DEVELOP ?? detection.developBranch ?? undefined,
|
|
28847
|
-
prefixes: detection.prefixes,
|
|
28848
|
-
allowed: detection.allowed,
|
|
28849
|
-
protected: detection.protected,
|
|
28850
|
-
integration
|
|
28851
|
-
};
|
|
28852
|
-
const glob = `${workspace_root.replace(/[\\/]+$/, "")}/**`;
|
|
28853
|
-
if (!validateWorkspaceGlob(glob).ok)
|
|
28854
|
-
return { ok: false, error: `invalid workspace glob: ${glob}` };
|
|
28855
|
-
let existing;
|
|
28856
|
-
try {
|
|
28857
|
-
existing = resolveWorkspaceFrom(workspace_root, dir);
|
|
28858
|
-
} catch (error) {
|
|
28859
|
-
const selectedName = env.WORKFLOW_BP_NAME?.trim();
|
|
28860
|
-
if (!selectedName)
|
|
28861
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
28862
|
-
try {
|
|
28863
|
-
existing = resolveWorkspaceFrom(workspace_root, dir, selectedName);
|
|
28864
|
-
} catch (selectionError) {
|
|
28865
|
-
return {
|
|
28866
|
-
ok: false,
|
|
28867
|
-
error: selectionError instanceof Error ? selectionError.message : String(selectionError)
|
|
28868
|
-
};
|
|
28869
|
-
}
|
|
28870
|
-
}
|
|
28871
|
-
if (existing?.branchPolicy && isDeepStrictEqual(existing.branchPolicy, policy)) {
|
|
28872
|
-
return {
|
|
28873
|
-
ok: true,
|
|
28874
|
-
status: "already-configured",
|
|
28875
|
-
workspace: existing,
|
|
28876
|
-
policy,
|
|
28877
|
-
config_path: wsPath
|
|
28878
|
-
};
|
|
28879
|
-
}
|
|
28880
|
-
const next = existing ? entries.map((w) => w.name === existing?.name ? { ...w, branchPolicy: policy } : w) : [...entries, { name, glob, branchPolicy: policy }];
|
|
28881
|
-
if (!existing && entries.some((entry) => entry.name === name))
|
|
28882
|
-
return { ok: false, error: `workspace name ${JSON.stringify(name)} is already configured` };
|
|
28883
|
-
const proposed = validateWorkspacesDocument({ ...document, workspaces: next }, wsPath);
|
|
28884
|
-
if (proposed.status !== "valid")
|
|
28885
|
-
return { ok: false, error: proposed.error };
|
|
28886
|
-
if (workspacesRevision(readFileSafe(wsPath)) !== revision)
|
|
28887
|
-
return {
|
|
28888
|
-
ok: false,
|
|
28889
|
-
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
28890
|
-
};
|
|
28891
|
-
mkdirSync5(path19.dirname(wsPath), { recursive: true });
|
|
28892
|
-
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
28893
|
-
try {
|
|
28894
|
-
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
28666
|
+
function initApplyData(action, env = process.env) {
|
|
28667
|
+
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
28668
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
28669
|
+
switch (action) {
|
|
28670
|
+
case "youtrack_json": {
|
|
28671
|
+
const out = path19.join(dir, "youtrack.json");
|
|
28672
|
+
fs9.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
28895
28673
|
`, "utf8");
|
|
28896
|
-
|
|
28897
|
-
|
|
28674
|
+
return { action, ok: true, path: out };
|
|
28675
|
+
}
|
|
28676
|
+
case "youtrack_token_placeholder": {
|
|
28677
|
+
const p = path19.join(dir, "youtrack.token");
|
|
28678
|
+
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
|
|
28679
|
+
`, 384) === "preserved";
|
|
28680
|
+
const abs = path19.resolve(p);
|
|
28898
28681
|
return {
|
|
28899
|
-
|
|
28900
|
-
|
|
28682
|
+
action,
|
|
28683
|
+
ok: true,
|
|
28684
|
+
path: abs,
|
|
28685
|
+
token_edit_path: abs,
|
|
28686
|
+
placeholder: TOKEN_PLACEHOLDER2,
|
|
28687
|
+
preserved,
|
|
28688
|
+
instruction: `Open ${abs} in your editor, replace YOUR_TOKEN_HERE with your YouTrack permanent token, save, then run /wk-status`
|
|
28901
28689
|
};
|
|
28902
28690
|
}
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
28906
|
-
|
|
28907
|
-
|
|
28908
|
-
|
|
28909
|
-
|
|
28910
|
-
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
|
|
28914
|
-
|
|
28915
|
-
|
|
28916
|
-
|
|
28917
|
-
|
|
28918
|
-
|
|
28919
|
-
|
|
28920
|
-
|
|
28921
|
-
|
|
28922
|
-
|
|
28923
|
-
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
|
|
28934
|
-
|
|
28935
|
-
|
|
28936
|
-
|
|
28937
|
-
|
|
28938
|
-
|
|
28939
|
-
|
|
28940
|
-
|
|
28941
|
-
|
|
28942
|
-
|
|
28943
|
-
|
|
28944
|
-
|
|
28945
|
-
|
|
28946
|
-
|
|
28947
|
-
|
|
28948
|
-
|
|
28949
|
-
|
|
28950
|
-
|
|
28951
|
-
|
|
28952
|
-
|
|
28953
|
-
|
|
28954
|
-
|
|
28955
|
-
}
|
|
28956
|
-
|
|
28957
|
-
|
|
28958
|
-
|
|
28959
|
-
|
|
28960
|
-
|
|
28961
|
-
|
|
28962
|
-
|
|
28963
|
-
} catch {
|
|
28964
|
-
return { error: "YouTrack configuration is unavailable" };
|
|
28965
|
-
}
|
|
28966
|
-
if (!isConfigObject(parsed))
|
|
28967
|
-
return { error: "YouTrack configuration is unavailable" };
|
|
28968
|
-
return { data: readOnlyYouTrackConfig(parsed) };
|
|
28969
|
-
};
|
|
28970
|
-
function tzParts(date, tz) {
|
|
28971
|
-
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
28972
|
-
timeZone: tz,
|
|
28973
|
-
year: "numeric",
|
|
28974
|
-
month: "2-digit",
|
|
28975
|
-
day: "2-digit",
|
|
28976
|
-
hour: "2-digit",
|
|
28977
|
-
minute: "2-digit",
|
|
28978
|
-
hourCycle: "h23"
|
|
28979
|
-
}).formatToParts(date);
|
|
28980
|
-
const map = Object.fromEntries(parts.map((p) => [p.type, p.value]));
|
|
28981
|
-
return { y: map.year, m: map.month, d: map.day, hour: map.hour, minute: map.minute };
|
|
28982
|
-
}
|
|
28983
|
-
function youTrackGreeting(configOverride) {
|
|
28984
|
-
const cfgPath = configOverride ?? youTrackConfigPath();
|
|
28985
|
-
let parsed;
|
|
28986
|
-
try {
|
|
28987
|
-
parsed = JSON.parse(fs8.readFileSync(cfgPath, "utf8"));
|
|
28988
|
-
} catch {
|
|
28989
|
-
return {
|
|
28990
|
-
stdout: "",
|
|
28991
|
-
exitCode: 1,
|
|
28992
|
-
stderr: fs8.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
|
|
28993
|
-
};
|
|
28994
|
-
}
|
|
28995
|
-
if (!isConfigObject(parsed)) {
|
|
28996
|
-
return { stdout: "", exitCode: 1, stderr: `${cfgPath} is not a JSON object` };
|
|
28997
|
-
}
|
|
28998
|
-
const config = parsed;
|
|
28999
|
-
try {
|
|
29000
|
-
const tz = String(config.timezone ?? "America/Santiago");
|
|
29001
|
-
const now = new Date;
|
|
29002
|
-
const { y, m, d, hour, minute } = tzParts(now, tz);
|
|
29003
|
-
const cutoff = String(config.greetingCutoff ?? "12:00").split(":");
|
|
29004
|
-
const cutoffHour = Number(cutoff[0]);
|
|
29005
|
-
const cutoffMinute = Number(cutoff[1] ?? 0);
|
|
29006
|
-
const greetings = config.greetings ?? {};
|
|
29007
|
-
const isMorning = Number(hour) < cutoffHour || Number(hour) === cutoffHour && Number(minute) < cutoffMinute;
|
|
29008
|
-
const greeting = isMorning ? greetings.morning ?? "buenos días" : greetings.afternoon ?? "buenas tardes";
|
|
29009
|
-
const mention = String(config.defaultMention ?? "Alejandra.Flores");
|
|
29010
|
-
return { stdout: `@${mention} Hola, ${greeting}.
|
|
29011
|
-
`, exitCode: 0, stderr: "" };
|
|
29012
|
-
} catch (err) {
|
|
29013
|
-
return {
|
|
29014
|
-
stdout: "",
|
|
29015
|
-
exitCode: 1,
|
|
29016
|
-
stderr: err instanceof Error ? err.message : "greeting failed"
|
|
29017
|
-
};
|
|
29018
|
-
}
|
|
29019
|
-
}
|
|
29020
|
-
function youTrackParseDuration(text) {
|
|
29021
|
-
const lower = String(text).toLowerCase().trim();
|
|
29022
|
-
let total = 0;
|
|
29023
|
-
for (const match of lower.matchAll(/(\d+)\s*h/g))
|
|
29024
|
-
total += Number(match[1]) * 60;
|
|
29025
|
-
for (const match of lower.matchAll(/(\d+)\s*m/g))
|
|
29026
|
-
total += Number(match[1]);
|
|
29027
|
-
if (total === 0 && /^\d+$/.test(lower))
|
|
29028
|
-
total = Number(lower);
|
|
29029
|
-
if (total <= 0)
|
|
29030
|
-
return { error: "could not parse duration" };
|
|
29031
|
-
return { data: { minutes: total, text: String(text).trim() } };
|
|
29032
|
-
}
|
|
29033
|
-
var youTrackToken = () => {
|
|
29034
|
-
const loaded = readYouTrackConfig(true);
|
|
29035
|
-
if ("error" in loaded)
|
|
29036
|
-
return loaded;
|
|
29037
|
-
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
29038
|
-
const tokenPath = tokenFile ? path20.isAbsolute(tokenFile) ? path20.resolve(tokenFile) : path20.resolve(process.cwd(), tokenFile) : "";
|
|
29039
|
-
if (!tokenPath || !fs8.existsSync(tokenPath))
|
|
29040
|
-
return { error: "missing youtrack.token" };
|
|
29041
|
-
if (!youTrackTokenModeOk(tokenPath))
|
|
29042
|
-
return { error: "youtrack.token mode must be 0600" };
|
|
29043
|
-
const token = fs8.readFileSync(tokenPath, "utf8").trim();
|
|
29044
|
-
if (!token)
|
|
29045
|
-
return { error: "empty token file" };
|
|
29046
|
-
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
29047
|
-
return {
|
|
29048
|
-
error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
|
|
29049
|
-
};
|
|
29050
|
-
}
|
|
29051
|
-
const base = String(loaded.config.baseUrl ?? "").replace(/\/+$/, "");
|
|
29052
|
-
if (!base)
|
|
29053
|
-
return { error: "baseUrl missing in config" };
|
|
29054
|
-
return { token, base };
|
|
29055
|
-
};
|
|
29056
|
-
async function youTrackRequest(url, init) {
|
|
29057
|
-
const headers = {
|
|
29058
|
-
Authorization: `Bearer ${init.token}`,
|
|
29059
|
-
Accept: "application/json"
|
|
29060
|
-
};
|
|
29061
|
-
let body;
|
|
29062
|
-
if (init.body !== undefined) {
|
|
29063
|
-
headers["Content-Type"] = "application/json";
|
|
29064
|
-
body = JSON.stringify(init.body);
|
|
29065
|
-
}
|
|
29066
|
-
try {
|
|
29067
|
-
const res = await fetch(url, { method: init.method, headers, body });
|
|
29068
|
-
const text = await res.text();
|
|
29069
|
-
if (!res.ok) {
|
|
29070
|
-
return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
|
|
28691
|
+
case "youtrack_scaffold": {
|
|
28692
|
+
const jsonOut = path19.join(dir, "youtrack.json");
|
|
28693
|
+
const tokenOut = path19.join(dir, "youtrack.token");
|
|
28694
|
+
fs9.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
28695
|
+
`, "utf8");
|
|
28696
|
+
const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
|
|
28697
|
+
`, 384) === "preserved";
|
|
28698
|
+
const configPath = path19.resolve(jsonOut);
|
|
28699
|
+
const tokenPath = path19.resolve(tokenOut);
|
|
28700
|
+
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
28701
|
+
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
28702
|
+
let tokenCreate = {};
|
|
28703
|
+
try {
|
|
28704
|
+
const cfg = readJson(configPath) ?? {};
|
|
28705
|
+
const base = String(cfg.baseUrl ?? "").replace(/\/+$/, "");
|
|
28706
|
+
const meeting = String(cfg.meetingIssue ?? "");
|
|
28707
|
+
tokenCreate = youTrackTokenCreateUrl().data;
|
|
28708
|
+
return {
|
|
28709
|
+
action,
|
|
28710
|
+
ok: true,
|
|
28711
|
+
youtrack_json: configPath,
|
|
28712
|
+
youtrack_token: tokenPath,
|
|
28713
|
+
token_edit_path: tokenPath,
|
|
28714
|
+
token_create_url: tokenCreate.createUrl,
|
|
28715
|
+
token_create: tokenCreate,
|
|
28716
|
+
config_edit_path: configPath,
|
|
28717
|
+
placeholder: TOKEN_PLACEHOLDER2,
|
|
28718
|
+
preserved,
|
|
28719
|
+
youtrack_config: {
|
|
28720
|
+
config_edit_path: configPath,
|
|
28721
|
+
baseUrl: base,
|
|
28722
|
+
meetingIssue: meeting,
|
|
28723
|
+
meetingIssueUrl: base && meeting ? `${base}/issue/${meeting}` : null,
|
|
28724
|
+
defaultMention: cfg.defaultMention,
|
|
28725
|
+
timezone: cfg.timezone,
|
|
28726
|
+
locale: cfg.locale,
|
|
28727
|
+
tokenCreate,
|
|
28728
|
+
timeLogging: {
|
|
28729
|
+
meetings: {
|
|
28730
|
+
issue: meeting,
|
|
28731
|
+
skill: "/wk-meetings",
|
|
28732
|
+
logsTime: true,
|
|
28733
|
+
postsComment: false
|
|
28734
|
+
},
|
|
28735
|
+
taskWork: {
|
|
28736
|
+
issueSource: "active spec/plan **YouTrack:** field or --issue",
|
|
28737
|
+
skill: "/wk-issue-update",
|
|
28738
|
+
logsTime: true,
|
|
28739
|
+
postsComment: true
|
|
28740
|
+
}
|
|
28741
|
+
}
|
|
28742
|
+
},
|
|
28743
|
+
instruction: `Open the create-token URL, New token → name workit, scope YouTrack, paste into ${tokenPath}, then /wk-status.`
|
|
28744
|
+
};
|
|
28745
|
+
} finally {
|
|
28746
|
+
if (prev === undefined)
|
|
28747
|
+
delete process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
28748
|
+
else
|
|
28749
|
+
process.env.WORKFLOW_YOUTRACK_CONFIG = prev;
|
|
28750
|
+
}
|
|
29071
28751
|
}
|
|
29072
|
-
|
|
29073
|
-
|
|
29074
|
-
|
|
29075
|
-
|
|
29076
|
-
|
|
29077
|
-
|
|
29078
|
-
|
|
29079
|
-
if (!fs8.existsSync(cfgPath))
|
|
29080
|
-
return { error: "missing youtrack.json" };
|
|
29081
|
-
const creds = youTrackToken();
|
|
29082
|
-
if ("error" in creds)
|
|
29083
|
-
return { error: creds.error };
|
|
29084
|
-
const { token, base } = creds;
|
|
29085
|
-
const me = await youTrackRequest(`${base}/api/users/me?fields=id,login,name,email`, {
|
|
29086
|
-
method: "GET",
|
|
29087
|
-
token
|
|
29088
|
-
});
|
|
29089
|
-
if (me.status !== 0) {
|
|
29090
|
-
const err = me.status === 401 || me.status === 403 ? "authentication failed (401/403)" : `HTTP error: ${me.stderr.slice(0, 200)}`;
|
|
29091
|
-
return { error: err, http_status: me.status };
|
|
29092
|
-
}
|
|
29093
|
-
let user;
|
|
29094
|
-
try {
|
|
29095
|
-
user = JSON.parse(me.stdout);
|
|
29096
|
-
} catch {
|
|
29097
|
-
return { error: "invalid JSON from YouTrack /api/users/me" };
|
|
29098
|
-
}
|
|
29099
|
-
const result = {
|
|
29100
|
-
ok: true,
|
|
29101
|
-
method: "GET /api/users/me",
|
|
29102
|
-
baseUrl: base,
|
|
29103
|
-
login: user.login,
|
|
29104
|
-
name: user.name,
|
|
29105
|
-
email: user.email,
|
|
29106
|
-
id: user.id
|
|
29107
|
-
};
|
|
29108
|
-
const meeting = readYouTrackConfig(false);
|
|
29109
|
-
const meetingIssue = "config" in meeting ? meeting.config.meetingIssue : undefined;
|
|
29110
|
-
if (meetingIssue) {
|
|
29111
|
-
const issue = await youTrackRequest(`${base}/api/issues/${meetingIssue}?fields=id,idReadable,summary`, { method: "GET", token });
|
|
29112
|
-
if (issue.status === 0) {
|
|
28752
|
+
case "vcs_scaffold": {
|
|
28753
|
+
const jsonOut = path19.join(dir, "vcs.json");
|
|
28754
|
+
fs9.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
|
|
28755
|
+
`, "utf8");
|
|
28756
|
+
const configPath = path19.resolve(jsonOut);
|
|
28757
|
+
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
28758
|
+
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
29113
28759
|
try {
|
|
29114
|
-
const
|
|
29115
|
-
|
|
29116
|
-
|
|
29117
|
-
|
|
29118
|
-
|
|
29119
|
-
|
|
29120
|
-
|
|
29121
|
-
|
|
29122
|
-
|
|
28760
|
+
const cfg = readJson(configPath) ?? {};
|
|
28761
|
+
const rawProvider = cfg.provider;
|
|
28762
|
+
const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider : null;
|
|
28763
|
+
return {
|
|
28764
|
+
action,
|
|
28765
|
+
ok: true,
|
|
28766
|
+
vcs_json: configPath,
|
|
28767
|
+
config_edit_path: configPath,
|
|
28768
|
+
vcs_config: {
|
|
28769
|
+
config_edit_path: configPath,
|
|
28770
|
+
provider,
|
|
28771
|
+
defaultTargetBranch: cfg.defaultTargetBranch,
|
|
28772
|
+
pr: cfg.pr,
|
|
28773
|
+
switchHint: 'Change "provider" to "github" when you migrate; authenticate with gh/glab',
|
|
28774
|
+
skill: "/wk-pr"
|
|
28775
|
+
},
|
|
28776
|
+
instruction: provider === null ? `No provider resolved — set provider to gitlab or github in ${configPath}, then /wk-status.` : `Run ${provider === "gitlab" ? "glab" : "gh"} auth login, then /wk-status.`
|
|
28777
|
+
};
|
|
28778
|
+
} finally {
|
|
28779
|
+
if (prev === undefined)
|
|
28780
|
+
delete process.env.WORKFLOW_VCS_CONFIG;
|
|
28781
|
+
else
|
|
28782
|
+
process.env.WORKFLOW_VCS_CONFIG = prev;
|
|
28783
|
+
}
|
|
29123
28784
|
}
|
|
29124
|
-
|
|
29125
|
-
|
|
29126
|
-
}
|
|
29127
|
-
function youTrackTokenCreateUrl() {
|
|
29128
|
-
const tokenName = process.env.WORKFLOW_YT_TOKEN_NAME ?? "workit";
|
|
29129
|
-
const loaded = readYouTrackConfig(false);
|
|
29130
|
-
if ("error" in loaded) {
|
|
29131
|
-
return { data: { error: loaded.error } };
|
|
29132
|
-
}
|
|
29133
|
-
const config = loaded.config;
|
|
29134
|
-
const defaults = config.tokenDefaults ?? {};
|
|
29135
|
-
const name = String(defaults.name ?? tokenName);
|
|
29136
|
-
const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
|
|
29137
|
-
const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
|
|
29138
|
-
const base = String(config.baseUrl ?? "https://enghouseamg.youtrack.cloud").replace(/\/+$/, "");
|
|
29139
|
-
const tokenFile = String(config.tokenFile ?? path20.join(path20.dirname(loaded.path), "youtrack.token"));
|
|
29140
|
-
const tab = String(defaults.profileTab ?? "account-security");
|
|
29141
|
-
const createUrl = `${base}/users/me?${new URLSearchParams({ tab })}`;
|
|
29142
|
-
const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
|
|
29143
|
-
return {
|
|
29144
|
-
data: {
|
|
29145
|
-
tokenName: name,
|
|
29146
|
-
tokenDescription: desc,
|
|
29147
|
-
scopes,
|
|
29148
|
-
tokenFile: path20.resolve(tokenFile),
|
|
29149
|
-
createUrl,
|
|
29150
|
-
docsUrl,
|
|
29151
|
-
prefillSupported: false,
|
|
29152
|
-
steps: [
|
|
29153
|
-
"Profile → Account Security → **New token** (or open createUrl)",
|
|
29154
|
-
`Name: **${name}**`,
|
|
29155
|
-
`Scope: **${scopes.join(", ")}** only — remove other services`,
|
|
29156
|
-
"**Create token** → copy immediately (shown once)",
|
|
29157
|
-
"Paste into token file → save → `/wk-status`"
|
|
29158
|
-
]
|
|
28785
|
+
case "branch_policy": {
|
|
28786
|
+
const root = env.WORKFLOW_WORKSPACE_ROOT?.trim() ? env.WORKFLOW_WORKSPACE_ROOT : process.cwd();
|
|
28787
|
+
return applyWorkspaceBranchPolicy({ workspace_root: root, env });
|
|
29159
28788
|
}
|
|
29160
|
-
|
|
29161
|
-
|
|
29162
|
-
|
|
29163
|
-
|
|
29164
|
-
if (!trimmed)
|
|
29165
|
-
return { error: "empty issue reference" };
|
|
29166
|
-
if (ISSUE_RE.test(trimmed)) {
|
|
29167
|
-
return { issueId: trimmed, source: "id" };
|
|
29168
|
-
}
|
|
29169
|
-
const fromPath = trimmed.match(/\/(?:issue|issues)\/([A-Z]+-\d+)/i);
|
|
29170
|
-
if (fromPath && ISSUE_RE.test(fromPath[1])) {
|
|
29171
|
-
return { issueId: fromPath[1], source: "url" };
|
|
29172
|
-
}
|
|
29173
|
-
const anywhere = trimmed.match(/([A-Z]+-\d+)/);
|
|
29174
|
-
if (anywhere && ISSUE_RE.test(anywhere[1])) {
|
|
29175
|
-
return { issueId: anywhere[1], source: "url" };
|
|
29176
|
-
}
|
|
29177
|
-
return { error: `could not parse issue id from: ${trimmed}` };
|
|
29178
|
-
}
|
|
29179
|
-
var contextScripts = {
|
|
29180
|
-
config: readYouTrackContextConfig,
|
|
29181
|
-
greeting: () => youTrackGreeting(youTrackReadOnlyConfigPath()),
|
|
29182
|
-
parseDuration: (text) => youTrackParseDuration(text),
|
|
29183
|
-
api: () => ({ error: "YouTrack context is read-only" })
|
|
29184
|
-
};
|
|
29185
|
-
function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
29186
|
-
const root = resolveWorkspaceRoot(workspace_root);
|
|
29187
|
-
for (const rel of [spec_path, plan_path].filter(Boolean)) {
|
|
29188
|
-
let full;
|
|
29189
|
-
try {
|
|
29190
|
-
full = resolveInside(root, rel);
|
|
29191
|
-
} catch {
|
|
29192
|
-
continue;
|
|
28789
|
+
default: {
|
|
28790
|
+
return {
|
|
28791
|
+
error: `unknown action ${action} (youtrack_scaffold|youtrack_json|youtrack_token_placeholder|vcs_scaffold)`
|
|
28792
|
+
};
|
|
29193
28793
|
}
|
|
29194
|
-
if (!fs8.existsSync(full))
|
|
29195
|
-
continue;
|
|
29196
|
-
const text = fs8.readFileSync(full, "utf8");
|
|
29197
|
-
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
29198
|
-
if (m)
|
|
29199
|
-
return m[1];
|
|
29200
28794
|
}
|
|
29201
|
-
return null;
|
|
29202
28795
|
}
|
|
29203
|
-
function
|
|
29204
|
-
|
|
29205
|
-
|
|
29206
|
-
|
|
29207
|
-
|
|
29208
|
-
|
|
29209
|
-
|
|
29210
|
-
|
|
29211
|
-
url: item.url ?? (base && item.issue ? `${base}/issue/${item.issue}` : null)
|
|
29212
|
-
}));
|
|
29213
|
-
}
|
|
29214
|
-
const issue = cfg.meetingIssue;
|
|
29215
|
-
return [
|
|
29216
|
-
{
|
|
29217
|
-
key: "general",
|
|
29218
|
-
issue,
|
|
29219
|
-
label: "General meetings",
|
|
29220
|
-
workItemText: "Reuniones",
|
|
29221
|
-
url: base && issue ? `${base}/issue/${issue}` : null
|
|
29222
|
-
}
|
|
29223
|
-
];
|
|
28796
|
+
function initApply({
|
|
28797
|
+
action,
|
|
28798
|
+
confirmed,
|
|
28799
|
+
env
|
|
28800
|
+
}) {
|
|
28801
|
+
if (!confirmed)
|
|
28802
|
+
return { error: "confirmed: true required" };
|
|
28803
|
+
return { data: initApplyData(action, env ? { ...process.env, ...env } : process.env) };
|
|
29224
28804
|
}
|
|
29225
|
-
|
|
29226
|
-
|
|
29227
|
-
|
|
29228
|
-
|
|
29229
|
-
|
|
29230
|
-
|
|
29231
|
-
|
|
29232
|
-
|
|
29233
|
-
|
|
29234
|
-
|
|
29235
|
-
|
|
29236
|
-
|
|
29237
|
-
};
|
|
29238
|
-
const safe = {};
|
|
29239
|
-
const baseUrl = publicUrl(cfg.baseUrl);
|
|
29240
|
-
if (baseUrl)
|
|
29241
|
-
safe.baseUrl = baseUrl;
|
|
29242
|
-
for (const key of ["timezone", "greetingCutoff", "defaultMention", "meetingIssue"])
|
|
29243
|
-
if (typeof cfg[key] === "string")
|
|
29244
|
-
safe[key] = cfg[key];
|
|
29245
|
-
if (cfg.greetings && typeof cfg.greetings === "object" && !Array.isArray(cfg.greetings)) {
|
|
29246
|
-
const greetings = {};
|
|
29247
|
-
for (const key of ["morning", "afternoon"])
|
|
29248
|
-
if (typeof cfg.greetings[key] === "string")
|
|
29249
|
-
greetings[key] = cfg.greetings[key];
|
|
29250
|
-
if (Object.keys(greetings).length)
|
|
29251
|
-
safe.greetings = greetings;
|
|
29252
|
-
}
|
|
29253
|
-
if (cfg.meetingIssues && typeof cfg.meetingIssues === "object" && !Array.isArray(cfg.meetingIssues)) {
|
|
29254
|
-
safe.meetingIssues = Object.fromEntries(Object.entries(cfg.meetingIssues).flatMap(([key, value]) => {
|
|
29255
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
29256
|
-
return [];
|
|
29257
|
-
const item = value;
|
|
29258
|
-
const safeItem = {};
|
|
29259
|
-
for (const field of ["issue", "label", "workItemText"])
|
|
29260
|
-
if (typeof item[field] === "string")
|
|
29261
|
-
safeItem[field] = item[field];
|
|
29262
|
-
const url = publicUrl(item.url);
|
|
29263
|
-
if (url)
|
|
29264
|
-
safeItem.url = url;
|
|
29265
|
-
return [[key, safeItem]];
|
|
29266
|
-
}));
|
|
29267
|
-
}
|
|
29268
|
-
return safe;
|
|
28805
|
+
|
|
28806
|
+
// packages/workit-opencode/src/shared/repo-result.ts
|
|
28807
|
+
var output = (value) => JSON.stringify(value, null, 2);
|
|
28808
|
+
var diagnostics = ({ stdout, stderr, exitCode }) => ({
|
|
28809
|
+
stdout,
|
|
28810
|
+
stderr,
|
|
28811
|
+
exitCode
|
|
28812
|
+
});
|
|
28813
|
+
var requireConfirmed = (confirmed) => {
|
|
28814
|
+
if (confirmed === true)
|
|
28815
|
+
return null;
|
|
28816
|
+
return output(fail("confirmed: true required"));
|
|
29269
28817
|
};
|
|
29270
|
-
|
|
29271
|
-
|
|
29272
|
-
|
|
29273
|
-
|
|
29274
|
-
|
|
29275
|
-
|
|
29276
|
-
|
|
29277
|
-
|
|
29278
|
-
}, scripts = contextScripts) {
|
|
29279
|
-
const cfg = scripts.config();
|
|
29280
|
-
if (cfg.error)
|
|
29281
|
-
return { error: cfg.error };
|
|
29282
|
-
const greeting = scripts.greeting();
|
|
29283
|
-
if (greeting.exitCode !== 0) {
|
|
29284
|
-
return { error: (greeting.stderr || greeting.stdout || "greeting failed").trim() };
|
|
28818
|
+
var legacyScriptResult = (result) => {
|
|
28819
|
+
let parsed = null;
|
|
28820
|
+
try {
|
|
28821
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
28822
|
+
} catch {}
|
|
28823
|
+
if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false) {
|
|
28824
|
+
const error = parsed?.error ? String(parsed.error) : parsed?.ok === false ? "legacy operation reported failure" : result.stderr.trim() || result.stdout.trim() || "workflow script failed";
|
|
28825
|
+
return fail(error, diagnostics(result));
|
|
29285
28826
|
}
|
|
29286
|
-
|
|
29287
|
-
|
|
29288
|
-
|
|
28827
|
+
if (!parsed)
|
|
28828
|
+
return fail("workflow output parse failed", diagnostics(result));
|
|
28829
|
+
const { ok: _legacyOk, ...data } = parsed;
|
|
28830
|
+
return ok({ ...data, exitCode: 0, ...result.stderr ? { stderr: result.stderr } : {} });
|
|
28831
|
+
};
|
|
28832
|
+
|
|
28833
|
+
// packages/workit-opencode/src/shared/init-apply.ts
|
|
28834
|
+
var initApplyRuntime = {
|
|
28835
|
+
initApply: (root, action, env) => {
|
|
28836
|
+
const out = initApply({ action, confirmed: true, env });
|
|
29289
28837
|
return {
|
|
29290
|
-
|
|
29291
|
-
|
|
29292
|
-
|
|
29293
|
-
|
|
29294
|
-
meetingOptions,
|
|
29295
|
-
issueId: null
|
|
28838
|
+
exitCode: out.error ? 1 : 0,
|
|
28839
|
+
stdout: JSON.stringify(out.data ?? out),
|
|
28840
|
+
stderr: "",
|
|
28841
|
+
cwd: root
|
|
29296
28842
|
};
|
|
29297
28843
|
}
|
|
29298
|
-
|
|
29299
|
-
|
|
29300
|
-
|
|
29301
|
-
|
|
29302
|
-
|
|
29303
|
-
|
|
28844
|
+
};
|
|
28845
|
+
var executeInitApply = (args, directory, runtime) => {
|
|
28846
|
+
const rejected = requireConfirmed(args.confirmed);
|
|
28847
|
+
if (rejected)
|
|
28848
|
+
return rejected;
|
|
28849
|
+
const action = args.action;
|
|
28850
|
+
if (action === "hygiene") {
|
|
28851
|
+
const result = ensureHygieneFiles(directory, {
|
|
28852
|
+
confirmed: args.confirmed,
|
|
28853
|
+
includeOpenSource: args.include_open_source
|
|
28854
|
+
});
|
|
28855
|
+
return output(result.ok ? ok(result) : fail(result.error));
|
|
29304
28856
|
}
|
|
29305
|
-
if (
|
|
29306
|
-
|
|
29307
|
-
|
|
29308
|
-
issue = resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) ?? undefined;
|
|
29309
|
-
if (!issue || !ISSUE_RE.test(issue)) {
|
|
29310
|
-
return {
|
|
29311
|
-
error: "invalid or missing issue id — pass issue_url, issue_id, or spec/plan with **YouTrack:**",
|
|
29312
|
-
requiresIssueInput: true
|
|
29313
|
-
};
|
|
28857
|
+
if (action === "gitignore") {
|
|
28858
|
+
const result = ensureProjectGitignore(directory, args.confirmed);
|
|
28859
|
+
return output(result.ok ? ok(result) : fail(result.error));
|
|
29314
28860
|
}
|
|
29315
|
-
|
|
29316
|
-
|
|
29317
|
-
|
|
28861
|
+
if (action === "config") {
|
|
28862
|
+
const LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
|
|
28863
|
+
if (args.locale !== undefined && !LOCALE_RE.test(args.locale)) {
|
|
28864
|
+
return output(fail(`invalid locale: ${JSON.stringify(args.locale)} — expected BCP-47 like en or es-CL`));
|
|
28865
|
+
}
|
|
28866
|
+
const current = readConfig();
|
|
28867
|
+
const next = mergeConfigValues({
|
|
28868
|
+
locale: args.locale,
|
|
28869
|
+
localeOptions: args.locale_options,
|
|
28870
|
+
timezone: args.timezone,
|
|
28871
|
+
preset: args.branch_policy_preset,
|
|
28872
|
+
allowed: args.branch_policy_allowed,
|
|
28873
|
+
protectedNames: args.branch_policy_protected
|
|
28874
|
+
}, current);
|
|
28875
|
+
writeConfig(next);
|
|
28876
|
+
return output(ok({ action: "config", path: path20.join(configDir(), "config.json"), ...next }));
|
|
28877
|
+
}
|
|
28878
|
+
const env = Object.fromEntries(Object.entries({
|
|
28879
|
+
WORKFLOW_YT_BASE_URL: args.base_url,
|
|
28880
|
+
WORKFLOW_YT_MENTION: args.default_mention,
|
|
28881
|
+
WORKFLOW_YT_MEETING_ISSUE: args.meeting_issue,
|
|
28882
|
+
WORKFLOW_VCS_PROVIDER: args.vcs_provider,
|
|
28883
|
+
WORKFLOW_VCS_TARGET_BRANCH: args.vcs_target_branch,
|
|
28884
|
+
WORKFLOW_WORKSPACE_ROOT: directory,
|
|
28885
|
+
WORKFLOW_BP_NAME: args.name,
|
|
28886
|
+
WORKFLOW_BP_DEVELOP: args.develop_branch,
|
|
28887
|
+
WORKFLOW_BP_INTEGRATION: args.integration
|
|
28888
|
+
}).filter((entry) => entry[1] !== undefined));
|
|
28889
|
+
return output(legacyScriptResult(runtime.initApply(directory, action, env)));
|
|
28890
|
+
};
|
|
28891
|
+
|
|
28892
|
+
// packages/workit-opencode/src/tools/repo.ts
|
|
28893
|
+
function createRepoTools(runtime = initApplyRuntime) {
|
|
29318
28894
|
return {
|
|
29319
|
-
|
|
29320
|
-
|
|
29321
|
-
|
|
29322
|
-
|
|
29323
|
-
|
|
29324
|
-
|
|
29325
|
-
|
|
28895
|
+
workit_init_apply: tool({
|
|
28896
|
+
description: "Apply a confirmed toolkit initialization action",
|
|
28897
|
+
args: {
|
|
28898
|
+
confirmed: tool.schema.boolean(),
|
|
28899
|
+
action: tool.schema.enum([
|
|
28900
|
+
"youtrack_scaffold",
|
|
28901
|
+
"youtrack_json",
|
|
28902
|
+
"youtrack_token_placeholder",
|
|
28903
|
+
"vcs_scaffold",
|
|
28904
|
+
"config",
|
|
28905
|
+
"gitignore",
|
|
28906
|
+
"hygiene",
|
|
28907
|
+
"branch_policy"
|
|
28908
|
+
]),
|
|
28909
|
+
base_url: tool.schema.string().optional(),
|
|
28910
|
+
default_mention: tool.schema.string().optional(),
|
|
28911
|
+
meeting_issue: tool.schema.string().optional(),
|
|
28912
|
+
vcs_provider: tool.schema.enum(["gitlab", "github"]).optional(),
|
|
28913
|
+
vcs_target_branch: tool.schema.string().optional(),
|
|
28914
|
+
name: tool.schema.string().optional(),
|
|
28915
|
+
develop_branch: tool.schema.string().optional(),
|
|
28916
|
+
integration: tool.schema.enum(["pr", "merge"]).optional(),
|
|
28917
|
+
locale: tool.schema.string().optional(),
|
|
28918
|
+
locale_options: tool.schema.array(tool.schema.string()).optional(),
|
|
28919
|
+
timezone: tool.schema.string().optional(),
|
|
28920
|
+
branch_policy_preset: tool.schema.enum(["gitflow", "github-flow", "trunk-based", "custom"]).optional(),
|
|
28921
|
+
branch_policy_allowed: tool.schema.array(tool.schema.string()).optional(),
|
|
28922
|
+
branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
|
|
28923
|
+
include_open_source: tool.schema.boolean().optional()
|
|
28924
|
+
},
|
|
28925
|
+
execute: async (args, context) => executeInitApply(args, context.directory, runtime)
|
|
28926
|
+
})
|
|
29326
28927
|
};
|
|
29327
28928
|
}
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
|
|
29333
|
-
|
|
29334
|
-
|
|
29335
|
-
|
|
28929
|
+
|
|
28930
|
+
// packages/workit-opencode/src/tools/context.ts
|
|
28931
|
+
import { realpathSync as realpathSync10 } from "node:fs";
|
|
28932
|
+
import path23 from "node:path";
|
|
28933
|
+
|
|
28934
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
28935
|
+
import fs10 from "node:fs";
|
|
28936
|
+
import path21 from "node:path";
|
|
28937
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
28938
|
+
function parseGhIssue(value) {
|
|
28939
|
+
const m = /issues\/(\d+)/.exec(value);
|
|
28940
|
+
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
28941
|
+
}
|
|
28942
|
+
function whichOnPath(tool) {
|
|
28943
|
+
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
28944
|
+
for (const dir of (process.env.PATH ?? "").split(path21.delimiter)) {
|
|
28945
|
+
if (!dir)
|
|
28946
|
+
continue;
|
|
28947
|
+
for (const name of names) {
|
|
28948
|
+
const candidate = path21.join(dir, name);
|
|
28949
|
+
try {
|
|
28950
|
+
fs10.accessSync(candidate, fs10.constants.X_OK);
|
|
28951
|
+
return candidate;
|
|
28952
|
+
} catch {}
|
|
28953
|
+
}
|
|
28954
|
+
}
|
|
28955
|
+
return null;
|
|
28956
|
+
}
|
|
28957
|
+
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
28958
|
+
var pushRemote = (cwd) => {
|
|
28959
|
+
const result = spawnSync6("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
28960
|
+
cwd,
|
|
28961
|
+
encoding: "utf8"
|
|
28962
|
+
});
|
|
28963
|
+
if (result.status !== 0)
|
|
28964
|
+
return null;
|
|
28965
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
28966
|
+
return urls.length === 1 ? urls[0] : null;
|
|
28967
|
+
};
|
|
28968
|
+
|
|
28969
|
+
// packages/workit-core/src/core/repo-context.ts
|
|
28970
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
28971
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync7 } from "node:fs";
|
|
28972
|
+
import path22 from "node:path";
|
|
28973
|
+
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
29336
28974
|
try {
|
|
29337
|
-
|
|
29338
|
-
|
|
29339
|
-
const stateField = customFields.find((field) => typeof field === "object" && field !== null && field.name === "State");
|
|
29340
|
-
const stateValue = stateField?.value;
|
|
29341
|
-
const stateName = typeof stateValue === "object" && stateValue !== null ? stateValue.name : undefined;
|
|
29342
|
-
return {
|
|
29343
|
-
data: {
|
|
29344
|
-
idReadable: String(parsed.idReadable ?? issue),
|
|
29345
|
-
summary: String(parsed.summary ?? ""),
|
|
29346
|
-
description: typeof parsed.description === "string" ? parsed.description : null,
|
|
29347
|
-
state: typeof stateValue === "string" ? stateValue : typeof stateName === "string" && stateName ? stateName : null
|
|
29348
|
-
}
|
|
29349
|
-
};
|
|
28975
|
+
gitRevisionParts(value);
|
|
28976
|
+
return true;
|
|
29350
28977
|
} catch {
|
|
29351
|
-
return
|
|
28978
|
+
return false;
|
|
28979
|
+
}
|
|
28980
|
+
})();
|
|
28981
|
+
var isResolvableContextRange = (cwd, value) => {
|
|
28982
|
+
if (!isSafeContextRange(value))
|
|
28983
|
+
return false;
|
|
28984
|
+
try {
|
|
28985
|
+
resolveGitRevision(cwd, value);
|
|
28986
|
+
return true;
|
|
28987
|
+
} catch {
|
|
28988
|
+
return false;
|
|
29352
28989
|
}
|
|
29353
28990
|
};
|
|
29354
|
-
|
|
29355
|
-
|
|
29356
|
-
|
|
29357
|
-
|
|
29358
|
-
|
|
29359
|
-
|
|
29360
|
-
|
|
29361
|
-
|
|
29362
|
-
|
|
28991
|
+
var invalidRange = (cwd) => ({
|
|
28992
|
+
stdout: "",
|
|
28993
|
+
stderr: `ERROR: invalid revision range
|
|
28994
|
+
`,
|
|
28995
|
+
exitCode: 2,
|
|
28996
|
+
cwd
|
|
28997
|
+
});
|
|
28998
|
+
var runGit = (cwd, args) => {
|
|
28999
|
+
const result = spawnSync7("git", args, { cwd, encoding: "utf8" });
|
|
29000
|
+
return {
|
|
29001
|
+
stdout: (result.stdout ?? "").trimEnd(),
|
|
29002
|
+
stderr: (result.stderr ?? "").trimEnd(),
|
|
29003
|
+
exitCode: result.status ?? 1
|
|
29004
|
+
};
|
|
29005
|
+
};
|
|
29006
|
+
var repoRoot3 = (cwd) => {
|
|
29007
|
+
const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
29008
|
+
return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
|
|
29009
|
+
};
|
|
29010
|
+
var currentBranch = (cwd) => {
|
|
29011
|
+
const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
29012
|
+
return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
|
|
29013
|
+
};
|
|
29014
|
+
var defaultBase = (cwd) => {
|
|
29015
|
+
for (const ref of [
|
|
29016
|
+
"origin/main",
|
|
29017
|
+
"main",
|
|
29018
|
+
"origin/master",
|
|
29019
|
+
"master",
|
|
29020
|
+
"origin/develop",
|
|
29021
|
+
"develop"
|
|
29022
|
+
]) {
|
|
29023
|
+
if (runGit(cwd, ["rev-parse", "--verify", ref]).exitCode === 0)
|
|
29024
|
+
return ref;
|
|
29363
29025
|
}
|
|
29026
|
+
return "HEAD~1";
|
|
29364
29027
|
};
|
|
29365
|
-
var
|
|
29366
|
-
var
|
|
29367
|
-
var
|
|
29368
|
-
|
|
29369
|
-
|
|
29370
|
-
|
|
29371
|
-
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
cwd: root,
|
|
29375
|
-
encoding: "utf8"
|
|
29376
|
-
});
|
|
29377
|
-
if (out.status === 0) {
|
|
29378
|
-
const url = (out.stdout ?? "").trim();
|
|
29379
|
-
if (/github\.com[:/]/.test(url))
|
|
29380
|
-
return "github";
|
|
29381
|
-
if (/gitlab\.com[:/]/.test(url))
|
|
29382
|
-
return "gitlab";
|
|
29383
|
-
}
|
|
29384
|
-
} catch {}
|
|
29385
|
-
return null;
|
|
29386
|
-
};
|
|
29387
|
-
var resolvePath = (p) => {
|
|
29388
|
-
try {
|
|
29389
|
-
return fs9.realpathSync(p);
|
|
29390
|
-
} catch {
|
|
29391
|
-
return path21.resolve(p);
|
|
29392
|
-
}
|
|
29393
|
-
};
|
|
29394
|
-
function initStatusData(configDirPath = configDir()) {
|
|
29395
|
-
const ytJson = path21.join(configDirPath, "youtrack.json");
|
|
29396
|
-
const vcsJson = path21.join(configDirPath, "vcs.json");
|
|
29397
|
-
const items = [];
|
|
29398
|
-
let youtrackConfig = null;
|
|
29399
|
-
let youtrackTokenCreate = null;
|
|
29400
|
-
const ytParsed = readJson(ytJson);
|
|
29401
|
-
if (ytParsed) {
|
|
29402
|
-
const base = String(ytParsed.baseUrl ?? "").replace(/\/+$/, "");
|
|
29403
|
-
const meeting = String(ytParsed.meetingIssue ?? "");
|
|
29404
|
-
const tokenFile = String(ytParsed.tokenFile ?? "");
|
|
29405
|
-
const meetingIssues = [];
|
|
29406
|
-
const rawMeetings = ytParsed.meetingIssues ?? {};
|
|
29407
|
-
if (rawMeetings && typeof rawMeetings === "object") {
|
|
29408
|
-
for (const [key, item] of Object.entries(rawMeetings)) {
|
|
29409
|
-
const iss = String(item?.issue ?? "");
|
|
29410
|
-
meetingIssues.push({
|
|
29411
|
-
key,
|
|
29412
|
-
issue: iss,
|
|
29413
|
-
label: item?.label ?? iss,
|
|
29414
|
-
workItemText: item?.workItemText ?? "Reuniones",
|
|
29415
|
-
url: item?.url ?? (base && iss ? `${base}/issue/${iss}` : null)
|
|
29416
|
-
});
|
|
29417
|
-
}
|
|
29418
|
-
}
|
|
29419
|
-
const expanded = tokenFile ? path21.resolve(tokenFile) : null;
|
|
29420
|
-
const resolvedTokenFile = expanded && fs9.existsSync(expanded) ? resolvePath(expanded) : expanded ? path21.isAbsolute(expanded) ? expanded : path21.resolve(configDirPath, expanded) : null;
|
|
29421
|
-
youtrackConfig = {
|
|
29422
|
-
config_edit_path: resolvePath(ytJson),
|
|
29423
|
-
baseUrl: base,
|
|
29424
|
-
meetingIssue: meeting,
|
|
29425
|
-
meetingIssues,
|
|
29426
|
-
meetingIssueUrl: base && meeting ? `${base}/issue/${meeting}` : null,
|
|
29427
|
-
defaultMention: ytParsed.defaultMention,
|
|
29428
|
-
timezone: ytParsed.timezone,
|
|
29429
|
-
locale: ytParsed.locale,
|
|
29430
|
-
tokenFile: resolvedTokenFile,
|
|
29431
|
-
tokenDefaults: ytParsed.tokenDefaults,
|
|
29432
|
-
timeLogging: {
|
|
29433
|
-
meetings: {
|
|
29434
|
-
options: meetingIssues,
|
|
29435
|
-
skill: "/wk-meetings",
|
|
29436
|
-
logsTime: true,
|
|
29437
|
-
postsComment: false
|
|
29438
|
-
},
|
|
29439
|
-
taskWork: {
|
|
29440
|
-
issueSource: "active spec/plan **YouTrack:** field or --issue",
|
|
29441
|
-
skill: "/wk-issue-update",
|
|
29442
|
-
logsTime: true,
|
|
29443
|
-
postsComment: true
|
|
29444
|
-
}
|
|
29445
|
-
}
|
|
29028
|
+
var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase(cwd)}...HEAD`;
|
|
29029
|
+
var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
|
|
29030
|
+
var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
|
|
29031
|
+
function resolvePrBranchContext(cwd) {
|
|
29032
|
+
const branch = currentBranch(cwd);
|
|
29033
|
+
if (isProtectedBranch(branch)) {
|
|
29034
|
+
return {
|
|
29035
|
+
ok: false,
|
|
29036
|
+
error: `cannot build PR context on protected branch ${branch} — PRs are for feature/* or bugfix/* only`
|
|
29446
29037
|
};
|
|
29447
|
-
|
|
29448
|
-
|
|
29449
|
-
|
|
29450
|
-
|
|
29451
|
-
|
|
29452
|
-
|
|
29453
|
-
|
|
29454
|
-
JSON.parse(fs9.readFileSync(ytJson, "utf8"));
|
|
29455
|
-
} catch {
|
|
29456
|
-
parseFailed = true;
|
|
29038
|
+
}
|
|
29039
|
+
if (!isPrBranch(branch)) {
|
|
29040
|
+
if (branch === "unknown") {
|
|
29041
|
+
return {
|
|
29042
|
+
ok: false,
|
|
29043
|
+
error: `not in a git repository at ${repoRoot3(cwd)} — open the target repository as the OpenCode session directory`
|
|
29044
|
+
};
|
|
29457
29045
|
}
|
|
29458
|
-
|
|
29459
|
-
|
|
29460
|
-
error:
|
|
29046
|
+
return {
|
|
29047
|
+
ok: false,
|
|
29048
|
+
error: `branch ${branch} is not feature/* or bugfix/* — checkout a feature branch or pass an explicit git range`
|
|
29461
29049
|
};
|
|
29462
29050
|
}
|
|
29463
|
-
|
|
29464
|
-
|
|
29465
|
-
|
|
29466
|
-
|
|
29467
|
-
|
|
29468
|
-
|
|
29469
|
-
|
|
29470
|
-
}
|
|
29471
|
-
const
|
|
29472
|
-
|
|
29473
|
-
|
|
29474
|
-
|
|
29475
|
-
|
|
29476
|
-
|
|
29477
|
-
|
|
29478
|
-
|
|
29479
|
-
|
|
29480
|
-
|
|
29481
|
-
|
|
29482
|
-
|
|
29483
|
-
|
|
29484
|
-
|
|
29485
|
-
if (youtrackTokenCreate.createUrl)
|
|
29486
|
-
youtrackTokenItem.token_create_url = youtrackTokenCreate.createUrl;
|
|
29487
|
-
if (youtrackTokenCreate.docsUrl)
|
|
29488
|
-
youtrackTokenItem.token_create_docs_url = youtrackTokenCreate.docsUrl;
|
|
29489
|
-
if (youtrackTokenCreate.scopes)
|
|
29490
|
-
youtrackTokenItem.token_scopes = youtrackTokenCreate.scopes;
|
|
29491
|
-
if (youtrackTokenCreate.tokenName)
|
|
29492
|
-
youtrackTokenItem.token_name = youtrackTokenCreate.tokenName;
|
|
29493
|
-
if (youtrackTokenCreate.steps)
|
|
29494
|
-
youtrackTokenItem.token_create_steps = youtrackTokenCreate.steps;
|
|
29495
|
-
youtrackTokenItem.token_prefill_supported = youtrackTokenCreate.prefillSupported ?? false;
|
|
29496
|
-
}
|
|
29497
|
-
items.push(youtrackTokenItem);
|
|
29498
|
-
let vcsCfg = null;
|
|
29499
|
-
const vcsParsed = readJson(vcsJson);
|
|
29500
|
-
if (vcsParsed) {
|
|
29501
|
-
const rawProvider = vcsParsed.provider;
|
|
29502
|
-
const effective = vcsConfig("resolve", process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd());
|
|
29503
|
-
const provider = effective?.ok && typeof effective.provider === "string" ? effective.provider : typeof rawProvider === "string" && rawProvider.trim() ? rawProvider.toLowerCase() : null;
|
|
29504
|
-
vcsCfg = {
|
|
29505
|
-
config_edit_path: resolvePath(vcsJson),
|
|
29506
|
-
provider,
|
|
29507
|
-
defaultTargetBranch: effective.ok ? effective.defaultTargetBranch : vcsParsed.defaultTargetBranch ?? "develop",
|
|
29508
|
-
pr: vcsParsed.pr ?? {},
|
|
29509
|
-
gitlab: vcsParsed.gitlab,
|
|
29510
|
-
github: vcsParsed.github,
|
|
29511
|
-
skill: "/wk-pr",
|
|
29512
|
-
switchHint: 'Set "provider" to "gitlab" or "github" in vcs.json'
|
|
29051
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
29052
|
+
if (resolved.ok === false) {
|
|
29053
|
+
return { ok: false, error: String(resolved.error) };
|
|
29054
|
+
}
|
|
29055
|
+
const base = String(resolved.defaultTargetBranch ?? "");
|
|
29056
|
+
if (!base || runGit(cwd, ["check-ref-format", "--branch", base]).exitCode !== 0) {
|
|
29057
|
+
return { ok: false, error: `invalid configured PR target branch ${base}` };
|
|
29058
|
+
}
|
|
29059
|
+
for (const ref of [`origin/${base}`, base]) {
|
|
29060
|
+
if (runGit(cwd, ["rev-parse", "--verify", ref]).exitCode !== 0)
|
|
29061
|
+
continue;
|
|
29062
|
+
const mb = runGit(cwd, ["merge-base", ref, "HEAD"]);
|
|
29063
|
+
if (mb.exitCode !== 0 || !mb.stdout)
|
|
29064
|
+
continue;
|
|
29065
|
+
return {
|
|
29066
|
+
ok: true,
|
|
29067
|
+
value: {
|
|
29068
|
+
baseRef: ref,
|
|
29069
|
+
mergeBase: mb.stdout,
|
|
29070
|
+
range: `${ref}..HEAD`,
|
|
29071
|
+
diffRange: `${mb.stdout}..HEAD`
|
|
29072
|
+
}
|
|
29513
29073
|
};
|
|
29514
|
-
} else if (fs9.existsSync(vcsJson)) {
|
|
29515
|
-
vcsCfg = { config_edit_path: resolvePath(vcsJson), error: "invalid vcs.json" };
|
|
29516
29074
|
}
|
|
29517
|
-
items.push({
|
|
29518
|
-
id: "vcs_json",
|
|
29519
|
-
label: "VCS config (GitLab / GitHub)",
|
|
29520
|
-
ok: fs9.existsSync(vcsJson) && vcsCfg !== null && !("error" in vcsCfg),
|
|
29521
|
-
path: fs9.existsSync(vcsJson) ? resolvePath(vcsJson) : path21.resolve(vcsJson),
|
|
29522
|
-
config_edit_path: resolvePath(vcsJson),
|
|
29523
|
-
fix: "workit_init_apply action=vcs_scaffold"
|
|
29524
|
-
});
|
|
29525
29075
|
return {
|
|
29526
|
-
config_dir: configDirPath,
|
|
29527
|
-
plugin_root: PLUGIN_ROOT,
|
|
29528
|
-
token_placeholder: TOKEN_PLACEHOLDER2,
|
|
29529
|
-
youtrack_config: youtrackConfig,
|
|
29530
|
-
vcs_config: vcsCfg,
|
|
29531
|
-
items,
|
|
29532
|
-
ready: false
|
|
29533
|
-
};
|
|
29534
|
-
}
|
|
29535
|
-
async function toolkitStatusData(configDirPath = configDir()) {
|
|
29536
|
-
const status = initStatusData(configDirPath);
|
|
29537
|
-
const tokenItem = status.items.find((i) => i.id === "youtrack_token") ?? {};
|
|
29538
|
-
const placeholder = Boolean(tokenItem.placeholder);
|
|
29539
|
-
const vcsCfg = status.vcs_config ?? {};
|
|
29540
|
-
const provider = typeof vcsCfg.provider === "string" && vcsCfg.provider.trim() ? vcsCfg.provider.toLowerCase() : null;
|
|
29541
|
-
const vcsJsonOk = Boolean(status.items.find((i) => i.id === "vcs_json")?.ok);
|
|
29542
|
-
const verify = placeholder ? { ok: false, error: "token still placeholder YOUR_TOKEN_HERE" } : await youTrackVerifyToken();
|
|
29543
|
-
const vcsVerify = provider === null ? {
|
|
29544
29076
|
ok: false,
|
|
29545
|
-
error:
|
|
29546
|
-
}
|
|
29547
|
-
const youTrackHealth = "data" in verify ? verify.data : verify;
|
|
29548
|
-
status.youtrack_verify = verify;
|
|
29549
|
-
status.youtrack_ok = placeholder ? false : Boolean(youTrackHealth.ok);
|
|
29550
|
-
status.vcs_verify = vcsVerify;
|
|
29551
|
-
status.vcs_ok = vcsJsonOk && Boolean(vcsVerify.ok);
|
|
29552
|
-
const fsReady = status.items.every((i) => i.required === false || i.ok);
|
|
29553
|
-
status.ready = fsReady && Boolean(status.youtrack_ok) && (!vcsJsonOk || Boolean(status.vcs_ok));
|
|
29554
|
-
if (placeholder) {
|
|
29555
|
-
const tokenPath = tokenItem.token_edit_path ?? tokenItem.path ?? "";
|
|
29556
|
-
const createUrl = tokenItem.token_create_url ?? status.youtrack_config?.tokenCreate?.createUrl;
|
|
29557
|
-
status.token_edit_path = tokenPath;
|
|
29558
|
-
if (createUrl)
|
|
29559
|
-
status.token_create_url = createUrl;
|
|
29560
|
-
status.next_step = createUrl ? "Open the YouTrack create-token URL, New token (name workit, scope YouTrack), paste into the token file, save, then run /wk-status" : "Open the YouTrack token file, replace YOUR_TOKEN_HERE, save, then run /wk-status";
|
|
29561
|
-
} else if (!status.youtrack_ok) {
|
|
29562
|
-
status.next_step = "Fix YouTrack token or re-run /wk-init";
|
|
29563
|
-
} else if (vcsJsonOk && !status.vcs_ok) {
|
|
29564
|
-
status.next_step = `Run ${provider === "gitlab" ? "glab" : "gh"} auth login/switch, then /wk-status`;
|
|
29565
|
-
} else if (status.ready) {
|
|
29566
|
-
status.next_step = "All checks passed";
|
|
29567
|
-
}
|
|
29568
|
-
return status;
|
|
29077
|
+
error: `configured PR target branch ${base} not found — fetch/checkout it or pass an explicit git range`
|
|
29078
|
+
};
|
|
29569
29079
|
}
|
|
29570
|
-
var
|
|
29571
|
-
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
|
|
29577
|
-
|
|
29578
|
-
|
|
29579
|
-
meetingIssues: {
|
|
29580
|
-
general: {
|
|
29581
|
-
issue: process.env.WORKFLOW_YT_MEETING_ISSUE ?? "IRPT-12",
|
|
29582
|
-
label: "General meetings (Reuniones internas Team IRP)",
|
|
29583
|
-
workItemText: "Reuniones"
|
|
29584
|
-
},
|
|
29585
|
-
web: {
|
|
29586
|
-
issue: process.env.WORKFLOW_YT_WEB_MEETING_ISSUE ?? "NSXFT-21",
|
|
29587
|
-
label: "Web meetings",
|
|
29588
|
-
workItemText: "Reuniones web",
|
|
29589
|
-
url: "https://enghouseamg.youtrack.cloud/projects/NSXFT/issues/NSXFT-21"
|
|
29080
|
+
var printSection = (title) => `
|
|
29081
|
+
## ${title}
|
|
29082
|
+
|
|
29083
|
+
`;
|
|
29084
|
+
var probeCaseInsensitive = (dir, wanted) => {
|
|
29085
|
+
try {
|
|
29086
|
+
for (const entry of readdirSync7(dir)) {
|
|
29087
|
+
if (entry.toLowerCase() === wanted.toLowerCase())
|
|
29088
|
+
return entry;
|
|
29590
29089
|
}
|
|
29591
|
-
}
|
|
29592
|
-
|
|
29593
|
-
|
|
29594
|
-
|
|
29595
|
-
|
|
29596
|
-
|
|
29597
|
-
|
|
29598
|
-
|
|
29599
|
-
|
|
29600
|
-
|
|
29090
|
+
} catch {}
|
|
29091
|
+
return null;
|
|
29092
|
+
};
|
|
29093
|
+
var findPrTemplate = (cwd) => {
|
|
29094
|
+
for (const rel of [
|
|
29095
|
+
".gitlab/merge_request_templates/Default.md",
|
|
29096
|
+
".gitlab/merge_request_templates/default.md",
|
|
29097
|
+
".gitlab/merge_request_templates/merge_request_template.md",
|
|
29098
|
+
".github/PULL_REQUEST_TEMPLATE.md",
|
|
29099
|
+
".github/pull_request_template.md",
|
|
29100
|
+
".github/PULL_REQUEST_TEMPLATE/pull_request_template.md",
|
|
29101
|
+
"docs/PULL_REQUEST_TEMPLATE.md",
|
|
29102
|
+
"PULL_REQUEST_TEMPLATE.md"
|
|
29103
|
+
]) {
|
|
29104
|
+
const parts = rel.split("/");
|
|
29105
|
+
const base = parts[parts.length - 1];
|
|
29106
|
+
const dir = parts.length > 1 ? path22.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
29107
|
+
const actual = probeCaseInsensitive(dir, base);
|
|
29108
|
+
if (actual)
|
|
29109
|
+
return [...parts.slice(0, -1), actual].join("/");
|
|
29601
29110
|
}
|
|
29602
|
-
|
|
29603
|
-
var vcsJsonContent = () => {
|
|
29604
|
-
const provider = resolveInitProvider();
|
|
29605
|
-
return {
|
|
29606
|
-
...provider ? { provider } : {},
|
|
29607
|
-
defaultTargetBranch: process.env.WORKFLOW_VCS_TARGET_BRANCH ?? "develop",
|
|
29608
|
-
gitlab: {
|
|
29609
|
-
host: process.env.WORKFLOW_GITLAB_HOST ?? "gitlab.com",
|
|
29610
|
-
apiUrl: process.env.WORKFLOW_GITLAB_API_URL ?? "https://gitlab.com/api/v4"
|
|
29611
|
-
},
|
|
29612
|
-
github: {
|
|
29613
|
-
host: process.env.WORKFLOW_GITHUB_HOST ?? "github.com"
|
|
29614
|
-
},
|
|
29615
|
-
pr: { squashOnMerge: true, removeSourceBranch: true, pushBranch: true, confirmSkip: true }
|
|
29616
|
-
};
|
|
29111
|
+
return null;
|
|
29617
29112
|
};
|
|
29618
|
-
|
|
29619
|
-
|
|
29620
|
-
|
|
29621
|
-
|
|
29622
|
-
|
|
29623
|
-
|
|
29624
|
-
|
|
29625
|
-
|
|
29626
|
-
|
|
29627
|
-
|
|
29628
|
-
|
|
29629
|
-
|
|
29630
|
-
|
|
29631
|
-
|
|
29632
|
-
|
|
29633
|
-
|
|
29634
|
-
|
|
29635
|
-
|
|
29636
|
-
|
|
29637
|
-
|
|
29638
|
-
|
|
29639
|
-
|
|
29640
|
-
|
|
29641
|
-
|
|
29642
|
-
|
|
29643
|
-
|
|
29644
|
-
|
|
29645
|
-
|
|
29646
|
-
|
|
29647
|
-
|
|
29648
|
-
|
|
29649
|
-
|
|
29650
|
-
|
|
29651
|
-
|
|
29652
|
-
|
|
29653
|
-
|
|
29654
|
-
|
|
29655
|
-
|
|
29656
|
-
|
|
29657
|
-
|
|
29658
|
-
|
|
29659
|
-
|
|
29660
|
-
|
|
29661
|
-
|
|
29662
|
-
|
|
29663
|
-
youtrack_json: configPath,
|
|
29664
|
-
youtrack_token: tokenPath,
|
|
29665
|
-
token_edit_path: tokenPath,
|
|
29666
|
-
token_create_url: tokenCreate.createUrl,
|
|
29667
|
-
token_create: tokenCreate,
|
|
29668
|
-
config_edit_path: configPath,
|
|
29669
|
-
placeholder: TOKEN_PLACEHOLDER2,
|
|
29670
|
-
preserved,
|
|
29671
|
-
youtrack_config: {
|
|
29672
|
-
config_edit_path: configPath,
|
|
29673
|
-
baseUrl: base,
|
|
29674
|
-
meetingIssue: meeting,
|
|
29675
|
-
meetingIssueUrl: base && meeting ? `${base}/issue/${meeting}` : null,
|
|
29676
|
-
defaultMention: cfg.defaultMention,
|
|
29677
|
-
timezone: cfg.timezone,
|
|
29678
|
-
locale: cfg.locale,
|
|
29679
|
-
tokenCreate,
|
|
29680
|
-
timeLogging: {
|
|
29681
|
-
meetings: {
|
|
29682
|
-
issue: meeting,
|
|
29683
|
-
skill: "/wk-meetings",
|
|
29684
|
-
logsTime: true,
|
|
29685
|
-
postsComment: false
|
|
29686
|
-
},
|
|
29687
|
-
taskWork: {
|
|
29688
|
-
issueSource: "active spec/plan **YouTrack:** field or --issue",
|
|
29689
|
-
skill: "/wk-issue-update",
|
|
29690
|
-
logsTime: true,
|
|
29691
|
-
postsComment: true
|
|
29692
|
-
}
|
|
29693
|
-
}
|
|
29694
|
-
},
|
|
29695
|
-
instruction: `Open the create-token URL, New token → name workit, scope YouTrack, paste into ${tokenPath}, then /wk-status.`
|
|
29696
|
-
};
|
|
29697
|
-
} finally {
|
|
29698
|
-
if (prev === undefined)
|
|
29699
|
-
delete process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
29700
|
-
else
|
|
29701
|
-
process.env.WORKFLOW_YOUTRACK_CONFIG = prev;
|
|
29702
|
-
}
|
|
29113
|
+
var fallbackPrTemplate = () => `## Summary
|
|
29114
|
+
-
|
|
29115
|
+
|
|
29116
|
+
## Validation
|
|
29117
|
+
- [ ] Not run`;
|
|
29118
|
+
var commitLogForRange = (cwd, range) => {
|
|
29119
|
+
const r = runGit(cwd, ["log", "--oneline", "--decorate", "--no-merges", range, "--"]);
|
|
29120
|
+
if (r.exitCode === 0)
|
|
29121
|
+
return r.stdout;
|
|
29122
|
+
return runGit(cwd, ["log", "--oneline", "--decorate", "-10", "--"]).stdout;
|
|
29123
|
+
};
|
|
29124
|
+
var changedFilesForRange = (cwd, range) => {
|
|
29125
|
+
const r = runGit(cwd, ["diff", "--name-only", range, "--"]);
|
|
29126
|
+
if (r.exitCode === 0)
|
|
29127
|
+
return r.stdout;
|
|
29128
|
+
return runGit(cwd, ["diff", "--name-only", "--"]).stdout;
|
|
29129
|
+
};
|
|
29130
|
+
var diffStatForRange = (cwd, range) => {
|
|
29131
|
+
const r = runGit(cwd, ["diff", "--stat", range, "--"]);
|
|
29132
|
+
if (r.exitCode === 0)
|
|
29133
|
+
return r.stdout;
|
|
29134
|
+
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
29135
|
+
};
|
|
29136
|
+
var packageScripts = (cwd, keys) => {
|
|
29137
|
+
const pkgPath = path22.join(cwd, "package.json");
|
|
29138
|
+
if (!existsSync11(pkgPath))
|
|
29139
|
+
return [];
|
|
29140
|
+
try {
|
|
29141
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
29142
|
+
return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
|
|
29143
|
+
} catch {
|
|
29144
|
+
return [];
|
|
29145
|
+
}
|
|
29146
|
+
};
|
|
29147
|
+
var documentationFiles = (cwd) => {
|
|
29148
|
+
const excluded = new Set([".git", "node_modules", "target", "dist"]);
|
|
29149
|
+
const matches = [];
|
|
29150
|
+
const walk = (dir, depth) => {
|
|
29151
|
+
if (depth > 3)
|
|
29152
|
+
return;
|
|
29153
|
+
let entries;
|
|
29154
|
+
try {
|
|
29155
|
+
entries = readdirSync7(dir, { withFileTypes: true });
|
|
29156
|
+
} catch {
|
|
29157
|
+
return;
|
|
29703
29158
|
}
|
|
29704
|
-
|
|
29705
|
-
|
|
29706
|
-
|
|
29707
|
-
|
|
29708
|
-
|
|
29709
|
-
|
|
29710
|
-
|
|
29711
|
-
try {
|
|
29712
|
-
const cfg = readJson(configPath) ?? {};
|
|
29713
|
-
const rawProvider = cfg.provider;
|
|
29714
|
-
const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider : null;
|
|
29715
|
-
return {
|
|
29716
|
-
action,
|
|
29717
|
-
ok: true,
|
|
29718
|
-
vcs_json: configPath,
|
|
29719
|
-
config_edit_path: configPath,
|
|
29720
|
-
vcs_config: {
|
|
29721
|
-
config_edit_path: configPath,
|
|
29722
|
-
provider,
|
|
29723
|
-
defaultTargetBranch: cfg.defaultTargetBranch,
|
|
29724
|
-
pr: cfg.pr,
|
|
29725
|
-
switchHint: 'Change "provider" to "github" when you migrate; authenticate with gh/glab',
|
|
29726
|
-
skill: "/wk-pr"
|
|
29727
|
-
},
|
|
29728
|
-
instruction: provider === null ? `No provider resolved — set provider to gitlab or github in ${configPath}, then /wk-status.` : `Run ${provider === "gitlab" ? "glab" : "gh"} auth login, then /wk-status.`
|
|
29729
|
-
};
|
|
29730
|
-
} finally {
|
|
29731
|
-
if (prev === undefined)
|
|
29732
|
-
delete process.env.WORKFLOW_VCS_CONFIG;
|
|
29733
|
-
else
|
|
29734
|
-
process.env.WORKFLOW_VCS_CONFIG = prev;
|
|
29159
|
+
for (const entry of entries) {
|
|
29160
|
+
if (entry.isDirectory()) {
|
|
29161
|
+
if (excluded.has(entry.name))
|
|
29162
|
+
continue;
|
|
29163
|
+
walk(path22.join(dir, entry.name), depth + 1);
|
|
29164
|
+
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
29165
|
+
matches.push("./" + path22.relative(cwd, path22.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
29735
29166
|
}
|
|
29736
29167
|
}
|
|
29737
|
-
|
|
29738
|
-
|
|
29739
|
-
|
|
29740
|
-
|
|
29741
|
-
|
|
29742
|
-
|
|
29743
|
-
|
|
29744
|
-
|
|
29745
|
-
|
|
29168
|
+
};
|
|
29169
|
+
walk(cwd, 1);
|
|
29170
|
+
return matches.sort().slice(0, 200);
|
|
29171
|
+
};
|
|
29172
|
+
var readTrimmed = (file, maxLines) => {
|
|
29173
|
+
if (!existsSync11(file))
|
|
29174
|
+
return "";
|
|
29175
|
+
const lines = readFileSync10(file, "utf8").split(`
|
|
29176
|
+
`);
|
|
29177
|
+
return lines.slice(0, maxLines).join(`
|
|
29178
|
+
`);
|
|
29179
|
+
};
|
|
29180
|
+
function prReadyContext(root, range) {
|
|
29181
|
+
const cwd = path22.resolve(repoRoot3(root));
|
|
29182
|
+
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
29183
|
+
return invalidRange(cwd);
|
|
29184
|
+
let stdout = "";
|
|
29185
|
+
const autoRange = range === undefined;
|
|
29186
|
+
let ctx = null;
|
|
29187
|
+
if (autoRange) {
|
|
29188
|
+
ctx = resolvePrBranchContext(cwd);
|
|
29189
|
+
if (!ctx.ok)
|
|
29190
|
+
return { stdout: "", stderr: `ERROR: ${ctx.error}
|
|
29191
|
+
`, exitCode: 1, cwd };
|
|
29192
|
+
range = ctx.value.range;
|
|
29746
29193
|
}
|
|
29747
|
-
|
|
29748
|
-
|
|
29749
|
-
|
|
29750
|
-
|
|
29751
|
-
|
|
29752
|
-
|
|
29753
|
-
|
|
29754
|
-
|
|
29755
|
-
|
|
29756
|
-
|
|
29194
|
+
stdout += printSection("Repository");
|
|
29195
|
+
stdout += `root: ${cwd}
|
|
29196
|
+
`;
|
|
29197
|
+
stdout += `branch: ${currentBranch(cwd)}
|
|
29198
|
+
`;
|
|
29199
|
+
stdout += `range: ${range}
|
|
29200
|
+
`;
|
|
29201
|
+
if (autoRange && ctx?.ok) {
|
|
29202
|
+
stdout += `base_ref: ${ctx.value.baseRef}
|
|
29203
|
+
`;
|
|
29204
|
+
stdout += `merge_base: ${ctx.value.mergeBase}
|
|
29205
|
+
`;
|
|
29206
|
+
stdout += `diff_range: ${ctx.value.diffRange}
|
|
29207
|
+
`;
|
|
29208
|
+
stdout += `range_mode: branch-exclusive
|
|
29209
|
+
`;
|
|
29210
|
+
if (process.env.PR_SYNC_NOTES)
|
|
29211
|
+
stdout += `git_sync: ${process.env.PR_SYNC_NOTES}
|
|
29212
|
+
`;
|
|
29213
|
+
}
|
|
29214
|
+
const diffRange = autoRange && ctx?.ok ? ctx.value.diffRange : range ?? "";
|
|
29215
|
+
stdout += printSection("Working Tree");
|
|
29216
|
+
const status = runGit(cwd, ["status", "--short"]);
|
|
29217
|
+
stdout += status.stdout + (status.stderr ? `
|
|
29218
|
+
${status.stderr}` : "");
|
|
29219
|
+
stdout += `
|
|
29220
|
+
`;
|
|
29221
|
+
stdout += printSection("Commits");
|
|
29222
|
+
stdout += commitLogForRange(cwd, range ?? "");
|
|
29223
|
+
stdout += `
|
|
29224
|
+
`;
|
|
29225
|
+
stdout += printSection("Diff Stat");
|
|
29226
|
+
stdout += diffStatForRange(cwd, diffRange);
|
|
29227
|
+
stdout += `
|
|
29228
|
+
`;
|
|
29229
|
+
stdout += printSection("Changed Files");
|
|
29230
|
+
stdout += changedFilesForRange(cwd, diffRange);
|
|
29231
|
+
stdout += `
|
|
29232
|
+
`;
|
|
29233
|
+
stdout += printSection("PR Template");
|
|
29234
|
+
const template = findPrTemplate(cwd);
|
|
29235
|
+
if (template) {
|
|
29236
|
+
stdout += `template_path: ${template}
|
|
29757
29237
|
|
|
29758
|
-
|
|
29759
|
-
|
|
29238
|
+
`;
|
|
29239
|
+
stdout += readTrimmed(path22.join(cwd, template), 220);
|
|
29240
|
+
} else {
|
|
29241
|
+
stdout += `template_path: none
|
|
29760
29242
|
|
|
29761
|
-
|
|
29762
|
-
|
|
29763
|
-
var diagnostics = ({ stdout, stderr, exitCode }) => ({
|
|
29764
|
-
stdout,
|
|
29765
|
-
stderr,
|
|
29766
|
-
exitCode
|
|
29767
|
-
});
|
|
29768
|
-
var requireConfirmed = (confirmed) => {
|
|
29769
|
-
if (confirmed === true)
|
|
29770
|
-
return null;
|
|
29771
|
-
return output(fail("confirmed: true required"));
|
|
29772
|
-
};
|
|
29773
|
-
function scriptResult(result, parse) {
|
|
29774
|
-
if (result.exitCode !== 0) {
|
|
29775
|
-
return fail(result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
|
|
29243
|
+
`;
|
|
29244
|
+
stdout += fallbackPrTemplate();
|
|
29776
29245
|
}
|
|
29777
|
-
|
|
29778
|
-
|
|
29779
|
-
|
|
29780
|
-
|
|
29781
|
-
|
|
29782
|
-
|
|
29783
|
-
|
|
29784
|
-
|
|
29246
|
+
stdout += `
|
|
29247
|
+
`;
|
|
29248
|
+
stdout += printSection("Recent Validation Signals");
|
|
29249
|
+
if (existsSync11(path22.join(cwd, "package.json"))) {
|
|
29250
|
+
stdout += `package.json detected. Common scripts:
|
|
29251
|
+
`;
|
|
29252
|
+
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
29253
|
+
if (found.length)
|
|
29254
|
+
stdout += found.join(`
|
|
29255
|
+
`) + `
|
|
29256
|
+
`;
|
|
29785
29257
|
}
|
|
29786
|
-
|
|
29787
|
-
|
|
29788
|
-
|
|
29789
|
-
try {
|
|
29790
|
-
parsed = JSON.parse(result.stdout.trim());
|
|
29791
|
-
} catch {}
|
|
29792
|
-
if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false) {
|
|
29793
|
-
const error = parsed?.error ? String(parsed.error) : parsed?.ok === false ? "legacy operation reported failure" : result.stderr.trim() || result.stdout.trim() || "workflow script failed";
|
|
29794
|
-
return fail(error, diagnostics(result));
|
|
29258
|
+
if (existsSync11(path22.join(cwd, "Cargo.toml")) || existsSync11(path22.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
29259
|
+
stdout += `Rust project detected.
|
|
29260
|
+
`;
|
|
29795
29261
|
}
|
|
29796
|
-
|
|
29797
|
-
|
|
29798
|
-
|
|
29799
|
-
|
|
29800
|
-
|
|
29801
|
-
|
|
29802
|
-
|
|
29803
|
-
|
|
29804
|
-
|
|
29805
|
-
|
|
29806
|
-
|
|
29807
|
-
|
|
29808
|
-
|
|
29809
|
-
|
|
29810
|
-
|
|
29811
|
-
|
|
29262
|
+
stdout += printSection("VCS Config");
|
|
29263
|
+
const vc = vcsConfig("resolve", cwd);
|
|
29264
|
+
if (vc.ok) {
|
|
29265
|
+
stdout += `workspace: ${String(vc.workspace_name ?? "none")}
|
|
29266
|
+
`;
|
|
29267
|
+
stdout += `provider: ${String(vc.provider ?? "unconfigured")}
|
|
29268
|
+
`;
|
|
29269
|
+
} else {
|
|
29270
|
+
stdout += `vcs: unreadable (malformed) — ${String(vc.error ?? "cannot read vcs.json")}
|
|
29271
|
+
`;
|
|
29272
|
+
}
|
|
29273
|
+
if (!vcsConfig("summary", cwd).ok) {
|
|
29274
|
+
stdout += `vcs: not configured — run /wk-init action vcs_scaffold
|
|
29275
|
+
`;
|
|
29276
|
+
}
|
|
29277
|
+
stdout += printSection("Merged PR Style");
|
|
29278
|
+
const style = mergedPrStyle(6, cwd);
|
|
29279
|
+
stdout += JSON.stringify(style, null, 2) + `
|
|
29280
|
+
`;
|
|
29281
|
+
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
29282
|
+
}
|
|
29283
|
+
var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
29284
|
+
- Use Added, Changed, Deprecated, Removed, Fixed, Security.
|
|
29285
|
+
- Entries should be human-readable and user-facing.
|
|
29286
|
+
- Do not use raw commit messages as changelog bullets.
|
|
29287
|
+
- MERGE into existing ### Category under [Unreleased] — never append a second ### Added / ### Fixed block.
|
|
29288
|
+
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
29289
|
+
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
29290
|
+
function changelogContext(root, range) {
|
|
29291
|
+
const cwd = path22.resolve(repoRoot3(root));
|
|
29292
|
+
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
29293
|
+
return invalidRange(cwd);
|
|
29294
|
+
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
29295
|
+
if (!isResolvableContextRange(cwd, resolvedRange))
|
|
29296
|
+
return invalidRange(cwd);
|
|
29297
|
+
let stdout = "";
|
|
29298
|
+
stdout += printSection("Repository");
|
|
29299
|
+
stdout += `root: ${cwd}
|
|
29300
|
+
`;
|
|
29301
|
+
stdout += `branch: ${currentBranch(cwd)}
|
|
29302
|
+
`;
|
|
29303
|
+
stdout += `range: ${resolvedRange}
|
|
29304
|
+
`;
|
|
29305
|
+
stdout += printSection("Keep a Changelog Rules");
|
|
29306
|
+
stdout += CHANGELOG_RULES + `
|
|
29307
|
+
`;
|
|
29308
|
+
stdout += printSection("Existing CHANGELOG.md");
|
|
29309
|
+
const changelogPath = path22.join(cwd, "CHANGELOG.md");
|
|
29310
|
+
if (existsSync11(changelogPath)) {
|
|
29311
|
+
stdout += readTrimmed(changelogPath, 260) + `
|
|
29312
|
+
`;
|
|
29313
|
+
} else {
|
|
29314
|
+
stdout += `CHANGELOG.md not found.
|
|
29315
|
+
`;
|
|
29812
29316
|
}
|
|
29813
|
-
|
|
29814
|
-
|
|
29815
|
-
|
|
29816
|
-
|
|
29817
|
-
|
|
29818
|
-
|
|
29819
|
-
|
|
29820
|
-
|
|
29821
|
-
|
|
29822
|
-
|
|
29823
|
-
|
|
29824
|
-
|
|
29317
|
+
stdout += printSection("Commits");
|
|
29318
|
+
stdout += commitLogForRange(cwd, resolvedRange) + `
|
|
29319
|
+
`;
|
|
29320
|
+
stdout += printSection("Diff Stat");
|
|
29321
|
+
stdout += diffStatForRange(cwd, resolvedRange) + `
|
|
29322
|
+
`;
|
|
29323
|
+
stdout += printSection("Changed Files");
|
|
29324
|
+
stdout += changedFilesForRange(cwd, resolvedRange) + `
|
|
29325
|
+
`;
|
|
29326
|
+
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
29327
|
+
}
|
|
29328
|
+
function docsRefreshContext(root, range) {
|
|
29329
|
+
const cwd = path22.resolve(repoRoot3(root));
|
|
29330
|
+
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
29331
|
+
return invalidRange(cwd);
|
|
29332
|
+
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
29333
|
+
if (!isResolvableContextRange(cwd, resolvedRange))
|
|
29334
|
+
return invalidRange(cwd);
|
|
29335
|
+
let stdout = "";
|
|
29336
|
+
stdout += printSection("Repository");
|
|
29337
|
+
stdout += `root: ${cwd}
|
|
29338
|
+
`;
|
|
29339
|
+
stdout += `branch: ${currentBranch(cwd)}
|
|
29340
|
+
`;
|
|
29341
|
+
stdout += `range: ${resolvedRange}
|
|
29342
|
+
`;
|
|
29343
|
+
stdout += printSection("Changed Files");
|
|
29344
|
+
stdout += changedFilesForRange(cwd, resolvedRange) + `
|
|
29345
|
+
`;
|
|
29346
|
+
stdout += printSection("Documentation Files");
|
|
29347
|
+
stdout += documentationFiles(cwd).join(`
|
|
29348
|
+
`) + `
|
|
29349
|
+
`;
|
|
29350
|
+
stdout += printSection("README Preview");
|
|
29351
|
+
const readme = path22.join(cwd, "README.md");
|
|
29352
|
+
if (existsSync11(readme)) {
|
|
29353
|
+
stdout += readTrimmed(readme, 220) + `
|
|
29354
|
+
`;
|
|
29355
|
+
} else {
|
|
29356
|
+
stdout += `README.md not found.
|
|
29357
|
+
`;
|
|
29825
29358
|
}
|
|
29826
|
-
|
|
29827
|
-
|
|
29828
|
-
|
|
29359
|
+
stdout += printSection("Package Scripts");
|
|
29360
|
+
const pkgPath = path22.join(cwd, "package.json");
|
|
29361
|
+
if (existsSync11(pkgPath)) {
|
|
29362
|
+
try {
|
|
29363
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
29364
|
+
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
29365
|
+
`;
|
|
29366
|
+
} catch {}
|
|
29367
|
+
} else {
|
|
29368
|
+
stdout += `package.json not found.
|
|
29369
|
+
`;
|
|
29829
29370
|
}
|
|
29830
|
-
|
|
29831
|
-
|
|
29832
|
-
|
|
29833
|
-
|
|
29834
|
-
|
|
29835
|
-
|
|
29836
|
-
|
|
29837
|
-
locale: args.locale,
|
|
29838
|
-
localeOptions: args.locale_options,
|
|
29839
|
-
timezone: args.timezone,
|
|
29840
|
-
preset: args.branch_policy_preset,
|
|
29841
|
-
allowed: args.branch_policy_allowed,
|
|
29842
|
-
protectedNames: args.branch_policy_protected
|
|
29843
|
-
}, current);
|
|
29844
|
-
writeConfig(next);
|
|
29845
|
-
return output(ok({ action: "config", path: path22.join(configDir(), "config.json"), ...next }));
|
|
29371
|
+
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
29372
|
+
}
|
|
29373
|
+
function releaseNotesContext(root, rangeOrTag) {
|
|
29374
|
+
const cwd = path22.resolve(repoRoot3(root));
|
|
29375
|
+
if (!rangeOrTag) {
|
|
29376
|
+
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
29377
|
+
`, exitCode: 1, cwd };
|
|
29846
29378
|
}
|
|
29847
|
-
|
|
29848
|
-
|
|
29849
|
-
|
|
29850
|
-
|
|
29851
|
-
|
|
29852
|
-
|
|
29853
|
-
|
|
29854
|
-
|
|
29855
|
-
|
|
29856
|
-
|
|
29857
|
-
|
|
29858
|
-
|
|
29859
|
-
|
|
29860
|
-
|
|
29861
|
-
|
|
29862
|
-
|
|
29863
|
-
|
|
29864
|
-
|
|
29865
|
-
|
|
29866
|
-
|
|
29867
|
-
|
|
29868
|
-
|
|
29869
|
-
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
|
|
29876
|
-
|
|
29877
|
-
|
|
29878
|
-
|
|
29879
|
-
|
|
29880
|
-
cwd
|
|
29881
|
-
|
|
29882
|
-
|
|
29883
|
-
var json2 = (stdout) => JSON.parse(stdout.trim());
|
|
29884
|
-
var optionalJson = (value) => {
|
|
29885
|
-
if (!value?.trim())
|
|
29886
|
-
return null;
|
|
29887
|
-
try {
|
|
29888
|
-
return JSON.parse(value);
|
|
29889
|
-
} catch {
|
|
29890
|
-
return null;
|
|
29379
|
+
if (!isResolvableContextRange(cwd, rangeOrTag))
|
|
29380
|
+
return invalidRange(cwd);
|
|
29381
|
+
const resolvedRange = rangeArgOrDefault(rangeOrTag, cwd);
|
|
29382
|
+
if (!isResolvableContextRange(cwd, resolvedRange))
|
|
29383
|
+
return invalidRange(cwd);
|
|
29384
|
+
let stdout = "";
|
|
29385
|
+
stdout += printSection("Repository");
|
|
29386
|
+
stdout += `root: ${cwd}
|
|
29387
|
+
`;
|
|
29388
|
+
stdout += `branch: ${currentBranch(cwd)}
|
|
29389
|
+
`;
|
|
29390
|
+
stdout += `requested: ${rangeOrTag}
|
|
29391
|
+
`;
|
|
29392
|
+
stdout += `range: ${resolvedRange}
|
|
29393
|
+
`;
|
|
29394
|
+
stdout += printSection("Tags");
|
|
29395
|
+
const tags = runGit(cwd, ["tag", "--sort=-creatordate"]);
|
|
29396
|
+
if (tags.exitCode === 0)
|
|
29397
|
+
stdout += tags.stdout.split(`
|
|
29398
|
+
`).slice(0, 20).join(`
|
|
29399
|
+
`) + `
|
|
29400
|
+
`;
|
|
29401
|
+
stdout += printSection("Commits");
|
|
29402
|
+
stdout += commitLogForRange(cwd, resolvedRange) + `
|
|
29403
|
+
`;
|
|
29404
|
+
stdout += printSection("Diff Stat");
|
|
29405
|
+
stdout += diffStatForRange(cwd, resolvedRange) + `
|
|
29406
|
+
`;
|
|
29407
|
+
stdout += printSection("Changed Files");
|
|
29408
|
+
stdout += changedFilesForRange(cwd, resolvedRange) + `
|
|
29409
|
+
`;
|
|
29410
|
+
stdout += printSection("Existing Release Files");
|
|
29411
|
+
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
29412
|
+
if (existsSync11(path22.join(cwd, rel)))
|
|
29413
|
+
stdout += rel + `
|
|
29414
|
+
`;
|
|
29891
29415
|
}
|
|
29892
|
-
|
|
29893
|
-
|
|
29894
|
-
var parsePr = (stdout) => {
|
|
29895
|
-
const part = sections(stdout);
|
|
29896
|
-
const repo = parseKeyValueLines(part.Repository ?? "", [
|
|
29897
|
-
"branch",
|
|
29898
|
-
"range",
|
|
29899
|
-
"base_ref",
|
|
29900
|
-
"merge_base",
|
|
29901
|
-
"diff_range",
|
|
29902
|
-
"range_mode",
|
|
29903
|
-
"git_sync"
|
|
29904
|
-
]);
|
|
29905
|
-
return {
|
|
29906
|
-
...repo,
|
|
29907
|
-
commits: part.Commits ?? "",
|
|
29908
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
29909
|
-
files: part["Changed Files"] ?? "",
|
|
29910
|
-
pr_template: part["PR Template"] ?? "",
|
|
29911
|
-
vcs_config: optionalJson(part["VCS Config"]),
|
|
29912
|
-
merged_pr_style: optionalJson(part["Merged PR Style"])
|
|
29913
|
-
};
|
|
29914
|
-
};
|
|
29915
|
-
var parseChangelog = (stdout) => {
|
|
29916
|
-
const part = sections(stdout);
|
|
29917
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["branch", "range"]);
|
|
29918
|
-
return {
|
|
29919
|
-
...repo,
|
|
29920
|
-
changelog_excerpt: part["Existing CHANGELOG.md"] ?? "",
|
|
29921
|
-
rules: part["Keep a Changelog Rules"] ?? "",
|
|
29922
|
-
commits: part.Commits ?? "",
|
|
29923
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
29924
|
-
files: part["Changed Files"] ?? ""
|
|
29925
|
-
};
|
|
29926
|
-
};
|
|
29927
|
-
var parseRelease = (stdout) => {
|
|
29928
|
-
const part = sections(stdout);
|
|
29929
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["requested", "range"]);
|
|
29930
|
-
return {
|
|
29931
|
-
...repo,
|
|
29932
|
-
tags: part.Tags ?? "",
|
|
29933
|
-
commits: part.Commits ?? "",
|
|
29934
|
-
diff_stat: part["Diff Stat"] ?? "",
|
|
29935
|
-
files: part["Changed Files"] ?? "",
|
|
29936
|
-
release_files: part["Existing Release Files"] ?? ""
|
|
29937
|
-
};
|
|
29938
|
-
};
|
|
29939
|
-
var parseDocs = (stdout) => {
|
|
29940
|
-
const part = sections(stdout);
|
|
29941
|
-
const repo = parseKeyValueLines(part.Repository ?? "", ["branch", "range"]);
|
|
29942
|
-
return {
|
|
29943
|
-
...repo,
|
|
29944
|
-
changed_files: part["Changed Files"] ?? "",
|
|
29945
|
-
readme_preview: part["README Preview"] ?? "",
|
|
29946
|
-
package_scripts: part["Package Scripts"] ?? "",
|
|
29947
|
-
files: part["Documentation Files"] ?? ""
|
|
29948
|
-
};
|
|
29949
|
-
};
|
|
29950
|
-
function createRepoTools(runtime = defaultRuntime) {
|
|
29951
|
-
const validateRange = (root, value) => {
|
|
29952
|
-
for (const revision of gitRevisionParts(value)) {
|
|
29953
|
-
const resolved = runtime.git(root, [
|
|
29954
|
-
"rev-parse",
|
|
29955
|
-
"--verify",
|
|
29956
|
-
"--quiet",
|
|
29957
|
-
"--end-of-options",
|
|
29958
|
-
`${revision}^{commit}`
|
|
29959
|
-
]);
|
|
29960
|
-
if (resolved.exitCode !== 0)
|
|
29961
|
-
throw new Error(`invalid Git revision or range: ${value}`);
|
|
29962
|
-
}
|
|
29963
|
-
};
|
|
29964
|
-
const contextWithRange = async (root, runContext, value, parse) => {
|
|
29965
|
-
try {
|
|
29966
|
-
if (value)
|
|
29967
|
-
validateRange(root, value);
|
|
29968
|
-
} catch (error) {
|
|
29969
|
-
return output(fail(error instanceof Error ? error.message : "invalid Git revision or range"));
|
|
29970
|
-
}
|
|
29971
|
-
return output(scriptResult(await runContext(root, value), parse));
|
|
29972
|
-
};
|
|
29973
|
-
return {
|
|
29974
|
-
workit_init_status: tool({
|
|
29975
|
-
description: "Inspect toolkit initialization",
|
|
29976
|
-
args: {},
|
|
29977
|
-
execute: async (_input, context) => output(scriptResult(await runtime.initStatus(context.directory), json2))
|
|
29978
|
-
}),
|
|
29979
|
-
workit_status: tool({
|
|
29980
|
-
description: "Inspect toolkit and repository state",
|
|
29981
|
-
args: {},
|
|
29982
|
-
execute: async (_input, context) => output(scriptResult(await runtime.toolkitStatus(context.directory), json2))
|
|
29983
|
-
}),
|
|
29984
|
-
workit_git_context: tool({
|
|
29985
|
-
description: "Read Git branch and change context",
|
|
29986
|
-
args: { paths: tool.schema.array(tool.schema.string()).optional() },
|
|
29987
|
-
execute: async ({ paths }, context) => output(ok(gitContext(context.directory, paths ?? [])))
|
|
29988
|
-
}),
|
|
29989
|
-
workit_verify: tool({
|
|
29990
|
-
description: "Discover and run repository verification",
|
|
29991
|
-
args: { dry_run: tool.schema.boolean().optional() },
|
|
29992
|
-
execute: async ({ dry_run }, context) => output(scriptResult(runtime.verifyProject(context.directory, Boolean(dry_run)), parseVerifyOutput))
|
|
29993
|
-
}),
|
|
29994
|
-
workit_pr_context: tool({
|
|
29995
|
-
description: "Gather branch-exclusive PR context",
|
|
29996
|
-
args: { range: tool.schema.string().optional() },
|
|
29997
|
-
execute: async ({ range }, context) => contextWithRange(context.directory, runtime.prContext, range, parsePr)
|
|
29998
|
-
}),
|
|
29999
|
-
workit_changelog_context: tool({
|
|
30000
|
-
description: "Gather changelog context",
|
|
30001
|
-
args: { range: tool.schema.string().optional() },
|
|
30002
|
-
execute: async ({ range }, context) => contextWithRange(context.directory, runtime.changelogContext, range, parseChangelog)
|
|
30003
|
-
}),
|
|
30004
|
-
workit_release_notes_context: tool({
|
|
30005
|
-
description: "Gather release notes for an explicit range",
|
|
30006
|
-
args: { range_or_tag: tool.schema.string() },
|
|
30007
|
-
execute: async ({ range_or_tag }, context) => !range_or_tag.trim() ? output(fail("release tag or range required")) : contextWithRange(context.directory, (root, value) => runtime.releaseContext(root, value ?? ""), range_or_tag, parseRelease)
|
|
30008
|
-
}),
|
|
30009
|
-
workit_docs_context: tool({
|
|
30010
|
-
description: "Gather documentation refresh context",
|
|
30011
|
-
args: { range: tool.schema.string().optional() },
|
|
30012
|
-
execute: async ({ range }, context) => output(scriptResult(runtime.docsContext(context.directory, range), parseDocs))
|
|
30013
|
-
}),
|
|
30014
|
-
workit_init_apply: tool({
|
|
30015
|
-
description: "Apply a confirmed toolkit initialization action",
|
|
30016
|
-
args: {
|
|
30017
|
-
confirmed: tool.schema.boolean(),
|
|
30018
|
-
action: tool.schema.enum([
|
|
30019
|
-
"youtrack_scaffold",
|
|
30020
|
-
"youtrack_json",
|
|
30021
|
-
"youtrack_token_placeholder",
|
|
30022
|
-
"vcs_scaffold",
|
|
30023
|
-
"config",
|
|
30024
|
-
"gitignore",
|
|
30025
|
-
"hygiene",
|
|
30026
|
-
"branch_policy"
|
|
30027
|
-
]),
|
|
30028
|
-
base_url: tool.schema.string().optional(),
|
|
30029
|
-
default_mention: tool.schema.string().optional(),
|
|
30030
|
-
meeting_issue: tool.schema.string().optional(),
|
|
30031
|
-
vcs_provider: tool.schema.enum(["gitlab", "github"]).optional(),
|
|
30032
|
-
vcs_target_branch: tool.schema.string().optional(),
|
|
30033
|
-
name: tool.schema.string().optional(),
|
|
30034
|
-
develop_branch: tool.schema.string().optional(),
|
|
30035
|
-
integration: tool.schema.enum(["pr", "merge"]).optional(),
|
|
30036
|
-
locale: tool.schema.string().optional(),
|
|
30037
|
-
locale_options: tool.schema.array(tool.schema.string()).optional(),
|
|
30038
|
-
timezone: tool.schema.string().optional(),
|
|
30039
|
-
branch_policy_preset: tool.schema.enum(["gitflow", "github-flow", "trunk-based", "custom"]).optional(),
|
|
30040
|
-
branch_policy_allowed: tool.schema.array(tool.schema.string()).optional(),
|
|
30041
|
-
branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
|
|
30042
|
-
include_open_source: tool.schema.boolean().optional()
|
|
30043
|
-
},
|
|
30044
|
-
execute: async (args, context) => executeInitApply(args, context.directory, runtime)
|
|
30045
|
-
})
|
|
30046
|
-
};
|
|
30047
|
-
}
|
|
29416
|
+
stdout += printSection("Release Notes Draft");
|
|
29417
|
+
stdout += `## Release notes (${resolvedRange})
|
|
30048
29418
|
|
|
30049
|
-
|
|
30050
|
-
|
|
30051
|
-
|
|
29419
|
+
`;
|
|
29420
|
+
const commits = commitLogForRange(cwd, resolvedRange).split(`
|
|
29421
|
+
`).map((line) => line.trim()).filter(Boolean);
|
|
29422
|
+
stdout += commits.length ? `${commits.map((line) => `- ${line}`).join(`
|
|
29423
|
+
`)}
|
|
29424
|
+
` : `- No commits in range.
|
|
29425
|
+
`;
|
|
29426
|
+
stdout += `
|
|
29427
|
+
### Changed files
|
|
30052
29428
|
|
|
30053
|
-
|
|
30054
|
-
|
|
30055
|
-
|
|
30056
|
-
|
|
30057
|
-
|
|
30058
|
-
|
|
30059
|
-
|
|
30060
|
-
}
|
|
30061
|
-
function whichOnPath(tool) {
|
|
30062
|
-
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
30063
|
-
for (const dir of (process.env.PATH ?? "").split(path23.delimiter)) {
|
|
30064
|
-
if (!dir)
|
|
30065
|
-
continue;
|
|
30066
|
-
for (const name of names) {
|
|
30067
|
-
const candidate = path23.join(dir, name);
|
|
30068
|
-
try {
|
|
30069
|
-
fs10.accessSync(candidate, fs10.constants.X_OK);
|
|
30070
|
-
return candidate;
|
|
30071
|
-
} catch {}
|
|
30072
|
-
}
|
|
30073
|
-
}
|
|
30074
|
-
return null;
|
|
29429
|
+
`;
|
|
29430
|
+
const files = changedFilesForRange(cwd, resolvedRange).split(`
|
|
29431
|
+
`).map((line) => line.trim()).filter(Boolean);
|
|
29432
|
+
stdout += files.length ? `${files.map((line) => `- ${line}`).join(`
|
|
29433
|
+
`)}
|
|
29434
|
+
` : `- None.
|
|
29435
|
+
`;
|
|
29436
|
+
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
30075
29437
|
}
|
|
30076
|
-
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
30077
|
-
var pushRemote = (cwd) => {
|
|
30078
|
-
const result = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
30079
|
-
cwd,
|
|
30080
|
-
encoding: "utf8"
|
|
30081
|
-
});
|
|
30082
|
-
if (result.status !== 0)
|
|
30083
|
-
return null;
|
|
30084
|
-
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
30085
|
-
return urls.length === 1 ? urls[0] : null;
|
|
30086
|
-
};
|
|
30087
29438
|
|
|
30088
29439
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
30089
|
-
import { spawnSync as
|
|
29440
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
30090
29441
|
var repoPathFromRemote = (remote) => {
|
|
30091
29442
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
30092
29443
|
if (!rest)
|
|
@@ -30107,7 +29458,7 @@ var defaultCli = (bin, available) => {
|
|
|
30107
29458
|
if (!available)
|
|
30108
29459
|
return null;
|
|
30109
29460
|
return (args, root) => {
|
|
30110
|
-
const out =
|
|
29461
|
+
const out = spawnSync8(bin, args, {
|
|
30111
29462
|
cwd: root,
|
|
30112
29463
|
encoding: "utf8",
|
|
30113
29464
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -30274,7 +29625,7 @@ var createContextTool = () => tool({
|
|
|
30274
29625
|
try {
|
|
30275
29626
|
const payload = parsed.data.payload;
|
|
30276
29627
|
const cwd = payload.cwd;
|
|
30277
|
-
root = realpathSync10(cwd ?
|
|
29628
|
+
root = realpathSync10(cwd ? path23.resolve(context.directory, cwd) : context.directory);
|
|
30278
29629
|
} catch {
|
|
30279
29630
|
return JSON.stringify(failure("invalid_input", "action target directory cannot be resolved", {
|
|
30280
29631
|
outcome: "not_started"
|
|
@@ -30286,14 +29637,14 @@ var createContextTool = () => tool({
|
|
|
30286
29637
|
|
|
30287
29638
|
// packages/workit-opencode/src/shared/session.ts
|
|
30288
29639
|
import { realpathSync as realpathSync11 } from "node:fs";
|
|
30289
|
-
import
|
|
29640
|
+
import path24 from "node:path";
|
|
30290
29641
|
var sameWorkspace = (expected, observed) => {
|
|
30291
29642
|
if (typeof observed !== "string" || !observed)
|
|
30292
29643
|
return false;
|
|
30293
29644
|
try {
|
|
30294
29645
|
return realpathSync11(expected) === realpathSync11(observed);
|
|
30295
29646
|
} catch {
|
|
30296
|
-
return
|
|
29647
|
+
return path24.resolve(expected) === path24.resolve(observed);
|
|
30297
29648
|
}
|
|
30298
29649
|
};
|
|
30299
29650
|
|
|
@@ -30769,7 +30120,7 @@ var observeQuestionEvent = (receipts, event) => {
|
|
|
30769
30120
|
};
|
|
30770
30121
|
|
|
30771
30122
|
// packages/workit-opencode/src/runtime.ts
|
|
30772
|
-
import { readFileSync as
|
|
30123
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
30773
30124
|
var compactContextFor = (root, sessionID) => {
|
|
30774
30125
|
try {
|
|
30775
30126
|
const store = new TaskStore(root);
|
|
@@ -30835,7 +30186,7 @@ var workerContextFor = (root, sessionID, parentID, directChildren) => {
|
|
|
30835
30186
|
};
|
|
30836
30187
|
var loadProvenance = (logger, pkgUrl) => {
|
|
30837
30188
|
try {
|
|
30838
|
-
const pkg = JSON.parse(
|
|
30189
|
+
const pkg = JSON.parse(readFileSync11(pkgUrl, "utf8"));
|
|
30839
30190
|
return {
|
|
30840
30191
|
name: String(pkg.name ?? "workit-opencode"),
|
|
30841
30192
|
version: String(pkg.version ?? "unknown")
|
|
@@ -30848,7 +30199,7 @@ var loadProvenance = (logger, pkgUrl) => {
|
|
|
30848
30199
|
|
|
30849
30200
|
// packages/workit-opencode/src/v1/server.ts
|
|
30850
30201
|
var root = assetsRoot();
|
|
30851
|
-
var skillsPath =
|
|
30202
|
+
var skillsPath = path25.join(root, "skills");
|
|
30852
30203
|
var logger = createLogger({
|
|
30853
30204
|
appLog: () => {
|
|
30854
30205
|
return;
|
|
@@ -30867,7 +30218,7 @@ var warnStaleSources = () => {
|
|
|
30867
30218
|
logger.warn(EVENT.hooks, {
|
|
30868
30219
|
boundary: "stale_sources",
|
|
30869
30220
|
reason: "workit sources changed after plugin load; restart the session for latest behavior",
|
|
30870
|
-
files: changed.map((file) =>
|
|
30221
|
+
files: changed.map((file) => path25.basename(file))
|
|
30871
30222
|
});
|
|
30872
30223
|
} catch {}
|
|
30873
30224
|
};
|
|
@@ -30897,7 +30248,7 @@ var plugin = async ({ client, directory }) => {
|
|
|
30897
30248
|
const dispatches = new Map;
|
|
30898
30249
|
try {
|
|
30899
30250
|
logger.info(EVENT.initialization, { host: "opencode", plugin_root: root });
|
|
30900
|
-
logger.info(EVENT.provenance, loadProvenance(logger,
|
|
30251
|
+
logger.info(EVENT.provenance, loadProvenance(logger, path25.join(packageRoot2(), "package.json")));
|
|
30901
30252
|
} catch (error) {
|
|
30902
30253
|
logger.warn(EVENT.hooks, { boundary: "initialization", ...errorDetail(error) });
|
|
30903
30254
|
}
|
|
@@ -31190,7 +30541,7 @@ var plugin = async ({ client, directory }) => {
|
|
|
31190
30541
|
config: async (config) => {
|
|
31191
30542
|
const mutable = config;
|
|
31192
30543
|
const paths = [...mutable.skills?.paths ?? []];
|
|
31193
|
-
if (
|
|
30544
|
+
if (existsSync12(skillsPath) && !paths.includes(skillsPath))
|
|
31194
30545
|
paths.push(skillsPath);
|
|
31195
30546
|
mutable.skills ??= {};
|
|
31196
30547
|
mutable.skills.paths = paths;
|
|
@@ -31832,8 +31183,8 @@ var normalizeQuestionAnswers = (answers) => {
|
|
|
31832
31183
|
};
|
|
31833
31184
|
|
|
31834
31185
|
// packages/workit-opencode/src/v2/registry.ts
|
|
31835
|
-
import { existsSync as
|
|
31836
|
-
import
|
|
31186
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
|
|
31187
|
+
import path26 from "node:path";
|
|
31837
31188
|
var parseFrontmatter = (markdown) => {
|
|
31838
31189
|
const match = markdown.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
31839
31190
|
if (!match)
|
|
@@ -31853,8 +31204,8 @@ var parseFrontmatter = (markdown) => {
|
|
|
31853
31204
|
};
|
|
31854
31205
|
};
|
|
31855
31206
|
var registerSkills = async (ctx) => {
|
|
31856
|
-
const skillsDir =
|
|
31857
|
-
if (!
|
|
31207
|
+
const skillsDir = path26.join(assetsRoot(), "skills");
|
|
31208
|
+
if (!existsSync13(skillsDir))
|
|
31858
31209
|
return new Set;
|
|
31859
31210
|
const registered = new Set;
|
|
31860
31211
|
await ctx.skill.transform((editor) => {
|
|
@@ -31862,10 +31213,10 @@ var registerSkills = async (ctx) => {
|
|
|
31862
31213
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
31863
31214
|
if (existing.has(id))
|
|
31864
31215
|
continue;
|
|
31865
|
-
const file =
|
|
31866
|
-
if (!
|
|
31216
|
+
const file = path26.join(skillsDir, id, "SKILL.md");
|
|
31217
|
+
if (!existsSync13(file))
|
|
31867
31218
|
continue;
|
|
31868
|
-
const parsed = parseFrontmatter(
|
|
31219
|
+
const parsed = parseFrontmatter(readFileSync12(file, "utf8"));
|
|
31869
31220
|
editor.add({
|
|
31870
31221
|
id,
|
|
31871
31222
|
name: parsed.name || id,
|