@inteeka/task-cli 0.1.10 → 0.1.12

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/cli.js CHANGED
@@ -2170,7 +2170,11 @@ import { homedir as homedir5 } from "os";
2170
2170
  import { join as join7 } from "path";
2171
2171
  var FIX_PROMPT_JSON_SCHEMA = {
2172
2172
  type: "object",
2173
- required: ["summary", "suspected_files", "proposed_changes", "confidence"],
2173
+ // Phase 3 confidence_reason is REQUIRED unconditionally so the
2174
+ // server-side refine() (which mandates ≥20 chars on low/medium) never
2175
+ // rejects a fresh submission. The model emits it for high too; that's
2176
+ // cheap (≤1500 chars) and doubles as documentation for reviewers.
2177
+ required: ["summary", "suspected_files", "proposed_changes", "confidence", "confidence_reason"],
2174
2178
  additionalProperties: false,
2175
2179
  properties: {
2176
2180
  summary: { type: "string", minLength: 1, maxLength: 2e3 },
@@ -2209,11 +2213,11 @@ var FIX_PROMPT_JSON_SCHEMA = {
2209
2213
  },
2210
2214
  risk_notes: { type: "string", maxLength: 2e3 },
2211
2215
  confidence: { type: "string", enum: ["low", "medium", "high"] },
2212
- // Phase 3 — explicit reasoning for the confidence rating. Required at
2213
- // ≥20 chars for low/medium ratings; the dashboard's Zod refine() rejects
2214
- // anything shorter on `/submit`. Optional on `high` so the model can
2215
- // omit it when the evidence is overwhelming.
2216
- confidence_reason: { type: "string", maxLength: 1500 }
2216
+ // Phase 3 — explicit reasoning for the confidence rating. Always
2217
+ // required (see `required` array above). minLength=20 matches the
2218
+ // dashboard's Zod refine() so low/medium submissions never fail
2219
+ // server-side validation; high also emits it (cheap, useful).
2220
+ confidence_reason: { type: "string", minLength: 20, maxLength: 1500 }
2217
2221
  }
2218
2222
  };
2219
2223
  var LlmGenerationError = class extends Error {
@@ -2232,7 +2236,9 @@ async function generateFixPromptJson(args) {
2232
2236
  "",
2233
2237
  args.ticketBlock,
2234
2238
  "",
2235
- "Return JSON only matching the supplied schema. Do not include explanatory prose, markdown fences, or commentary."
2239
+ "Return JSON only matching the supplied schema. Do not include explanatory prose, markdown fences, or commentary.",
2240
+ "",
2241
+ "IMPORTANT: confidence_reason is REQUIRED for every rating (\u226520 chars). For low/medium, name which investigation axes lacked signal. For high, briefly state which axes corroborated the proposal."
2236
2242
  ].join("\n");
2237
2243
  const cliArgs = [
2238
2244
  "--print",
@@ -2705,6 +2711,143 @@ function clampInt(raw, min, max, fallback) {
2705
2711
  return Math.min(v, max);
2706
2712
  }
2707
2713
 
2714
+ // src/commands/pr-test.ts
2715
+ import { execFileSync as execFileSync6 } from "child_process";
2716
+ function registerPrTest(program2) {
2717
+ program2.command("pr-test").description(
2718
+ "Dry-run the full PR pipeline \u2014 cuts a throwaway branch, opens a real PR via the dashboard, then cleans up. Use this to verify your git integration before running task work on real tickets."
2719
+ ).option(
2720
+ "--keep",
2721
+ "Leave the PR open and the branches in place after the test (default: clean up)"
2722
+ ).option("--silent", "Suppress per-step progress output").action(async (opts) => {
2723
+ await runPrTest(opts);
2724
+ });
2725
+ }
2726
+ async function runPrTest(opts) {
2727
+ const cwd = findRepoRoot();
2728
+ const project = await readProjectConfig(cwd);
2729
+ if (!project) {
2730
+ throw new CliError(
2731
+ CLI_EXIT_CODES.MISCONFIGURATION,
2732
+ "No project link in this repo",
2733
+ "Run 'task link' first."
2734
+ );
2735
+ }
2736
+ const baseBranch = project.cli_base_branch ?? "development";
2737
+ const silent = !!opts.silent;
2738
+ if (!silent) process.stdout.write(`${c.dim(`Step 1/6: assertBaseBranch (${baseBranch})\u2026`)}
2739
+ `);
2740
+ assertBaseBranch(cwd, baseBranch);
2741
+ const timestamp = Date.now();
2742
+ const branchName = `task/pr-test-${timestamp}`;
2743
+ const prTitle = `[task pr-test] CLI dry-run probe ${new Date(timestamp).toISOString()}`;
2744
+ if (!silent) process.stdout.write(`${c.dim(`Step 2/6: createTicketBranch (${branchName})\u2026`)}
2745
+ `);
2746
+ createTicketBranch(cwd, branchName, baseBranch);
2747
+ let pushSucceeded = false;
2748
+ const recover = () => {
2749
+ try {
2750
+ checkoutBranch(cwd, baseBranch);
2751
+ } catch {
2752
+ }
2753
+ deleteLocalBranch(cwd, branchName);
2754
+ };
2755
+ try {
2756
+ if (!silent) process.stdout.write(`${c.dim("Step 3/6: empty commit\u2026")}
2757
+ `);
2758
+ execFileSync6(
2759
+ "git",
2760
+ ["commit", "--allow-empty", "-m", `task pr-test: connectivity probe ${timestamp}`],
2761
+ { cwd, stdio: ["ignore", "pipe", "pipe"] }
2762
+ );
2763
+ if (!silent) process.stdout.write(`${c.dim(`Step 4/6: pushBranch (${branchName})\u2026`)}
2764
+ `);
2765
+ pushBranch(cwd, branchName);
2766
+ pushSucceeded = true;
2767
+ if (!silent) process.stdout.write(`${c.dim("Step 5/6: open PR via dashboard\u2026")}
2768
+ `);
2769
+ const pr = await apiCallOrThrow("POST", `/api/v1/cli/me/git/pr-test`, {
2770
+ body: {
2771
+ project_id: project.project_id,
2772
+ source_branch: branchName,
2773
+ base_branch: baseBranch,
2774
+ title: prTitle
2775
+ }
2776
+ });
2777
+ process.stdout.write(
2778
+ `
2779
+ ${c.ok("\u2713 PR opened")} ${c.cyan(pr.pr_url)}
2780
+ repo: ${pr.repo}
2781
+ branch: ${pr.source_branch} \u2192 ${pr.base_branch}
2782
+ number: #${pr.pr_number}
2783
+
2784
+ `
2785
+ );
2786
+ if (opts.keep) {
2787
+ process.stdout.write(
2788
+ `${c.warn("--keep")} ${c.dim("flag set \u2014 PR and branches preserved. Manual cleanup:")}
2789
+ ${c.cyan(`task pr-test-cleanup ${pr.pr_number} ${branchName}`)}
2790
+ (or close on GitHub + ${c.cyan(`git push origin --delete ${branchName}`)} + ${c.cyan(`git branch -D ${branchName}`)})
2791
+ `
2792
+ );
2793
+ checkoutBranch(cwd, baseBranch);
2794
+ return;
2795
+ }
2796
+ if (!silent) process.stdout.write(`${c.dim("Step 6/6: cleanup\u2026")}
2797
+ `);
2798
+ const cleanup = await apiCall("POST", `/api/v1/cli/me/git/pr-test/cleanup`, {
2799
+ body: {
2800
+ project_id: project.project_id,
2801
+ pr_number: pr.pr_number,
2802
+ source_branch: branchName
2803
+ }
2804
+ });
2805
+ checkoutBranch(cwd, baseBranch);
2806
+ deleteLocalBranch(cwd, branchName);
2807
+ if (cleanup.ok && cleanup.data) {
2808
+ const { pr_closed, branch_deleted } = cleanup.data;
2809
+ process.stdout.write(
2810
+ `${c.ok("\u2713 Cleanup complete")} \u2014 PR ${pr_closed ? "closed" : c.warn("NOT closed")}, remote branch ${branch_deleted ? "deleted" : c.warn("NOT deleted")}, local branch deleted.
2811
+ `
2812
+ );
2813
+ if (!pr_closed || !branch_deleted) {
2814
+ process.stdout.write(
2815
+ c.dim(` Manually close at: ${pr.pr_url}
2816
+ `) + c.dim(` Manually delete remote: git push origin --delete ${branchName}
2817
+ `)
2818
+ );
2819
+ }
2820
+ } else {
2821
+ process.stdout.write(
2822
+ `${c.warn("Cleanup endpoint failed \u2014 branches & PR may need manual cleanup:")}
2823
+ ${c.cyan(pr.pr_url)}
2824
+ ${c.cyan(`git push origin --delete ${branchName}`)}
2825
+ `
2826
+ );
2827
+ }
2828
+ process.stdout.write(
2829
+ `
2830
+ ${c.ok("\u2713 pr-test passed")} \u2014 the full CLI \u2192 dashboard \u2192 GitHub PR pipeline works.
2831
+ `
2832
+ );
2833
+ } catch (err) {
2834
+ process.stdout.write(`
2835
+ ${c.err("\u2717 pr-test failed")}: ${err.message}
2836
+ `);
2837
+ if (pushSucceeded) {
2838
+ process.stdout.write(
2839
+ c.dim(
2840
+ ` The branch was pushed but the PR step failed. Manually clean up:
2841
+ git push origin --delete ${branchName}
2842
+ `
2843
+ )
2844
+ );
2845
+ }
2846
+ recover();
2847
+ throw err;
2848
+ }
2849
+ }
2850
+
2708
2851
  // src/commands/scheduled-task.ts
2709
2852
  import { randomUUID as randomUUID3 } from "crypto";
2710
2853
 
@@ -2715,7 +2858,7 @@ import { platform as platform2 } from "os";
2715
2858
  import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile8, unlink as unlink3, readdir } from "fs/promises";
2716
2859
  import { homedir as homedir6 } from "os";
2717
2860
  import { join as join8 } from "path";
2718
- import { execFileSync as execFileSync6, spawn as spawn4 } from "child_process";
2861
+ import { execFileSync as execFileSync7, spawn as spawn4 } from "child_process";
2719
2862
 
2720
2863
  // src/scheduler/cron-translate.ts
2721
2864
  function translateToLaunchd(cron) {
@@ -2883,17 +3026,17 @@ var launchdAdapter = {
2883
3026
  const path = plistPath(entry.id);
2884
3027
  await writeFile8(path, buildPlist(entry));
2885
3028
  try {
2886
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
3029
+ execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2887
3030
  } catch {
2888
3031
  }
2889
3032
  if (entry.enabled) {
2890
- execFileSync6("launchctl", ["bootstrap", bootstrapDomain(), path]);
3033
+ execFileSync7("launchctl", ["bootstrap", bootstrapDomain(), path]);
2891
3034
  }
2892
3035
  },
2893
3036
  async remove(id) {
2894
3037
  const path = plistPath(id);
2895
3038
  try {
2896
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
3039
+ execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2897
3040
  } catch {
2898
3041
  }
2899
3042
  try {
@@ -2958,10 +3101,10 @@ var launchdAdapter = {
2958
3101
  xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
2959
3102
  await writeFile8(path, xml);
2960
3103
  try {
2961
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
3104
+ execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2962
3105
  } catch {
2963
3106
  }
2964
- execFileSync6("launchctl", ["bootstrap", bootstrapDomain(), path]);
3107
+ execFileSync7("launchctl", ["bootstrap", bootstrapDomain(), path]);
2965
3108
  } else {
2966
3109
  if (!/<key>Disabled<\/key>/.test(xml)) {
2967
3110
  xml = xml.replace(
@@ -2971,7 +3114,7 @@ var launchdAdapter = {
2971
3114
  await writeFile8(path, xml);
2972
3115
  }
2973
3116
  try {
2974
- execFileSync6("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
3117
+ execFileSync7("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
2975
3118
  } catch {
2976
3119
  }
2977
3120
  }
@@ -2979,7 +3122,7 @@ var launchdAdapter = {
2979
3122
  };
2980
3123
 
2981
3124
  // src/scheduler/cron.ts
2982
- import { execFileSync as execFileSync7, spawn as spawn5 } from "child_process";
3125
+ import { execFileSync as execFileSync8, spawn as spawn5 } from "child_process";
2983
3126
 
2984
3127
  // src/scheduler/safe-command.ts
2985
3128
  var FORBIDDEN = /[;&|`$()<>\\]/;
@@ -3034,7 +3177,7 @@ var MARK_OPEN = (id) => `# task-cli:${id}:start`;
3034
3177
  var MARK_CLOSE = (id) => `# task-cli:${id}:end`;
3035
3178
  function readCrontab() {
3036
3179
  try {
3037
- return execFileSync7("crontab", ["-l"], { encoding: "utf8" });
3180
+ return execFileSync8("crontab", ["-l"], { encoding: "utf8" });
3038
3181
  } catch {
3039
3182
  return "";
3040
3183
  }
@@ -3149,7 +3292,7 @@ var cronAdapter = {
3149
3292
  };
3150
3293
 
3151
3294
  // src/scheduler/windows.ts
3152
- import { execFileSync as execFileSync8, spawn as spawn6 } from "child_process";
3295
+ import { execFileSync as execFileSync9, spawn as spawn6 } from "child_process";
3153
3296
  var TASK_PREFIX = "TaskCLI_";
3154
3297
  function taskName(id) {
3155
3298
  return `${TASK_PREFIX}${id.replace(/[^A-Za-z0-9_-]/g, "_")}`;
@@ -3214,22 +3357,22 @@ function pad(v) {
3214
3357
  var windowsAdapter = {
3215
3358
  async upsert(entry) {
3216
3359
  const args = buildSchtasksArgs(entry, entry.command);
3217
- execFileSync8("schtasks.exe", args, { stdio: "ignore" });
3360
+ execFileSync9("schtasks.exe", args, { stdio: "ignore" });
3218
3361
  if (!entry.enabled) {
3219
- execFileSync8("schtasks.exe", ["/Change", "/TN", taskName(entry.id), "/DISABLE"], {
3362
+ execFileSync9("schtasks.exe", ["/Change", "/TN", taskName(entry.id), "/DISABLE"], {
3220
3363
  stdio: "ignore"
3221
3364
  });
3222
3365
  }
3223
3366
  },
3224
3367
  async remove(id) {
3225
3368
  try {
3226
- execFileSync8("schtasks.exe", ["/Delete", "/TN", taskName(id), "/F"], { stdio: "ignore" });
3369
+ execFileSync9("schtasks.exe", ["/Delete", "/TN", taskName(id), "/F"], { stdio: "ignore" });
3227
3370
  } catch {
3228
3371
  }
3229
3372
  },
3230
3373
  async list() {
3231
3374
  try {
3232
- const csv = execFileSync8("schtasks.exe", ["/Query", "/FO", "CSV", "/V"], {
3375
+ const csv = execFileSync9("schtasks.exe", ["/Query", "/FO", "CSV", "/V"], {
3233
3376
  encoding: "utf8"
3234
3377
  });
3235
3378
  const lines = csv.split(/\r?\n/);
@@ -3279,7 +3422,7 @@ var windowsAdapter = {
3279
3422
  },
3280
3423
  async setEnabled(id, enabled) {
3281
3424
  try {
3282
- execFileSync8(
3425
+ execFileSync9(
3283
3426
  "schtasks.exe",
3284
3427
  ["/Change", "/TN", taskName(id), enabled ? "/ENABLE" : "/DISABLE"],
3285
3428
  { stdio: "ignore" }
@@ -3687,7 +3830,7 @@ function registerConfig(program2) {
3687
3830
  }
3688
3831
 
3689
3832
  // src/commands/doctor.ts
3690
- import { execFileSync as execFileSync9 } from "child_process";
3833
+ import { execFileSync as execFileSync10 } from "child_process";
3691
3834
  import { request as request5 } from "undici";
3692
3835
  function registerDoctor(program2) {
3693
3836
  program2.command("doctor").description("Diagnose your CLI setup").action(async () => {
@@ -3735,7 +3878,7 @@ function registerDoctor(program2) {
3735
3878
  });
3736
3879
  }
3737
3880
  try {
3738
- const dirty = execFileSync9("git", ["status", "--porcelain"], {
3881
+ const dirty = execFileSync10("git", ["status", "--porcelain"], {
3739
3882
  cwd: root,
3740
3883
  encoding: "utf8"
3741
3884
  }).trim();
@@ -3759,7 +3902,7 @@ function registerDoctor(program2) {
3759
3902
  }
3760
3903
  function checkBinary(name, command) {
3761
3904
  try {
3762
- const out = execFileSync9(command, ["--version"], { encoding: "utf8" }).trim();
3905
+ const out = execFileSync10(command, ["--version"], { encoding: "utf8" }).trim();
3763
3906
  return { name, ok: true, detail: out.split("\n")[0] ?? out };
3764
3907
  } catch {
3765
3908
  return { name, ok: false, detail: `'${command}' not found on PATH` };
@@ -3767,7 +3910,7 @@ function checkBinary(name, command) {
3767
3910
  }
3768
3911
 
3769
3912
  // src/commands/version.ts
3770
- var CLI_VERSION = true ? "0.1.10" : "0.0.0-dev";
3913
+ var CLI_VERSION = true ? "0.1.12" : "0.0.0-dev";
3771
3914
  function registerVersion(program2) {
3772
3915
  program2.command("version").description("Print the CLI version").action(() => {
3773
3916
  process.stdout.write(CLI_VERSION + "\n");
@@ -3791,6 +3934,7 @@ registerTickets(program);
3791
3934
  registerTicket(program);
3792
3935
  registerWork(program);
3793
3936
  registerScan(program);
3937
+ registerPrTest(program);
3794
3938
  registerScheduledTask(program);
3795
3939
  registerRuns(program);
3796
3940
  registerConfig(program);