@brainervirus/workit-core 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/README.md +2 -2
- package/package.json +2 -3
- package/scripts/sync-runtime.sh +9 -5
- package/src/core/detect-hosts.ts +24 -22
- package/src/core/doctor.ts +11 -4
- package/src/core/host-install.ts +458 -0
- package/src/core/init.ts +1 -256
- package/src/core/policy-resolver.ts +0 -45
- package/src/core/registration.ts +3 -4
- package/src/core/repo-context.ts +1 -1
- package/src/core/rules.ts +1 -61
- package/src/core/setup.ts +203 -11
- package/src/core/templates.ts +1 -26
- package/src/core/vcs-config.ts +2 -8
- package/src/core/workspaces.ts +18 -17
- package/src/core/config-guard.ts +0 -27
- package/src/core/doc-render.ts +0 -14
- package/src/core/docs-layout.ts +0 -279
- package/src/core/docs-migration.ts +0 -639
- package/src/core/docs-repo.ts +0 -233
- package/src/core/docs-validate.ts +0 -393
- package/src/core/parse-sections.ts +0 -24
- package/src/core/ports/init-apply.ts +0 -15
- package/src/core/ports/init-status.ts +0 -4
- package/src/core/ports/init-toolkit-status.ts +0 -4
- package/src/core/ports/pr-create.ts +0 -30
- package/src/core/ports/present-ascii.ts +0 -10
- package/src/core/ports/present-flow.ts +0 -10
- package/src/core/ports/vcs-config.ts +0 -14
- package/src/core/ports/vcs-merged-style.ts +0 -5
- package/src/core/ports/vcs-verify-token.ts +0 -4
- package/src/core/ports/youtrack-api.ts +0 -19
- package/src/core/ports/youtrack-config.ts +0 -21
- package/src/core/ports/youtrack-greeting.ts +0 -10
- package/src/core/ports/youtrack-parse-duration.ts +0 -14
- package/src/core/ports/youtrack-token-create-url.ts +0 -4
- package/src/core/ports/youtrack-verify-token.ts +0 -12
- package/src/core/ports/youtrack-work-date-ms.ts +0 -10
- package/src/core/present.ts +0 -109
- package/src/core/repo-tool.ts +0 -12
- package/src/core/repo-tools.ts +0 -22
- package/src/core/sync-runtime.ts +0 -375
- package/src/core/verify-parse.ts +0 -29
- package/src/core/verify-project.ts +0 -181
package/src/core/setup.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
readConfigFromDir,
|
|
23
23
|
resolveConfigDir,
|
|
24
24
|
type BranchPreset,
|
|
25
|
+
type ToolkitConfig,
|
|
25
26
|
} from "./config";
|
|
26
27
|
import { detectBranchPolicy } from "./branch-policy";
|
|
27
28
|
import { readSetupState, type SetupState } from "./setup-state";
|
|
@@ -38,13 +39,26 @@ import { planHygieneFiles } from "./hygiene";
|
|
|
38
39
|
import { packageRoot } from "./package-root";
|
|
39
40
|
import {
|
|
40
41
|
cursorMcpServerEntry,
|
|
42
|
+
CURSOR_RUNTIME_PACKAGE,
|
|
43
|
+
isWorkitPlugin,
|
|
41
44
|
mergeCursorMcp,
|
|
45
|
+
mergeOpenCodePlugins,
|
|
42
46
|
mergeCursorSettings,
|
|
43
47
|
mergeOpenCodeConfig,
|
|
44
48
|
OPENCODE_NPM_PIN,
|
|
45
49
|
} from "./registration";
|
|
46
50
|
import { runDoctor, type DoctorReport } from "./doctor";
|
|
47
51
|
import { writeFileExclusive } from "./safe-write";
|
|
52
|
+
import {
|
|
53
|
+
isCodexWorkitInstalled,
|
|
54
|
+
isPiWorkitInstalled,
|
|
55
|
+
planHostInstall,
|
|
56
|
+
runHostInstall,
|
|
57
|
+
type HostCommandRunner,
|
|
58
|
+
type HostId,
|
|
59
|
+
type HostInstallCommand,
|
|
60
|
+
runHostCommand,
|
|
61
|
+
} from "./host-install";
|
|
48
62
|
|
|
49
63
|
// Setup preview + apply (WZ-04-WZ-09, WZ-13-WZ-15; CA-12, CA-14, CA-22, CA-23).
|
|
50
64
|
// buildSetupPreview is a pure reader: it classifies the current setup state and
|
|
@@ -82,6 +96,8 @@ export type SetupPreviewInput = {
|
|
|
82
96
|
* configured tokenFile, the replacement is planned as its own distinct
|
|
83
97
|
* reviewed mutation (AR-10). */
|
|
84
98
|
tokenPaths?: { youtrack?: string; gitlab?: string; github?: string };
|
|
99
|
+
/** Optional global commit policy used as the base for repository overrides. */
|
|
100
|
+
commitPolicy?: ToolkitConfig["commitPolicy"];
|
|
85
101
|
};
|
|
86
102
|
|
|
87
103
|
export type SetupMutation =
|
|
@@ -98,6 +114,7 @@ export type SetupMutation =
|
|
|
98
114
|
// during preview so the reviewed mutation set IS the Apply write set.
|
|
99
115
|
| { type: "register-platform"; platform: Platform; path: string }
|
|
100
116
|
| { type: "install-adapter"; platform: "cursor"; path: string }
|
|
117
|
+
| { type: "install-host"; platform: HostId; path: string; commands: HostInstallCommand[] }
|
|
101
118
|
// AR-10: changing a configured tokenFile path is never hidden inside a
|
|
102
119
|
// generic merge — it is its own reviewed mutation.
|
|
103
120
|
| { type: "set-token-path"; path: string; key: string; value: string };
|
|
@@ -249,6 +266,7 @@ export function buildSetupPreview(
|
|
|
249
266
|
locale: values.locale,
|
|
250
267
|
localeOptions: current.localeOptions,
|
|
251
268
|
timezone: values.timezone,
|
|
269
|
+
...(values.commitPolicy === undefined ? {} : { commitPolicy: values.commitPolicy }),
|
|
252
270
|
branchPolicy: mergePreset(
|
|
253
271
|
values.branchPreset,
|
|
254
272
|
{
|
|
@@ -344,9 +362,56 @@ export function buildSetupPreview(
|
|
|
344
362
|
if (platform === "opencode") {
|
|
345
363
|
mutations.push({ type: "register-platform", platform, path: paths.opencodeConfig });
|
|
346
364
|
} else if (platform === "cursor") {
|
|
365
|
+
const cursorRoot = adapterRoot("cursor", {
|
|
366
|
+
...paths,
|
|
367
|
+
dev: opts.dev ?? env.WORKFLOW_TOOLKIT_DEV ?? null,
|
|
368
|
+
});
|
|
369
|
+
if (cursorRoot === null) {
|
|
370
|
+
const commands = planHostInstall("cursor", {
|
|
371
|
+
home: paths.home,
|
|
372
|
+
cwd: paths.cwd,
|
|
373
|
+
env,
|
|
374
|
+
packageVersion: "latest",
|
|
375
|
+
targetDir: paths.cursorPluginDir,
|
|
376
|
+
});
|
|
377
|
+
mutations.push({
|
|
378
|
+
type: "install-host",
|
|
379
|
+
platform: "cursor",
|
|
380
|
+
path: path.join(
|
|
381
|
+
paths.home,
|
|
382
|
+
".cursor",
|
|
383
|
+
"plugins",
|
|
384
|
+
"local",
|
|
385
|
+
".workit-package-bootstrap",
|
|
386
|
+
),
|
|
387
|
+
commands,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
347
390
|
mutations.push({ type: "install-adapter", platform, path: paths.cursorPluginDir });
|
|
348
391
|
mutations.push({ type: "register-platform", platform, path: paths.cursorSettings });
|
|
349
392
|
mutations.push({ type: "register-platform", platform, path: paths.cursorMcp });
|
|
393
|
+
} else if (platform === "codex" || platform === "pi") {
|
|
394
|
+
const commands = planHostInstall(platform, { home: paths.home, cwd: paths.cwd, env });
|
|
395
|
+
if (commands.length > 0) {
|
|
396
|
+
mutations.push({
|
|
397
|
+
type: "install-host",
|
|
398
|
+
platform,
|
|
399
|
+
path:
|
|
400
|
+
platform === "codex"
|
|
401
|
+
? path.join(
|
|
402
|
+
env.CODEX_HOME ?? path.join(paths.home, ".codex"),
|
|
403
|
+
"plugins",
|
|
404
|
+
"cache",
|
|
405
|
+
"workflow-toolkit",
|
|
406
|
+
"workit",
|
|
407
|
+
)
|
|
408
|
+
: path.join(
|
|
409
|
+
env.PI_CODING_AGENT_DIR ?? path.join(paths.home, ".pi", "agent"),
|
|
410
|
+
"settings.json",
|
|
411
|
+
),
|
|
412
|
+
commands,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
350
415
|
}
|
|
351
416
|
}
|
|
352
417
|
}
|
|
@@ -391,7 +456,7 @@ export type Platform = "opencode" | "cursor";
|
|
|
391
456
|
export type SetupResultStatus = "Installed" | "Configured" | "Skipped" | "Failed";
|
|
392
457
|
|
|
393
458
|
export type SetupResultEntry = {
|
|
394
|
-
platform:
|
|
459
|
+
platform: HostId | "core";
|
|
395
460
|
file: string;
|
|
396
461
|
status: SetupResultStatus;
|
|
397
462
|
detail?: string;
|
|
@@ -422,6 +487,10 @@ export type ApplySetupOptions = {
|
|
|
422
487
|
cursorMcp?: string;
|
|
423
488
|
cursorPluginDir?: string;
|
|
424
489
|
stateDir?: string;
|
|
490
|
+
/** Injectable synchronous host runner; defaults to a bounded no-shell spawn. */
|
|
491
|
+
runHostCommand?: HostCommandRunner;
|
|
492
|
+
/** Prefer staged npm adapter assets, used by the explicit upgrade flow. */
|
|
493
|
+
preferInstalledAdapter?: boolean;
|
|
425
494
|
};
|
|
426
495
|
|
|
427
496
|
type ResolvedApply = {
|
|
@@ -528,7 +597,7 @@ const readFileSafe = (p: string): string | null => {
|
|
|
528
597
|
// adapter resolution (AR-09).
|
|
529
598
|
type CoreMutation = Exclude<
|
|
530
599
|
SetupMutation,
|
|
531
|
-
{ type: "register-platform" } | { type: "install-adapter" }
|
|
600
|
+
{ type: "register-platform" } | { type: "install-adapter" } | { type: "install-host" }
|
|
532
601
|
>;
|
|
533
602
|
|
|
534
603
|
const workspaceEntriesAlreadyMatch = (
|
|
@@ -735,8 +804,23 @@ const isAdapter = (root: string, platform: Platform): boolean => {
|
|
|
735
804
|
}
|
|
736
805
|
};
|
|
737
806
|
|
|
738
|
-
function adapterRoot(
|
|
807
|
+
function adapterRoot(
|
|
808
|
+
platform: Platform,
|
|
809
|
+
res: ResolvedApply,
|
|
810
|
+
preferInstalledAdapter = false,
|
|
811
|
+
): string | null {
|
|
739
812
|
const candidates: string[] = [];
|
|
813
|
+
const stagedCursor = path.join(
|
|
814
|
+
res.home,
|
|
815
|
+
".cursor",
|
|
816
|
+
"plugins",
|
|
817
|
+
"local",
|
|
818
|
+
".workit-package-bootstrap",
|
|
819
|
+
"node_modules",
|
|
820
|
+
"@brainervirus",
|
|
821
|
+
"workit-cursor",
|
|
822
|
+
);
|
|
823
|
+
if (platform === "cursor" && preferInstalledAdapter) candidates.push(stagedCursor);
|
|
740
824
|
if (res.dev) {
|
|
741
825
|
// A dev checkout is the complete source of truth for adapter packages: when
|
|
742
826
|
// WORKFLOW_TOOLKIT_DEV is set, a package missing from it is a real failure,
|
|
@@ -757,6 +841,7 @@ function adapterRoot(platform: Platform, res: ResolvedApply): string | null {
|
|
|
757
841
|
candidates.push(
|
|
758
842
|
path.join(res.home, ".local", "share", "workit", "packages", `workit-${platform}`),
|
|
759
843
|
);
|
|
844
|
+
if (platform === "cursor") candidates.push(stagedCursor);
|
|
760
845
|
for (const candidate of candidates) {
|
|
761
846
|
if (isAdapter(candidate, platform)) return candidate;
|
|
762
847
|
}
|
|
@@ -787,8 +872,8 @@ const opencodePin = (root: string, res: ResolvedApply): string | null =>
|
|
|
787
872
|
resolveOpenCodePin(root, { dev: res.dev });
|
|
788
873
|
|
|
789
874
|
function applyOpenCode(root: string, res: ResolvedApply): SetupResultEntry {
|
|
790
|
-
const
|
|
791
|
-
if (!
|
|
875
|
+
const resolvedPin = opencodePin(root, res);
|
|
876
|
+
if (!resolvedPin) {
|
|
792
877
|
return {
|
|
793
878
|
platform: "opencode",
|
|
794
879
|
file: res.opencodeConfig,
|
|
@@ -807,7 +892,33 @@ function applyOpenCode(root: string, res: ResolvedApply): SetupResultEntry {
|
|
|
807
892
|
detail: `cannot merge into malformed config: ${existing.error} — repair or remove the file`,
|
|
808
893
|
};
|
|
809
894
|
}
|
|
810
|
-
const
|
|
895
|
+
const configuredPlugins =
|
|
896
|
+
existing.kind === "record"
|
|
897
|
+
? Array.isArray(existing.value.plugins)
|
|
898
|
+
? existing.value.plugins
|
|
899
|
+
: typeof existing.value.plugins === "string"
|
|
900
|
+
? [existing.value.plugins]
|
|
901
|
+
: typeof existing.value.plugin === "string"
|
|
902
|
+
? [existing.value.plugin]
|
|
903
|
+
: Array.isArray(existing.value.plugin)
|
|
904
|
+
? existing.value.plugin
|
|
905
|
+
: []
|
|
906
|
+
: [];
|
|
907
|
+
const configuredPin = configuredPlugins.find(
|
|
908
|
+
(entry): entry is string => typeof entry === "string" && isWorkitPlugin(entry),
|
|
909
|
+
);
|
|
910
|
+
const pin = configuredPin ?? resolvedPin;
|
|
911
|
+
const base = existing.kind === "record" ? existing.value : {};
|
|
912
|
+
const merged =
|
|
913
|
+
existing.kind === "record" && Object.hasOwn(existing.value, "plugins")
|
|
914
|
+
? (() => {
|
|
915
|
+
const plugins = mergeOpenCodePlugins(existing.value.plugins, pin);
|
|
916
|
+
return {
|
|
917
|
+
config: { ...existing.value, plugins: plugins.config },
|
|
918
|
+
changed: plugins.changed.length > 0 ? ["plugins"] : [],
|
|
919
|
+
};
|
|
920
|
+
})()
|
|
921
|
+
: mergeOpenCodeConfig(base, pin);
|
|
811
922
|
if (merged.changed.length === 0) {
|
|
812
923
|
return {
|
|
813
924
|
platform: "opencode",
|
|
@@ -996,11 +1107,21 @@ function applyCursorMcp(root: string, res: ResolvedApply): SetupResultEntry {
|
|
|
996
1107
|
detail: `cannot merge into malformed config: ${mcpExisting.error} — repair or remove the file`,
|
|
997
1108
|
};
|
|
998
1109
|
}
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1110
|
+
const mcpBase = mcpExisting.kind === "record" ? mcpExisting.value : {};
|
|
1111
|
+
const server = cursorMcpServerEntry(res.cursorPluginDir);
|
|
1112
|
+
const servers = isRecord(mcpBase.mcpServers) ? mcpBase.mcpServers : {};
|
|
1113
|
+
const current = isRecord(servers.workit) ? servers.workit : {};
|
|
1114
|
+
const previousArgs = Array.isArray(current.args) ? current.args.map(String) : [];
|
|
1115
|
+
const previousPackage = previousArgs.find(
|
|
1116
|
+
(arg) =>
|
|
1117
|
+
arg.startsWith("--package=@brainervirus/workit-cursor@") &&
|
|
1118
|
+
arg !== `--package=${CURSOR_RUNTIME_PACKAGE}`,
|
|
1003
1119
|
);
|
|
1120
|
+
if (previousPackage) {
|
|
1121
|
+
const index = server.args.findIndex((arg) => arg.startsWith("--package="));
|
|
1122
|
+
if (index >= 0) server.args[index] = previousPackage;
|
|
1123
|
+
}
|
|
1124
|
+
const mcp = mergeCursorMcp(mcpBase, "workit", server);
|
|
1004
1125
|
if (mcp.changed.length === 0) {
|
|
1005
1126
|
return {
|
|
1006
1127
|
platform: "cursor",
|
|
@@ -1117,6 +1238,77 @@ export function applySetupPreview(
|
|
|
1117
1238
|
// legacy identity or its registration.
|
|
1118
1239
|
let cursorCopyOk = false;
|
|
1119
1240
|
for (const mutation of preview.mutations) {
|
|
1241
|
+
if (mutation.type === "install-host") {
|
|
1242
|
+
const expectedPath =
|
|
1243
|
+
mutation.platform === "cursor"
|
|
1244
|
+
? path.join(res.home, ".cursor", "plugins", "local", ".workit-package-bootstrap")
|
|
1245
|
+
: mutation.platform === "codex"
|
|
1246
|
+
? path.join(
|
|
1247
|
+
res.env.CODEX_HOME ?? path.join(res.home, ".codex"),
|
|
1248
|
+
"plugins",
|
|
1249
|
+
"cache",
|
|
1250
|
+
"workflow-toolkit",
|
|
1251
|
+
"workit",
|
|
1252
|
+
)
|
|
1253
|
+
: mutation.platform === "pi"
|
|
1254
|
+
? path.join(
|
|
1255
|
+
res.env.PI_CODING_AGENT_DIR ?? path.join(res.home, ".pi", "agent"),
|
|
1256
|
+
"settings.json",
|
|
1257
|
+
)
|
|
1258
|
+
: "";
|
|
1259
|
+
if (mutation.path !== expectedPath) {
|
|
1260
|
+
entries.push({
|
|
1261
|
+
platform: mutation.platform,
|
|
1262
|
+
file: mutation.path,
|
|
1263
|
+
status: "Failed",
|
|
1264
|
+
detail: `preview/apply path mismatch: apply resolved ${expectedPath}, the previewed host install was ${mutation.path} — rebuild the preview with the same options`,
|
|
1265
|
+
});
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
const run =
|
|
1269
|
+
options.runHostCommand ??
|
|
1270
|
+
((step: HostInstallCommand) => runHostCommand(step, { home: res.home, env: res.env }));
|
|
1271
|
+
const installed = runHostInstall(mutation.commands, run);
|
|
1272
|
+
for (const { command, result } of installed.results) {
|
|
1273
|
+
entries.push({
|
|
1274
|
+
platform: mutation.platform,
|
|
1275
|
+
file: command.command,
|
|
1276
|
+
status: result.exitCode === 0 ? "Installed" : "Failed",
|
|
1277
|
+
detail:
|
|
1278
|
+
result.exitCode === 0
|
|
1279
|
+
? `${command.purpose} (${command.args.join(" ")})`
|
|
1280
|
+
: `${command.purpose} failed (${command.args.join(" ")}): ${result.stderr || `exit ${result.exitCode}`}`,
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
if (!installed.ok && installed.results.length === 0) {
|
|
1284
|
+
entries.push({
|
|
1285
|
+
platform: mutation.platform,
|
|
1286
|
+
file: mutation.platform,
|
|
1287
|
+
status: "Failed",
|
|
1288
|
+
detail: "host installation did not run",
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
if (!installed.ok) continue;
|
|
1292
|
+
if (mutation.platform === "cursor" && installed.packageRoot) {
|
|
1293
|
+
rootFor.set("cursor", installed.packageRoot);
|
|
1294
|
+
}
|
|
1295
|
+
const verified =
|
|
1296
|
+
mutation.platform === "codex"
|
|
1297
|
+
? isCodexWorkitInstalled(res.home, res.env)
|
|
1298
|
+
: mutation.platform === "pi"
|
|
1299
|
+
? isPiWorkitInstalled(res.home, res.env)
|
|
1300
|
+
: true;
|
|
1301
|
+
if (!verified) {
|
|
1302
|
+
entries.push({
|
|
1303
|
+
platform: mutation.platform,
|
|
1304
|
+
file: mutation.platform,
|
|
1305
|
+
status: "Failed",
|
|
1306
|
+
detail:
|
|
1307
|
+
"the host command succeeded, but Workit registration was not found; inspect the host's native package list and retry",
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1120
1312
|
if (mutation.type !== "register-platform" && mutation.type !== "install-adapter") {
|
|
1121
1313
|
entries.push(applyMutation(mutation));
|
|
1122
1314
|
continue;
|
|
@@ -1141,7 +1333,7 @@ export function applySetupPreview(
|
|
|
1141
1333
|
}
|
|
1142
1334
|
let root = rootFor.get(platform);
|
|
1143
1335
|
if (root === undefined) {
|
|
1144
|
-
root = adapterRoot(platform, res);
|
|
1336
|
+
root = adapterRoot(platform, res, options.preferInstalledAdapter);
|
|
1145
1337
|
rootFor.set(platform, root);
|
|
1146
1338
|
}
|
|
1147
1339
|
if (root === null) {
|
package/src/core/templates.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { configDir } from "./config";
|
|
4
4
|
import { assetRoot } from "./package-root";
|
|
@@ -20,28 +20,3 @@ export const readTemplate = (
|
|
|
20
20
|
content: readFileSync(path.join(repoRoot, "templates", `${name}.md`), "utf8"),
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
-
|
|
24
|
-
export const writeTemplate = (
|
|
25
|
-
name: TemplateName,
|
|
26
|
-
content: string,
|
|
27
|
-
confirmed: boolean,
|
|
28
|
-
): { ok: true; path: string } | { ok: false; error: string } => {
|
|
29
|
-
if (!confirmed) return { ok: false, error: "confirmed: true required" };
|
|
30
|
-
const file = templatePath(name);
|
|
31
|
-
mkdirSync(path.dirname(file), { recursive: true });
|
|
32
|
-
writeFileSync(file, content, "utf8");
|
|
33
|
-
return { ok: true, path: file };
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const listTemplates = (): {
|
|
37
|
-
name: TemplateName;
|
|
38
|
-
source: "config" | "repo" | "missing";
|
|
39
|
-
path: string;
|
|
40
|
-
}[] =>
|
|
41
|
-
(["issue-update", "greeting", "headers"] as TemplateName[]).map((name) => {
|
|
42
|
-
const cfg = templatePath(name);
|
|
43
|
-
const repoFile = path.join(repoRoot, "templates", `${name}.md`);
|
|
44
|
-
if (existsSync(cfg)) return { name, source: "config", path: cfg };
|
|
45
|
-
if (existsSync(repoFile)) return { name, source: "repo", path: repoFile };
|
|
46
|
-
return { name, source: "missing", path: cfg };
|
|
47
|
-
});
|
package/src/core/vcs-config.ts
CHANGED
|
@@ -191,10 +191,8 @@ export function vcsConfig(mode: "load" | "summary" | "resolve", cwd?: string): R
|
|
|
191
191
|
(hasWorkspacePolicy ? policyDefault : (cfg.defaultTargetBranch ?? policyDefault)) ??
|
|
192
192
|
"develop",
|
|
193
193
|
);
|
|
194
|
-
const linkIssues =
|
|
195
|
-
|
|
196
|
-
const youtrackBaseUrl =
|
|
197
|
-
provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
194
|
+
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
195
|
+
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
198
196
|
// github issues path only when BOTH providers are github (mirrors WorkspaceConfig.issues).
|
|
199
197
|
let issuesProvider: string | null = null;
|
|
200
198
|
let linkOnPr: boolean | null = null;
|
|
@@ -305,10 +303,6 @@ export function vcsCliIdentity(cwd?: string): Record<string, any> {
|
|
|
305
303
|
}
|
|
306
304
|
|
|
307
305
|
/** Legacy command name, now verifies native CLI auth rather than a separate token. */
|
|
308
|
-
export async function vcsVerifyToken(): Promise<Record<string, any>> {
|
|
309
|
-
return vcsCliIdentity();
|
|
310
|
-
}
|
|
311
|
-
|
|
312
306
|
/** Port of scripts/vcs/merged-style.sh — recent merged MR/PR bodies for style reference. */
|
|
313
307
|
export function mergedPrStyle(limit = 6, cwd?: string): Record<string, any> {
|
|
314
308
|
const cfg = vcsConfig("load", cwd);
|
package/src/core/workspaces.ts
CHANGED
|
@@ -134,13 +134,6 @@ const workspaceConfigSchema = z
|
|
|
134
134
|
})
|
|
135
135
|
.passthrough()
|
|
136
136
|
.superRefine((workspace, context) => {
|
|
137
|
-
if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
|
|
138
|
-
context.addIssue({
|
|
139
|
-
code: "custom",
|
|
140
|
-
path: ["youtrack"],
|
|
141
|
-
message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
137
|
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
145
138
|
context.addIssue({
|
|
146
139
|
code: "custom",
|
|
@@ -446,23 +439,20 @@ const selectWorkspaceMatch = <T extends { name: string; glob: string }>(
|
|
|
446
439
|
return mostSpecific[0];
|
|
447
440
|
};
|
|
448
441
|
|
|
449
|
-
/**
|
|
450
|
-
export const
|
|
442
|
+
/** Shared pure workspace matcher for draft previews and disk-backed resolution. */
|
|
443
|
+
export const resolveWorkspaceFromEntries = <T extends { name: string; glob: string }>(
|
|
451
444
|
cwd: string,
|
|
452
|
-
|
|
445
|
+
entries: readonly T[],
|
|
453
446
|
workspaceName?: string,
|
|
454
|
-
):
|
|
447
|
+
): T | null => {
|
|
455
448
|
// macOS/Windows tmpdir symlinks (/var -> /private/var): git's
|
|
456
449
|
// --show-toplevel returns the realpath while config globs are usually
|
|
457
450
|
// written with the logical path, so a workspace would silently stop
|
|
458
|
-
// matching on macOS. Match both forms on each side
|
|
459
|
-
// docs-migration escape-guard realpath comparison; on Linux both forms
|
|
451
|
+
// matching on macOS. Match both forms on each side; on Linux both forms
|
|
460
452
|
// are identical so behavior is unchanged.
|
|
461
|
-
const result = readWorkspacesResult(dir);
|
|
462
|
-
if (result.status === "malformed" || result.status === "invalid") throw new Error(result.error);
|
|
463
453
|
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
464
|
-
const matches:
|
|
465
|
-
for (const ws of
|
|
454
|
+
const matches: T[] = [];
|
|
455
|
+
for (const ws of entries) {
|
|
466
456
|
const glob = ws.glob.replaceAll("\\", "/");
|
|
467
457
|
const canonical = canonicalGlob(glob);
|
|
468
458
|
let matched = false;
|
|
@@ -480,6 +470,17 @@ export const resolveWorkspaceFrom = (
|
|
|
480
470
|
return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
|
|
481
471
|
};
|
|
482
472
|
|
|
473
|
+
/** Match a cwd against the workspaces.json under an explicit config dir. */
|
|
474
|
+
export const resolveWorkspaceFrom = (
|
|
475
|
+
cwd: string,
|
|
476
|
+
dir: string,
|
|
477
|
+
workspaceName?: string,
|
|
478
|
+
): WorkspaceConfig | null => {
|
|
479
|
+
const result = readWorkspacesResult(dir);
|
|
480
|
+
if (result.status === "malformed" || result.status === "invalid") throw new Error(result.error);
|
|
481
|
+
return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
|
|
482
|
+
};
|
|
483
|
+
|
|
483
484
|
export const resolveWorkspace = (
|
|
484
485
|
cwd: string,
|
|
485
486
|
workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined,
|
package/src/core/config-guard.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { initStatus } from "./init";
|
|
2
|
-
|
|
3
|
-
export const ALL_ITEM_IDS = ["youtrack_json", "youtrack_token", "vcs_json"];
|
|
4
|
-
export const CONFIG_GAP_MARKER = "workflow config missing";
|
|
5
|
-
|
|
6
|
-
export function describeConfigGaps(scope?: string[]): { missing: string[]; ok: boolean } {
|
|
7
|
-
const all = scope ?? ALL_ITEM_IDS;
|
|
8
|
-
try {
|
|
9
|
-
const status: unknown = initStatus();
|
|
10
|
-
if (!status || typeof status !== "object" || (status as Record<string, unknown>).error)
|
|
11
|
-
return { missing: all, ok: false };
|
|
12
|
-
const items = (status as Record<string, unknown>).items;
|
|
13
|
-
if (!Array.isArray(items) || items.length === 0) return { missing: all, ok: false };
|
|
14
|
-
const known = all;
|
|
15
|
-
const missing = items
|
|
16
|
-
.filter((item) => item && (item as Record<string, unknown>).ok === false)
|
|
17
|
-
.map((item) => String((item as Record<string, unknown>).id))
|
|
18
|
-
.filter((id) => known.includes(id));
|
|
19
|
-
return { missing, ok: missing.length === 0 };
|
|
20
|
-
} catch {
|
|
21
|
-
return { missing: all, ok: false };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function configGuardError(missing: string[]): string {
|
|
26
|
-
return `${CONFIG_GAP_MARKER}: ${missing.join(", ")}. Run \`npx workit init\` or \`/wk-init\` to configure.`;
|
|
27
|
-
}
|
package/src/core/doc-render.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export const MAX_LINES = 150;
|
|
2
|
-
export const MAX_BYTES = 8192;
|
|
3
|
-
export const MAX_MERMAID = 3;
|
|
4
|
-
|
|
5
|
-
const MERMAID_FENCE = /^```mermaid\s*$/gm;
|
|
6
|
-
|
|
7
|
-
export const shouldRenderDoc = (text: string): boolean => {
|
|
8
|
-
const lines = text.split(/\r?\n/);
|
|
9
|
-
if (lines[lines.length - 1] === "") lines.pop();
|
|
10
|
-
if (lines.length > MAX_LINES) return false;
|
|
11
|
-
if (Buffer.byteLength(text, "utf8") > MAX_BYTES) return false;
|
|
12
|
-
const mermaidCount = text.match(MERMAID_FENCE)?.length ?? 0;
|
|
13
|
-
return mermaidCount <= MAX_MERMAID;
|
|
14
|
-
};
|