strikethroo 3.18.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist-web/assets/{arc-BlSNa2QY.js → arc-XmjVSUrZ.js} +1 -1
  2. package/dist-web/assets/{architectureDiagram-3BPJPVTR-CPcCF2a4.js → architectureDiagram-3BPJPVTR-BkIMNPZQ.js} +1 -1
  3. package/dist-web/assets/{blockDiagram-GPEHLZMM-ECDR0edZ.js → blockDiagram-GPEHLZMM-CIUhAyCy.js} +1 -1
  4. package/dist-web/assets/{c4Diagram-AAUBKEIU-oM3VnEAQ.js → c4Diagram-AAUBKEIU-DHmyXhPl.js} +1 -1
  5. package/dist-web/assets/channel-BluR9kJ8.js +1 -0
  6. package/dist-web/assets/{chunk-2J33WTMH-DHk5SJLz.js → chunk-2J33WTMH-CsadzJpE.js} +1 -1
  7. package/dist-web/assets/{chunk-4BX2VUAB-CjBwgkMu.js → chunk-4BX2VUAB-rdZMTiJd.js} +1 -1
  8. package/dist-web/assets/{chunk-55IACEB6-D95zqyiV.js → chunk-55IACEB6-BFgbJmlS.js} +1 -1
  9. package/dist-web/assets/{chunk-727SXJPM-DCCPRUfv.js → chunk-727SXJPM-CaGOdOo-.js} +1 -1
  10. package/dist-web/assets/{chunk-AQP2D5EJ-qur7KQyD.js → chunk-AQP2D5EJ-Dj1Z_buE.js} +1 -1
  11. package/dist-web/assets/{chunk-FMBD7UC4-D46Su7Fv.js → chunk-FMBD7UC4-ByPFO0XR.js} +1 -1
  12. package/dist-web/assets/{chunk-ND2GUHAM-BioGsqxt.js → chunk-ND2GUHAM-CRT0WqT3.js} +1 -1
  13. package/dist-web/assets/{chunk-QZHKN3VN-C8LdDiqn.js → chunk-QZHKN3VN-DeBs7Z-A.js} +1 -1
  14. package/dist-web/assets/classDiagram-4FO5ZUOK-dvsWFqDg.js +1 -0
  15. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-dvsWFqDg.js +1 -0
  16. package/dist-web/assets/{cose-bilkent-S5V4N54A-ccY03BRh.js → cose-bilkent-S5V4N54A-8sFxzH_F.js} +1 -1
  17. package/dist-web/assets/{dagre-BM42HDAG-BYLmCViB.js → dagre-BM42HDAG-CXKxHlld.js} +1 -1
  18. package/dist-web/assets/{diagram-2AECGRRQ-DTvenvXR.js → diagram-2AECGRRQ-DtcDQjmq.js} +1 -1
  19. package/dist-web/assets/{diagram-5GNKFQAL-CCzsYDmr.js → diagram-5GNKFQAL-C5z5tfjo.js} +1 -1
  20. package/dist-web/assets/{diagram-KO2AKTUF-QSff7J_V.js → diagram-KO2AKTUF-DzGoK3-W.js} +1 -1
  21. package/dist-web/assets/{diagram-LMA3HP47-CVqlEmu2.js → diagram-LMA3HP47-CClXYqlA.js} +1 -1
  22. package/dist-web/assets/{diagram-OG6HWLK6-dBVz-I46.js → diagram-OG6HWLK6-CCOd6tb5.js} +1 -1
  23. package/dist-web/assets/{erDiagram-TEJ5UH35-B_y4ftNu.js → erDiagram-TEJ5UH35-DyUNZPGi.js} +1 -1
  24. package/dist-web/assets/{flowDiagram-I6XJVG4X-M410hT_n.js → flowDiagram-I6XJVG4X-DWE9txcF.js} +1 -1
  25. package/dist-web/assets/{ganttDiagram-6RSMTGT7-qiri2OJa.js → ganttDiagram-6RSMTGT7-B-OtEqRy.js} +1 -1
  26. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-FqGGBobG.js → gitGraphDiagram-PVQCEYII-F9vafo91.js} +1 -1
  27. package/dist-web/assets/{index-BaFSLlgl.js → index-CwMlRsHB.js} +1 -1
  28. package/dist-web/assets/{index-DrUIu0u6.js → index-D_5U356C.js} +4 -4
  29. package/dist-web/assets/{index-1SV04K_c.js → index-DahCY-9n.js} +1 -1
  30. package/dist-web/assets/{infoDiagram-5YYISTIA-CczHqRr1.js → infoDiagram-5YYISTIA-BqiJJ4mQ.js} +1 -1
  31. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-ty1DRy6n.js → ishikawaDiagram-YF4QCWOH-BJhigZjk.js} +1 -1
  32. package/dist-web/assets/{journeyDiagram-JHISSGLW-D_JYvt6i.js → journeyDiagram-JHISSGLW-DbnfMDvR.js} +1 -1
  33. package/dist-web/assets/{kanban-definition-UN3LZRKU-CT67nUTK.js → kanban-definition-UN3LZRKU-CWbRbJ6f.js} +1 -1
  34. package/dist-web/assets/{linear-B1HaE_t1.js → linear-D3Xq7OUM.js} +1 -1
  35. package/dist-web/assets/{mermaid.core-Dumx_iKV.js → mermaid.core-CT0YFN3s.js} +4 -4
  36. package/dist-web/assets/{mindmap-definition-RKZ34NQL-CVulJo63.js → mindmap-definition-RKZ34NQL-D6AZLTGR.js} +1 -1
  37. package/dist-web/assets/{pieDiagram-4H26LBE5-C1yD5ntZ.js → pieDiagram-4H26LBE5-NGxhfGd1.js} +1 -1
  38. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-B7PoiM8c.js → quadrantDiagram-W4KKPZXB-jNVzj-DZ.js} +1 -1
  39. package/dist-web/assets/{requirementDiagram-4Y6WPE33-COvHb3Mv.js → requirementDiagram-4Y6WPE33-cxYkgHCv.js} +1 -1
  40. package/dist-web/assets/{sankeyDiagram-5OEKKPKP-CAnwm5-J.js → sankeyDiagram-5OEKKPKP-CO7LMTUX.js} +1 -1
  41. package/dist-web/assets/{sequenceDiagram-3UESZ5HK-C2KFLIUq.js → sequenceDiagram-3UESZ5HK-C8d8VIsZ.js} +1 -1
  42. package/dist-web/assets/{stateDiagram-AJRCARHV-CqmhS5P6.js → stateDiagram-AJRCARHV-DVxepHih.js} +1 -1
  43. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-OpTLx_eY.js +1 -0
  44. package/dist-web/assets/{timeline-definition-PNZ67QCA-DvPre9M_.js → timeline-definition-PNZ67QCA-Bef04SiL.js} +1 -1
  45. package/dist-web/assets/{vennDiagram-CIIHVFJN-Top6i7KH.js → vennDiagram-CIIHVFJN-DlZHmyp0.js} +1 -1
  46. package/dist-web/assets/{wardley-L42UT6IY-_jVLsWqf.js → wardley-L42UT6IY-ChkIPtT_.js} +1 -1
  47. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-BV4e5mP6.js → wardleyDiagram-YWT4CUSO-D67J0WGX.js} +1 -1
  48. package/dist-web/assets/{xychartDiagram-2RQKCTM6-Bn1hw4Jk.js → xychartDiagram-2RQKCTM6-BoNIIDDe.js} +1 -1
  49. package/dist-web/index.html +1 -1
  50. package/package.json +1 -1
  51. package/templates/harness/skills/st-code-review/scripts/code-review.cjs +2710 -264
  52. package/templates/harness/skills/st-execute-blueprint/scripts/dispatch-task-execution.cjs +420 -213
  53. package/templates/harness/skills/st-execute-task/scripts/dispatch-task-execution.cjs +420 -213
  54. package/templates/harness/skills/st-full-workflow/scripts/dispatch-task-execution.cjs +420 -213
  55. package/templates/strikethroo/config/config.yaml +95 -6
  56. package/templates/strikethroo/gitignore +13 -10
  57. package/dist-web/assets/channel-CmU4mBgT.js +0 -1
  58. package/dist-web/assets/classDiagram-4FO5ZUOK-D_FMKQsB.js +0 -1
  59. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-D_FMKQsB.js +0 -1
  60. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-C3Kw8ZDR.js +0 -1
@@ -3342,8 +3342,8 @@ var require_excerpt = __commonJS({
3342
3342
  if (sep2 == null && (opts.excerpt === false || opts.excerpt == null)) {
3343
3343
  return file;
3344
3344
  }
3345
- const delimiter2 = typeof opts.excerpt === "string" ? opts.excerpt : sep2 || opts.delimiters[0];
3346
- const idx = file.content.indexOf(delimiter2);
3345
+ const delimiter3 = typeof opts.excerpt === "string" ? opts.excerpt : sep2 || opts.delimiters[0];
3346
+ const idx = file.content.indexOf(delimiter3);
3347
3347
  if (idx !== -1) {
3348
3348
  file.excerpt = file.content.slice(0, idx);
3349
3349
  }
@@ -3407,7 +3407,7 @@ var require_parse = __commonJS({
3407
3407
  var require_gray_matter = __commonJS({
3408
3408
  "node_modules/gray-matter/index.js"(exports2, module2) {
3409
3409
  "use strict";
3410
- var fs5 = require("fs");
3410
+ var fs6 = require("fs");
3411
3411
  var sections = require_section_matter();
3412
3412
  var defaults = require_defaults();
3413
3413
  var stringify = require_stringify();
@@ -3491,7 +3491,7 @@ var require_gray_matter = __commonJS({
3491
3491
  return stringify(file, data, options2);
3492
3492
  };
3493
3493
  matter2.read = function(filepath, options2) {
3494
- const str2 = fs5.readFileSync(filepath, "utf8");
3494
+ const str2 = fs6.readFileSync(filepath, "utf8");
3495
3495
  const file = matter2(str2, options2);
3496
3496
  file.path = filepath;
3497
3497
  return file;
@@ -3525,8 +3525,8 @@ __export(dispatch_task_execution_exports, {
3525
3525
  resolveDispatchRoute: () => resolveDispatchRoute
3526
3526
  });
3527
3527
  module.exports = __toCommonJS(dispatch_task_execution_exports);
3528
- var fs4 = __toESM(require("fs"));
3529
- var path5 = __toESM(require("path"));
3528
+ var fs5 = __toESM(require("fs"));
3529
+ var path6 = __toESM(require("path"));
3530
3530
  var import_gray_matter = __toESM(require_gray_matter());
3531
3531
 
3532
3532
  // src/types.ts
@@ -3664,6 +3664,7 @@ var EXTERNAL_HARNESS_ADAPTERS = {
3664
3664
  "claude",
3665
3665
  [
3666
3666
  "-p",
3667
+ ...request.cliArgs,
3667
3668
  ...modelArgv(request.model),
3668
3669
  ...request.reasoningEffort === void 0 ? [] : ["--effort", request.reasoningEffort]
3669
3670
  ],
@@ -3677,6 +3678,7 @@ var EXTERNAL_HARNESS_ADAPTERS = {
3677
3678
  "codex",
3678
3679
  [
3679
3680
  "exec",
3681
+ ...request.cliArgs,
3680
3682
  ...modelArgv(request.model),
3681
3683
  ...request.reasoningEffort === void 0 ? [] : ["--config", `model_reasoning_effort=${request.reasoningEffort}`],
3682
3684
  "-"
@@ -3687,19 +3689,23 @@ var EXTERNAL_HARNESS_ADAPTERS = {
3687
3689
  },
3688
3690
  cursor: {
3689
3691
  executable: "cursor-agent",
3690
- buildCommand: (request) => command("cursor-agent", ["--print", ...modelArgv(request.model)], request),
3692
+ buildCommand: (request) => command(
3693
+ "cursor-agent",
3694
+ ["--print", ...request.cliArgs, ...modelArgv(request.model)],
3695
+ request
3696
+ ),
3691
3697
  authenticationArgv: () => ["status"]
3692
3698
  },
3693
3699
  gemini: {
3694
3700
  executable: "gemini",
3695
3701
  // The empty positional prompt is the existing contract — content travels on
3696
3702
  // stdin. It stays even when the model pair is dropped.
3697
- buildCommand: (request) => command("gemini", ["--prompt", "", ...modelArgv(request.model)], request),
3703
+ buildCommand: (request) => command("gemini", ["--prompt", "", ...request.cliArgs, ...modelArgv(request.model)], request),
3698
3704
  authenticationArgv: () => ["auth", "status"]
3699
3705
  },
3700
3706
  copilot: {
3701
3707
  executable: "copilot",
3702
- buildCommand: (request) => command("copilot", ["-p", "", ...modelArgv(request.model)], request),
3708
+ buildCommand: (request) => command("copilot", ["-p", "", ...request.cliArgs, ...modelArgv(request.model)], request),
3703
3709
  authenticationArgv: () => ["auth", "status"]
3704
3710
  },
3705
3711
  opencode: {
@@ -3708,6 +3714,7 @@ var EXTERNAL_HARNESS_ADAPTERS = {
3708
3714
  "opencode",
3709
3715
  [
3710
3716
  "run",
3717
+ ...request.cliArgs,
3711
3718
  ...modelArgv(request.model),
3712
3719
  ...request.reasoningEffort === void 0 ? [] : ["--variant", request.reasoningEffort],
3713
3720
  "-"
@@ -3723,27 +3730,30 @@ if (adapterKeys.join("\0") !== harnessKeys.join("\0")) {
3723
3730
  throw new Error("External harness adapter registry does not cover SUPPORTED_HARNESSES exactly.");
3724
3731
  }
3725
3732
  var taskCommandRequest = (request) => ({
3733
+ cliArgs: request.cliArgs ?? [],
3726
3734
  model: request.model,
3727
3735
  reasoningEffort: request.reasoningEffort,
3728
3736
  workspace: request.workspace,
3729
3737
  prompt: taskPrompt(request)
3730
3738
  });
3731
- var executableOnPath = (executable) => (process.env.PATH ?? "").split(path2.delimiter).some((directory) => {
3732
- if (!directory) return false;
3733
- const candidate = path2.join(directory, executable);
3734
- try {
3735
- return fs.statSync(candidate).isFile();
3736
- } catch {
3737
- return false;
3739
+ var executableOnPath = (executable) => (/[\\/]/.test(executable) ? [""] : (process.env.PATH ?? "").split(path2.delimiter)).some(
3740
+ (directory) => {
3741
+ if (!directory && !/[\\/]/.test(executable)) return false;
3742
+ const candidate = directory === "" ? executable : path2.join(directory, executable);
3743
+ try {
3744
+ return fs.statSync(candidate).isFile();
3745
+ } catch {
3746
+ return false;
3747
+ }
3738
3748
  }
3739
- });
3749
+ );
3740
3750
  var CAPTURED_STDOUT_LIMIT = 262144;
3741
3751
  var STDIO_SLOTS = {
3742
3752
  ignore: { stdout: "ignore" },
3743
3753
  inherit: { stdout: "inherit" },
3744
3754
  capture: { stdout: "pipe" }
3745
3755
  };
3746
- var runProcess = (executable, argv, cwd, stdin, outputMode = "ignore") => new Promise((resolve3, reject) => {
3756
+ var runProcess = (executable, argv, cwd, stdin, outputMode = "ignore") => new Promise((resolve4, reject) => {
3747
3757
  let settled = false;
3748
3758
  const fail = (error) => {
3749
3759
  if (settled) return;
@@ -3775,7 +3785,7 @@ var runProcess = (executable, argv, cwd, stdin, outputMode = "ignore") => new Pr
3775
3785
  child.once("close", (code) => {
3776
3786
  if (settled) return;
3777
3787
  settled = true;
3778
- resolve3({
3788
+ resolve4({
3779
3789
  exitCode: code ?? 1,
3780
3790
  ...outputMode === "capture" ? { stdout: captured } : {}
3781
3791
  });
@@ -3826,11 +3836,12 @@ var prepareLaunch = async (harness, input, active, guard) => {
3826
3836
  }
3827
3837
  const blocked = guard?.();
3828
3838
  if (blocked) return blocked;
3829
- if (!active.executableExists(adapter.executable)) {
3839
+ const executable = adapter.executable;
3840
+ if (!active.executableExists(executable)) {
3830
3841
  return {
3831
3842
  kind: "fallback",
3832
3843
  reason: "executable-unavailable",
3833
- detail: `${adapter.executable} is unavailable.`
3844
+ detail: `${executable} is unavailable.`
3834
3845
  };
3835
3846
  }
3836
3847
  const commandSpec = adapter.buildCommand(input);
@@ -3874,191 +3885,14 @@ var dispatchExternalTask = async (request, overrides = {}) => {
3874
3885
  };
3875
3886
 
3876
3887
  // src/skill-scripts/shared/harness-availability.ts
3877
- var fs2 = __toESM(require("fs"));
3878
- var path3 = __toESM(require("path"));
3888
+ var import_crypto2 = require("crypto");
3879
3889
  var import_child_process3 = require("child_process");
3880
- var AVAILABILITY_REGISTRY_VERSION = 1;
3881
- var AVAILABLE_TTL_MS = 30 * 60 * 1e3;
3882
- var UNAVAILABLE_TTL_MS = 5 * 60 * 1e3;
3883
- var PROBE_TIMEOUT_MS = 2e4;
3884
- var AVAILABILITY_CACHE_RELATIVE_PATH = path3.join("runtime", "harness-availability.json");
3885
- var PROBE_PROMPT = "Reply with OK.";
3886
- var probeCommand = (executable, argv, cwd, stdin = PROBE_PROMPT) => ({ executable, argv, cwd, stdin });
3887
- var HARNESS_AVAILABILITY_REGISTRY = {
3888
- claude: {
3889
- version: AVAILABILITY_REGISTRY_VERSION,
3890
- executable: "claude",
3891
- buildCommand: (cwd) => probeCommand("claude", ["-p"], cwd)
3892
- },
3893
- codex: {
3894
- version: AVAILABILITY_REGISTRY_VERSION,
3895
- executable: "codex",
3896
- buildCommand: (cwd) => probeCommand("codex", ["exec", "-"], cwd)
3897
- },
3898
- cursor: {
3899
- version: AVAILABILITY_REGISTRY_VERSION,
3900
- executable: "cursor-agent",
3901
- buildCommand: (cwd) => probeCommand("cursor-agent", ["--print"], cwd)
3902
- },
3903
- gemini: {
3904
- version: AVAILABILITY_REGISTRY_VERSION,
3905
- executable: "gemini",
3906
- buildCommand: (cwd) => probeCommand("gemini", ["--prompt", PROBE_PROMPT], cwd, "")
3907
- },
3908
- copilot: {
3909
- version: AVAILABILITY_REGISTRY_VERSION,
3910
- executable: "copilot",
3911
- buildCommand: (cwd) => probeCommand("copilot", ["-p", PROBE_PROMPT], cwd, "")
3912
- },
3913
- opencode: {
3914
- version: AVAILABILITY_REGISTRY_VERSION,
3915
- executable: "opencode",
3916
- buildCommand: (cwd) => probeCommand("opencode", ["run", "-"], cwd)
3917
- }
3918
- };
3919
- var registryKeys = Object.keys(HARNESS_AVAILABILITY_REGISTRY).sort();
3920
- var harnessKeys2 = [...SUPPORTED_HARNESSES].sort();
3921
- if (registryKeys.join("\0") !== harnessKeys2.join("\0")) {
3922
- throw new Error("Harness availability registry does not cover SUPPORTED_HARNESSES exactly.");
3923
- }
3924
- for (const harness of SUPPORTED_HARNESSES) {
3925
- const availability = HARNESS_AVAILABILITY_REGISTRY[harness];
3926
- if (availability.executable !== EXTERNAL_HARNESS_ADAPTERS[harness].executable) {
3927
- throw new Error(`Harness availability executable disagrees with the ${harness} adapter.`);
3928
- }
3929
- }
3930
- var safeReason = (value, fallback) => {
3931
- if (typeof value !== "string") return fallback;
3932
- const firstLine = value.replace(/[\r\n]+/g, " ").trim().slice(0, 200);
3933
- return firstLine || fallback;
3934
- };
3935
- var isOutcome = (value) => {
3936
- if (!value || typeof value !== "object") return false;
3937
- const entry = value;
3938
- return typeof entry.available === "boolean" && typeof entry.observedAt === "number" && Number.isFinite(entry.observedAt) && typeof entry.expiresAt === "number" && Number.isFinite(entry.expiresAt) && typeof entry.reason === "string";
3939
- };
3940
- var readCache = (cachePath) => {
3941
- try {
3942
- const parsed = JSON.parse(fs2.readFileSync(cachePath, "utf8"));
3943
- if (!parsed || typeof parsed !== "object") throw new Error("invalid cache");
3944
- const record = parsed;
3945
- if (record.version !== 1 || !record.harnesses || typeof record.harnesses !== "object") {
3946
- throw new Error("invalid cache");
3947
- }
3948
- const harnesses = {};
3949
- for (const harness of SUPPORTED_HARNESSES) {
3950
- const candidate = record.harnesses[harness];
3951
- if (isOutcome(candidate)) harnesses[harness] = candidate;
3952
- }
3953
- return { version: 1, harnesses };
3954
- } catch {
3955
- return { version: 1, harnesses: {} };
3956
- }
3957
- };
3958
- var writeCache = (cachePath, harness, outcome) => {
3959
- fs2.mkdirSync(path3.dirname(cachePath), { recursive: true });
3960
- const cache = readCache(cachePath);
3961
- const existing = cache.harnesses[harness];
3962
- if (!existing || existing.observedAt <= outcome.observedAt) {
3963
- cache.harnesses[harness] = {
3964
- available: outcome.available,
3965
- observedAt: outcome.observedAt,
3966
- expiresAt: outcome.expiresAt,
3967
- reason: outcome.reason
3968
- };
3969
- }
3970
- const temporary = `${cachePath}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
3971
- try {
3972
- fs2.writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}
3973
- `, { mode: 384 });
3974
- fs2.renameSync(temporary, cachePath);
3975
- } finally {
3976
- try {
3977
- fs2.unlinkSync(temporary);
3978
- } catch {
3979
- }
3980
- }
3981
- };
3982
- var runProbe = (command2, timeoutMs) => new Promise((resolve3) => {
3983
- let settled = false;
3984
- let diagnostics = "";
3985
- const finish = (result) => {
3986
- if (settled) return;
3987
- settled = true;
3988
- resolve3(result);
3989
- };
3990
- const child = (0, import_child_process3.spawn)(command2.executable, command2.argv, {
3991
- cwd: command2.cwd,
3992
- shell: false,
3993
- stdio: ["pipe", "ignore", "pipe"]
3994
- });
3995
- const timer = setTimeout(() => {
3996
- child.kill("SIGKILL");
3997
- finish({ exitCode: 1, timedOut: true, detail: "Probe timed out." });
3998
- }, timeoutMs);
3999
- child.stderr?.on("data", (chunk) => {
4000
- if (diagnostics.length < 400) diagnostics += String(chunk).slice(0, 400 - diagnostics.length);
4001
- });
4002
- child.once("error", (error) => {
4003
- clearTimeout(timer);
4004
- finish({ exitCode: 1, detail: error.message });
4005
- });
4006
- child.once("close", (code) => {
4007
- clearTimeout(timer);
4008
- finish({ exitCode: code ?? 1, detail: diagnostics });
4009
- });
4010
- child.stdin?.on("error", () => void 0);
4011
- child.stdin?.end(command2.stdin);
4012
- });
4013
- var defaultDependencies = {
4014
- now: Date.now,
4015
- runProbe
4016
- };
4017
- var checkHarnessAvailability = async (request, overrides = {}) => {
4018
- const active = { ...defaultDependencies, ...overrides };
4019
- const now = active.now();
4020
- if (request.harness === void 0 || request.harness === request.currentHarness) {
4021
- return {
4022
- harness: request.harness ?? request.currentHarness,
4023
- available: true,
4024
- observedAt: now,
4025
- expiresAt: now,
4026
- reason: "Native/current harness targets do not require a probe.",
4027
- source: "bypass"
4028
- };
4029
- }
4030
- const harness = request.harness;
4031
- const cachePath = path3.join(request.strikethrooRoot, AVAILABILITY_CACHE_RELATIVE_PATH);
4032
- const cached = readCache(cachePath).harnesses[harness];
4033
- if (cached && cached.expiresAt > now) return { harness, ...cached, source: "cache" };
4034
- const definition = HARNESS_AVAILABILITY_REGISTRY[harness];
4035
- let probe;
4036
- try {
4037
- probe = await active.runProbe(definition.buildCommand(request.workspace), PROBE_TIMEOUT_MS);
4038
- } catch (error) {
4039
- probe = { exitCode: 1, detail: error instanceof Error ? error.message : String(error) };
4040
- }
4041
- const available = probe.exitCode === 0 && !probe.timedOut;
4042
- const reason = available ? "Harness probe succeeded." : safeReason(
4043
- probe.detail,
4044
- probe.timedOut ? "Harness probe timed out." : "Harness probe failed."
4045
- );
4046
- const outcome = {
4047
- harness,
4048
- available,
4049
- observedAt: now,
4050
- expiresAt: now + (available ? AVAILABLE_TTL_MS : UNAVAILABLE_TTL_MS),
4051
- reason,
4052
- source: "probe"
4053
- };
4054
- try {
4055
- writeCache(cachePath, harness, outcome);
4056
- } catch {
4057
- }
4058
- return outcome;
4059
- };
3890
+ var fs4 = __toESM(require("fs"));
3891
+ var os = __toESM(require("os"));
3892
+ var path5 = __toESM(require("path"));
4060
3893
 
4061
- // src/skill-scripts/shared/execution-routing.ts
3894
+ // src/skill-scripts/shared/harness-configuration.ts
3895
+ var import_crypto = require("crypto");
4062
3896
  var fs3 = __toESM(require("fs"));
4063
3897
  var path4 = __toESM(require("path"));
4064
3898
 
@@ -6285,7 +6119,9 @@ var DEFAULT_DUMP_OPTIONS = {
6285
6119
  };
6286
6120
 
6287
6121
  // src/skill-scripts/shared/execution-routing.ts
6288
- var WORKSPACE_CONFIG_RELPATH = path4.join("config", "config.yaml");
6122
+ var fs2 = __toESM(require("fs"));
6123
+ var path3 = __toESM(require("path"));
6124
+ var WORKSPACE_CONFIG_RELPATH = path3.join("config", "config.yaml");
6289
6125
  var EXECUTION_ROUTING_SECTION = "execution_routing";
6290
6126
  var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6291
6127
  var isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
@@ -6360,10 +6196,10 @@ var validateProfile = (name, raw, supportedHarnesses, errors) => {
6360
6196
  return { name, description: raw.description.trim(), targets };
6361
6197
  };
6362
6198
  var loadRoutingConfig = (strikethrooRoot, supportedHarnesses) => {
6363
- const configPath = path4.join(strikethrooRoot, WORKSPACE_CONFIG_RELPATH);
6199
+ const configPath = path3.join(strikethrooRoot, WORKSPACE_CONFIG_RELPATH);
6364
6200
  let contents;
6365
6201
  try {
6366
- contents = fs3.readFileSync(configPath, "utf8");
6202
+ contents = fs2.readFileSync(configPath, "utf8");
6367
6203
  } catch {
6368
6204
  return { kind: "no-config" };
6369
6205
  }
@@ -6430,6 +6266,349 @@ var loadRoutingConfig = (strikethrooRoot, supportedHarnesses) => {
6430
6266
  return { kind: "config", config };
6431
6267
  };
6432
6268
 
6269
+ // src/skill-scripts/shared/harness-configuration.ts
6270
+ var HARNESS_CONFIGURATION_SECTION = "harnesses";
6271
+ var HARNESS_CONFIGURATION_NORMALIZATION_VERSION = 1;
6272
+ var isPlainObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6273
+ var hashHarnessCliArgs = (harness, cliArgs, normalizationVersion = HARNESS_CONFIGURATION_NORMALIZATION_VERSION) => (0, import_crypto.createHash)("sha256").update(
6274
+ JSON.stringify({
6275
+ schema: normalizationVersion,
6276
+ harness,
6277
+ cliArgs
6278
+ }),
6279
+ "utf8"
6280
+ ).digest("hex");
6281
+ var normalizeInvocation = (harness, cliArgs) => {
6282
+ const immutableArgs = Object.freeze([...cliArgs]);
6283
+ return Object.freeze({
6284
+ cliArgs: immutableArgs,
6285
+ cliArgsHash: hashHarnessCliArgs(harness, immutableArgs)
6286
+ });
6287
+ };
6288
+ var emptyConfiguration = () => Object.freeze(
6289
+ Object.fromEntries(
6290
+ SUPPORTED_HARNESSES.map((harness) => [harness, normalizeInvocation(harness, [])])
6291
+ )
6292
+ );
6293
+ var validateHarnessEntry = (harness, raw, errors) => {
6294
+ const entryPath = `config.yaml ${HARNESS_CONFIGURATION_SECTION}.${harness}`;
6295
+ if (!isPlainObject3(raw)) {
6296
+ errors.push(`${entryPath} must be a mapping.`);
6297
+ return null;
6298
+ }
6299
+ for (const key of Object.keys(raw)) {
6300
+ if (key !== "cli_args") errors.push(`${entryPath}.${key} is not supported.`);
6301
+ }
6302
+ if (!("cli_args" in raw)) return normalizeInvocation(harness, []);
6303
+ if (!Array.isArray(raw.cli_args)) {
6304
+ errors.push(`${entryPath}.cli_args must be an array of exact strings.`);
6305
+ return null;
6306
+ }
6307
+ const cliArgs = [];
6308
+ raw.cli_args.forEach((value, index) => {
6309
+ const valuePath = `${entryPath}.cli_args[${index}]`;
6310
+ if (typeof value !== "string" || value.length === 0) {
6311
+ errors.push(`${valuePath} must be a non-empty string.`);
6312
+ return;
6313
+ }
6314
+ if (value.includes("\0")) {
6315
+ errors.push(`${valuePath} must not contain a NUL character.`);
6316
+ return;
6317
+ }
6318
+ cliArgs.push(value);
6319
+ });
6320
+ return normalizeInvocation(harness, cliArgs);
6321
+ };
6322
+ var loadHarnessConfiguration = (strikethrooRoot) => {
6323
+ const configPath = path4.join(strikethrooRoot, WORKSPACE_CONFIG_RELPATH);
6324
+ let contents;
6325
+ try {
6326
+ contents = fs3.readFileSync(configPath, "utf8");
6327
+ } catch (error) {
6328
+ if (error.code === "ENOENT") {
6329
+ return { kind: "config", config: emptyConfiguration() };
6330
+ }
6331
+ return {
6332
+ kind: "invalid",
6333
+ errors: [
6334
+ `config.yaml could not be read: ${error instanceof Error ? error.message : String(error)}`
6335
+ ]
6336
+ };
6337
+ }
6338
+ let document;
6339
+ try {
6340
+ document = load(contents);
6341
+ } catch (error) {
6342
+ return {
6343
+ kind: "invalid",
6344
+ errors: [
6345
+ `config.yaml is not valid YAML: ${error instanceof Error ? error.message : String(error)}`
6346
+ ]
6347
+ };
6348
+ }
6349
+ if (document === null || document === void 0) {
6350
+ return { kind: "config", config: emptyConfiguration() };
6351
+ }
6352
+ if (!isPlainObject3(document)) {
6353
+ return { kind: "invalid", errors: ["config.yaml must be a YAML mapping."] };
6354
+ }
6355
+ const section = document[HARNESS_CONFIGURATION_SECTION];
6356
+ if (section === null || section === void 0) {
6357
+ return { kind: "config", config: emptyConfiguration() };
6358
+ }
6359
+ if (!isPlainObject3(section)) {
6360
+ return {
6361
+ kind: "invalid",
6362
+ errors: [`config.yaml ${HARNESS_CONFIGURATION_SECTION} must be a YAML mapping.`]
6363
+ };
6364
+ }
6365
+ const errors = [];
6366
+ const supportedHarnesses = new Set(SUPPORTED_HARNESSES);
6367
+ for (const harness of Object.keys(section)) {
6368
+ if (!supportedHarnesses.has(harness)) {
6369
+ errors.push(`config.yaml ${HARNESS_CONFIGURATION_SECTION}.${harness} is not supported.`);
6370
+ }
6371
+ }
6372
+ const entries = {};
6373
+ for (const harness of SUPPORTED_HARNESSES) {
6374
+ if (!(harness in section)) {
6375
+ entries[harness] = normalizeInvocation(harness, []);
6376
+ continue;
6377
+ }
6378
+ const entry = validateHarnessEntry(harness, section[harness], errors);
6379
+ if (entry) entries[harness] = entry;
6380
+ }
6381
+ if (errors.length > 0) return { kind: "invalid", errors };
6382
+ return { kind: "config", config: Object.freeze(entries) };
6383
+ };
6384
+
6385
+ // src/skill-scripts/shared/harness-availability.ts
6386
+ var AVAILABILITY_REGISTRY_VERSION = 3;
6387
+ var AVAILABLE_TTL_MS = 30 * 60 * 1e3;
6388
+ var UNAVAILABLE_TTL_MS = 5 * 60 * 1e3;
6389
+ var PROBE_TIMEOUT_MS = 2e4;
6390
+ var AVAILABILITY_CACHE_RELATIVE_PATH = path5.join("runtime", "harness-availability.json");
6391
+ var CACHE_VERSION = 2;
6392
+ var availabilityDefinition = (harness) => {
6393
+ const adapter = EXTERNAL_HARNESS_ADAPTERS[harness];
6394
+ return {
6395
+ executable: adapter.executable,
6396
+ buildCommand: (cwd, cliArgs, prompt) => adapter.buildCommand({ cliArgs, workspace: cwd, prompt })
6397
+ };
6398
+ };
6399
+ var HARNESS_AVAILABILITY_REGISTRY = Object.freeze(
6400
+ Object.fromEntries(
6401
+ SUPPORTED_HARNESSES.map((harness) => [harness, availabilityDefinition(harness)])
6402
+ )
6403
+ );
6404
+ var resolveExecutable = (executable) => {
6405
+ const extensions = process.platform === "win32" ? ["", ...(process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")] : [""];
6406
+ const directories = /[\\/]/.test(executable) ? [""] : (process.env.PATH ?? "").split(path5.delimiter).filter(Boolean);
6407
+ for (const directory of directories) {
6408
+ for (const extension of extensions) {
6409
+ const candidate = path5.resolve(directory, `${executable}${extension}`);
6410
+ try {
6411
+ fs4.accessSync(
6412
+ candidate,
6413
+ process.platform === "win32" ? fs4.constants.F_OK : fs4.constants.X_OK
6414
+ );
6415
+ if (fs4.statSync(candidate).isFile()) return fs4.realpathSync(candidate);
6416
+ } catch {
6417
+ }
6418
+ }
6419
+ }
6420
+ return void 0;
6421
+ };
6422
+ var runProbe = (command2, timeoutMs) => new Promise((resolve4) => {
6423
+ let settled = false;
6424
+ let timedOut = false;
6425
+ const finish = (result) => {
6426
+ if (settled) return;
6427
+ settled = true;
6428
+ resolve4(result);
6429
+ };
6430
+ const child = (0, import_child_process3.spawn)(command2.executable, command2.argv, {
6431
+ cwd: command2.cwd,
6432
+ shell: false,
6433
+ stdio: ["pipe", "ignore", "ignore"]
6434
+ });
6435
+ const timer = setTimeout(() => {
6436
+ timedOut = true;
6437
+ child.kill("SIGKILL");
6438
+ }, timeoutMs);
6439
+ child.once("error", () => {
6440
+ clearTimeout(timer);
6441
+ finish({ exitCode: 1, timedOut });
6442
+ });
6443
+ child.once("close", (code) => {
6444
+ clearTimeout(timer);
6445
+ finish({ exitCode: code ?? 1, timedOut });
6446
+ });
6447
+ child.stdin?.on("error", () => void 0);
6448
+ child.stdin?.end(command2.stdin);
6449
+ });
6450
+ var defaultDependencies = {
6451
+ now: Date.now,
6452
+ resolveExecutable,
6453
+ runProbe
6454
+ };
6455
+ var isHarness = (value) => typeof value === "string" && SUPPORTED_HARNESSES.includes(value);
6456
+ var isCacheEntry = (value) => {
6457
+ if (!value || typeof value !== "object") return false;
6458
+ const entry = value;
6459
+ return typeof entry.key === "string" && isHarness(entry.harness) && typeof entry.available === "boolean" && typeof entry.observedAt === "number" && Number.isFinite(entry.observedAt) && typeof entry.expiresAt === "number" && Number.isFinite(entry.expiresAt) && typeof entry.reason === "string";
6460
+ };
6461
+ var emptyCache = () => ({ version: CACHE_VERSION, entries: [] });
6462
+ var readCache = (cachePath) => {
6463
+ try {
6464
+ const parsed = JSON.parse(fs4.readFileSync(cachePath, "utf8"));
6465
+ if (!parsed || typeof parsed !== "object") return emptyCache();
6466
+ const record = parsed;
6467
+ if (record.version !== CACHE_VERSION || !Array.isArray(record.entries)) return emptyCache();
6468
+ return { version: CACHE_VERSION, entries: record.entries.filter(isCacheEntry) };
6469
+ } catch {
6470
+ return emptyCache();
6471
+ }
6472
+ };
6473
+ var writeCache = (cachePath, entry) => {
6474
+ fs4.mkdirSync(path5.dirname(cachePath), { recursive: true });
6475
+ const cache = readCache(cachePath);
6476
+ const existingIndex = cache.entries.findIndex((candidate) => candidate.key === entry.key);
6477
+ if (existingIndex === -1) cache.entries.push(entry);
6478
+ else if (cache.entries[existingIndex].observedAt <= entry.observedAt) {
6479
+ cache.entries[existingIndex] = entry;
6480
+ }
6481
+ const temporary = `${cachePath}.${process.pid}.${(0, import_crypto2.randomUUID)()}.tmp`;
6482
+ try {
6483
+ fs4.writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}
6484
+ `, { mode: 384 });
6485
+ fs4.renameSync(temporary, cachePath);
6486
+ } finally {
6487
+ try {
6488
+ fs4.unlinkSync(temporary);
6489
+ } catch {
6490
+ }
6491
+ }
6492
+ };
6493
+ var cacheKey = (harness, executableIdentity, invocation) => (0, import_crypto2.createHash)("sha256").update(
6494
+ JSON.stringify({
6495
+ harness,
6496
+ executableIdentity,
6497
+ cliArgsHash: invocation.cliArgsHash,
6498
+ normalizationVersion: HARNESS_CONFIGURATION_NORMALIZATION_VERSION,
6499
+ probeRegistryVersion: AVAILABILITY_REGISTRY_VERSION
6500
+ })
6501
+ ).digest("hex");
6502
+ var readinessEvidence = () => {
6503
+ const nonce = (0, import_crypto2.randomUUID)();
6504
+ return {
6505
+ file: "strikethroo-readiness.txt",
6506
+ content: `strikethroo-readiness:${nonce}
6507
+ `
6508
+ };
6509
+ };
6510
+ var readinessPrompt = (evidence) => `Run a shell command that creates ${evidence.file} in the current workspace with the exact UTF-8 content ${JSON.stringify(evidence.content)}. Do not use a file editing tool.
6511
+ STRIKETHROO_READINESS=${JSON.stringify(evidence)}
6512
+ `;
6513
+ var initializeProbeWorkspace = () => {
6514
+ const workspace = fs4.mkdtempSync(path5.join(os.tmpdir(), "strikethroo-harness-probe-"));
6515
+ const initialized = (0, import_child_process3.spawnSync)("git", ["init", "--quiet"], {
6516
+ cwd: workspace,
6517
+ shell: false,
6518
+ stdio: "ignore",
6519
+ timeout: 5e3
6520
+ });
6521
+ if (initialized.status === 0 && !initialized.error) return workspace;
6522
+ fs4.rmSync(workspace, { recursive: true, force: true });
6523
+ return void 0;
6524
+ };
6525
+ var hasReadinessEvidence = (workspace, evidence) => {
6526
+ const target = path5.join(workspace, evidence.file);
6527
+ try {
6528
+ return fs4.lstatSync(target).isFile() && fs4.readFileSync(target, "utf8") === evidence.content;
6529
+ } catch {
6530
+ return false;
6531
+ }
6532
+ };
6533
+ var outcome = (harness, available, now, reason) => ({
6534
+ harness,
6535
+ available,
6536
+ observedAt: now,
6537
+ expiresAt: now + (available ? AVAILABLE_TTL_MS : UNAVAILABLE_TTL_MS),
6538
+ reason,
6539
+ source: "probe"
6540
+ });
6541
+ var invocationFor = (request, harness) => {
6542
+ if (request.invocation) return request.invocation;
6543
+ const loaded = loadHarnessConfiguration(request.strikethrooRoot);
6544
+ return loaded.kind === "config" ? loaded.config[harness] : void 0;
6545
+ };
6546
+ var checkHarnessAvailability = async (request, overrides = {}) => {
6547
+ const active = { ...defaultDependencies, ...overrides };
6548
+ const now = active.now();
6549
+ if (request.harness === void 0 || request.harness === request.currentHarness) {
6550
+ return {
6551
+ harness: request.harness ?? request.currentHarness,
6552
+ available: true,
6553
+ observedAt: now,
6554
+ expiresAt: now,
6555
+ reason: "Native/current harness targets do not require a probe.",
6556
+ source: "bypass"
6557
+ };
6558
+ }
6559
+ const harness = request.harness;
6560
+ const invocation = invocationFor(request, harness);
6561
+ if (!invocation) return outcome(harness, false, now, "Harness configuration is invalid.");
6562
+ const definition = HARNESS_AVAILABILITY_REGISTRY[harness];
6563
+ const executableIdentity = active.resolveExecutable(definition.executable);
6564
+ if (!executableIdentity)
6565
+ return outcome(harness, false, now, "Harness executable is unavailable.");
6566
+ const key = cacheKey(harness, executableIdentity, invocation);
6567
+ const cachePath = path5.join(request.strikethrooRoot, AVAILABILITY_CACHE_RELATIVE_PATH);
6568
+ const cached = readCache(cachePath).entries.find(
6569
+ (entry) => entry.key === key && entry.expiresAt > now
6570
+ );
6571
+ if (cached) {
6572
+ const { key: _key, ...cachedOutcome } = cached;
6573
+ return { ...cachedOutcome, source: "cache" };
6574
+ }
6575
+ const complete = (result) => {
6576
+ const { source: _source, ...cacheEntry } = result;
6577
+ try {
6578
+ writeCache(cachePath, { key, ...cacheEntry });
6579
+ } catch {
6580
+ }
6581
+ return result;
6582
+ };
6583
+ const probeWorkspace = initializeProbeWorkspace();
6584
+ if (!probeWorkspace) {
6585
+ return complete(outcome(harness, false, now, "Harness readiness check failed."));
6586
+ }
6587
+ try {
6588
+ const evidence = readinessEvidence();
6589
+ const command2 = definition.buildCommand(
6590
+ probeWorkspace,
6591
+ invocation.cliArgs,
6592
+ readinessPrompt(evidence)
6593
+ );
6594
+ const probe = await active.runProbe(
6595
+ { ...command2, executable: executableIdentity },
6596
+ PROBE_TIMEOUT_MS
6597
+ );
6598
+ const available = probe.exitCode === 0 && !probe.timedOut && hasReadinessEvidence(probeWorkspace, evidence);
6599
+ return complete(
6600
+ outcome(
6601
+ harness,
6602
+ available,
6603
+ now,
6604
+ available ? "Harness readiness verified." : "Harness readiness check failed."
6605
+ )
6606
+ );
6607
+ } finally {
6608
+ fs4.rmSync(probeWorkspace, { recursive: true, force: true });
6609
+ }
6610
+ };
6611
+
6433
6612
  // src/skill-scripts/dispatch-task-execution.ts
6434
6613
  var emit = (result, exitCode) => {
6435
6614
  process.stdout.write(`${JSON.stringify(result)}
@@ -6482,6 +6661,7 @@ var resolveDispatchRoute = async (request) => {
6482
6661
  }
6483
6662
  const avoided = /* @__PURE__ */ new Set();
6484
6663
  const unavailable = [];
6664
+ let harnessConfiguration;
6485
6665
  const candidateCount = configResult.config.profiles.find((candidate) => candidate.name === profile)?.targets.length ?? 0;
6486
6666
  for (let attempt = 0; attempt < Math.max(1, candidateCount); attempt += 1) {
6487
6667
  const selection = selectDispatchTarget(configResult.config, profile, avoided, {
@@ -6499,11 +6679,23 @@ var resolveDispatchRoute = async (request) => {
6499
6679
  ...selection.target.reasoning_effort === void 0 ? {} : { reasoningEffort: selection.target.reasoning_effort }
6500
6680
  };
6501
6681
  }
6682
+ harnessConfiguration ??= loadHarnessConfiguration(request.strikethrooRoot);
6683
+ if (harnessConfiguration.kind === "invalid") {
6684
+ return {
6685
+ kind: "fallback",
6686
+ reason: "invalid-execution",
6687
+ detail: `Harness invocation configuration is invalid: ${harnessConfiguration.errors.join(
6688
+ " "
6689
+ )}`
6690
+ };
6691
+ }
6692
+ const invocation = harnessConfiguration.config[harness];
6502
6693
  const availability = await checkHarnessAvailability({
6503
6694
  strikethrooRoot: request.strikethrooRoot,
6504
6695
  workspace: request.workspace,
6505
6696
  harness,
6506
- currentHarness: request.currentHarness
6697
+ currentHarness: request.currentHarness,
6698
+ invocation
6507
6699
  });
6508
6700
  if (availability.available) {
6509
6701
  return externalRoute(harness, selection.target.model, selection.target.reasoning_effort);
@@ -6541,26 +6733,41 @@ var main = async () => {
6541
6733
  const validWorkspace = workspace;
6542
6734
  const validPlanId = planId;
6543
6735
  const validTaskId = taskId;
6544
- const taskPath = path5.resolve(validTaskFile);
6545
- const taskMarkdown = fs4.readFileSync(taskPath, "utf8");
6736
+ const taskPath = path6.resolve(validTaskFile);
6737
+ const taskMarkdown = fs5.readFileSync(taskPath, "utf8");
6546
6738
  if (mode === "resolve") {
6547
6739
  emit(
6548
6740
  await resolveDispatchRoute({
6549
6741
  taskMarkdown,
6550
6742
  currentHarness: validCurrentHarness,
6551
- workspace: path5.resolve(validWorkspace),
6552
- strikethrooRoot: path5.join(path5.resolve(validWorkspace), ".ai", "strikethroo"),
6743
+ workspace: path6.resolve(validWorkspace),
6744
+ strikethrooRoot: path6.join(path6.resolve(validWorkspace), ".ai", "strikethroo"),
6553
6745
  taskId: Number(validTaskId)
6554
6746
  }),
6555
6747
  0
6556
6748
  );
6557
6749
  }
6558
6750
  const handoff = decodeHandoff(handoffArg);
6751
+ const executionConfiguration = loadHarnessConfiguration(
6752
+ path6.join(path6.resolve(validWorkspace), ".ai", "strikethroo")
6753
+ );
6754
+ if (executionConfiguration.kind === "invalid") {
6755
+ return emit(
6756
+ {
6757
+ kind: "infrastructure-failure",
6758
+ detail: `Harness invocation configuration is invalid: ${executionConfiguration.errors.join(
6759
+ " "
6760
+ )}`
6761
+ },
6762
+ 2
6763
+ );
6764
+ }
6559
6765
  const routed = {
6560
6766
  harness: handoff.harness,
6767
+ cliArgs: executionConfiguration.config[handoff.harness].cliArgs,
6561
6768
  model: handoff.model,
6562
6769
  ...handoff.reasoningEffort === void 0 ? {} : { reasoningEffort: handoff.reasoningEffort },
6563
- workspace: path5.resolve(validWorkspace),
6770
+ workspace: path6.resolve(validWorkspace),
6564
6771
  planId: validPlanId,
6565
6772
  taskId: validTaskId,
6566
6773
  taskFile: taskPath,