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