squad-kit 0.7.1 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] — 2026-05-03
11
+
12
+ ### What's new
13
+
14
+ - Generate page shows provider rate limits: countdown, auto-retry banner,
15
+ and rerun when the planner aborts over the wait cap.
16
+ - Planner emits richer `rate_limit` stream events (`retrying` vs `aborted`,
17
+ cap seconds, provider, short error snippet). CLI `new-plan` only shows the
18
+ “waiting to retry” spinner for the retrying phase.
19
+ - Run SSE ends a rate-limit abort with `done` (partial) only — no extra
20
+ `error` line repeating the same message.
21
+
22
+ ### Bug fixes
23
+
24
+ - Jira issue search called deprecated `/rest/api/3/search` (Atlassian returns
25
+ HTTP 410). Search now uses `/rest/api/3/search/jql` with clearer 410 errors
26
+ if an old client hits removed endpoints.
27
+
28
+ ## [0.7.2] — 2026-04-28
29
+
30
+ ### Bug fixes
31
+
32
+ - The console sidebar and dashboard still reported `v0.6.0` after 0.7.1
33
+ because `/api/meta` and `/api/dashboard` had the version hardcoded.
34
+ Both now read from `package.json` (matching the CLI and `/healthz`
35
+ fix in 0.7.1).
36
+ - `squad config set tracker --type github` was a no-op stub: it set
37
+ `tracker.type = github` without prompting for owner/repo or PAT and
38
+ skipped the connectivity probe. It now matches the Jira and Azure
39
+ flows — interactive credential prompt, secrets written to
40
+ `.squad/secrets.yaml`, post-config GitHub probe, and an updated
41
+ next-steps hint with `--id <github-issue-number>`.
42
+
10
43
  ## [0.7.1] — 2026-04-28
11
44
 
12
45
  ### Bug fixes
package/dist/cli.js CHANGED
@@ -926,6 +926,27 @@ async function promptAzureCredentials(defaults = {}) {
926
926
  });
927
927
  return { organization: organization.trim(), project: project.trim(), pat };
928
928
  }
929
+ async function promptGitHubCredentials(defaults = {}) {
930
+ const owner = await input({
931
+ message: "GitHub repo owner (user or org, e.g. octocat):",
932
+ default: defaults.owner ?? "",
933
+ validate: notEmpty
934
+ });
935
+ const repo = await input({
936
+ message: "GitHub repo name (e.g. hello-world):",
937
+ default: defaults.repo ?? "",
938
+ validate: notEmpty
939
+ });
940
+ const host = await input({
941
+ message: "GitHub host (leave blank for github.com; for GHES use ghes.example.com):",
942
+ default: defaults.host ?? ""
943
+ });
944
+ const pat = await password({
945
+ message: 'Personal Access Token (needs "repo" scope, or fine-grained "Issues: read"; input hidden):',
946
+ validate: (v) => v.length >= 20 ? true : "PAT seems too short"
947
+ });
948
+ return { owner: owner.trim(), repo: repo.trim(), host: host.trim(), pat };
949
+ }
929
950
  function newPlannerBlock(provider) {
930
951
  return {
931
952
  enabled: true,
@@ -1664,7 +1685,7 @@ var JiraClient = class {
1664
1685
  const esc = q.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1665
1686
  jql = `text ~ "${esc}" order by updated DESC`;
1666
1687
  }
1667
- const url = `${this.baseUrl}/rest/api/3/search?jql=${encodeURIComponent(jql)}&fields=summary,status,issuetype&maxResults=${limit}`;
1688
+ const url = `${this.baseUrl}/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&fields=summary,status,issuetype&maxResults=${limit}`;
1668
1689
  let res;
1669
1690
  try {
1670
1691
  res = await fetch(url, { headers: this.headers() });
@@ -1713,6 +1734,13 @@ var JiraClient = class {
1713
1734
  if (status === 429) {
1714
1735
  return new TrackerError(`Jira rate limit hit (HTTP 429). Wait a minute and retry.`, "rate-limited", status);
1715
1736
  }
1737
+ if (status === 410) {
1738
+ return new TrackerError(
1739
+ id === "search" ? `Jira search endpoint has been removed by Atlassian (HTTP 410). squad-kit must call /rest/api/3/search/jql instead \u2014 upgrade squad-kit to a version that includes this fix.` : `Jira endpoint has been removed by Atlassian (HTTP 410). Upgrade squad-kit to a version that calls the current Jira Cloud REST API.`,
1740
+ "other",
1741
+ status
1742
+ );
1743
+ }
1716
1744
  return new TrackerError(
1717
1745
  id === "search" ? `Jira search failed (HTTP ${status}).` : `Jira request failed (HTTP ${status}).`,
1718
1746
  "other",
@@ -1808,6 +1836,11 @@ function azureError() {
1808
1836
  `Azure DevOps configuration is incomplete. Set AZURE_DEVOPS_ORG, AZURE_DEVOPS_PROJECT, AZURE_DEVOPS_PAT (or run \`squad config set tracker\` without --yes to enter credentials interactively).`
1809
1837
  );
1810
1838
  }
1839
+ function githubError() {
1840
+ return new Error(
1841
+ `GitHub configuration is incomplete. Set tracker.workspace (owner) and tracker.project (repo) in .squad/config.yaml plus GITHUB_TOKEN (or run \`squad config set tracker\` without --yes to enter credentials interactively).`
1842
+ );
1843
+ }
1811
1844
  async function runConfigSetTracker(opts = {}) {
1812
1845
  const root = requireSquadRoot();
1813
1846
  const paths = buildPaths(root);
@@ -1836,7 +1869,36 @@ async function runConfigSetTracker(opts = {}) {
1836
1869
  if (type === "none") {
1837
1870
  nextTracker = { type: "none" };
1838
1871
  } else if (type === "github") {
1839
- nextTracker = { ...config2.tracker, type };
1872
+ if (interactive) {
1873
+ step("GitHub credentials (stored in .squad/secrets.yaml \u2014 always git-ignored)");
1874
+ const g = await promptGitHubCredentials({
1875
+ owner: config2.tracker.type === "github" ? config2.tracker.workspace : void 0,
1876
+ repo: config2.tracker.type === "github" ? config2.tracker.project : void 0,
1877
+ host: baseSecrets.tracker?.github?.host
1878
+ });
1879
+ const merged = mergeSecrets(baseSecrets, {
1880
+ tracker: { github: { host: g.host, pat: g.pat } }
1881
+ });
1882
+ saveSecrets(paths.secretsFile, merged);
1883
+ nextTracker = { type: "github", workspace: g.owner, project: g.repo };
1884
+ success("GitHub credentials saved");
1885
+ info(".squad/secrets.yaml updated (chmod 0600 on POSIX)");
1886
+ } else {
1887
+ const o = overlayTrackerEnv(baseSecrets);
1888
+ const owner = (config2.tracker.workspace ?? "").trim();
1889
+ const repo = (config2.tracker.project ?? "").trim();
1890
+ if (!owner || !repo) {
1891
+ throw githubError();
1892
+ }
1893
+ const candidate = {
1894
+ ...config2,
1895
+ tracker: { type: "github", workspace: owner, project: repo }
1896
+ };
1897
+ if (clientFor(candidate, o).error) {
1898
+ throw githubError();
1899
+ }
1900
+ nextTracker = { type: "github", workspace: owner, project: repo };
1901
+ }
1840
1902
  } else if (type === "jira") {
1841
1903
  if (interactive) {
1842
1904
  step("Jira Cloud credentials (stored in .squad/secrets.yaml \u2014 always git-ignored)");
@@ -1925,6 +1987,15 @@ async function runConfigSetTracker(opts = {}) {
1925
1987
  r.status !== void 0 ? `Azure DevOps connectivity check: HTTP ${r.status}` : `Azure DevOps connectivity check failed: ${r.detail ?? "unknown"}`
1926
1988
  );
1927
1989
  }
1990
+ } else if (reloaded.tracker.type === "github") {
1991
+ const r = await probeGitHubConnectivity(s, reloaded);
1992
+ if (r.ok) {
1993
+ info("GitHub connectivity check: OK");
1994
+ } else {
1995
+ warning(
1996
+ r.status !== void 0 ? `GitHub connectivity check: HTTP ${r.status}` : `GitHub connectivity check failed: ${r.detail ?? "unknown"}`
1997
+ );
1998
+ }
1928
1999
  }
1929
2000
  printTrackerNextSteps(next.tracker.type);
1930
2001
  }
@@ -1937,7 +2008,8 @@ function printTrackerNextSteps(type) {
1937
2008
  return;
1938
2009
  }
1939
2010
  info("1) Verify with `squad doctor` \u2014 tracker checks should be green.");
1940
- info(`2) Create a story: squad new-story <feature-slug> --id <${type === "jira" ? "JIRA-123" : "azure-work-item-id"}>`);
2011
+ const idHint = type === "jira" ? "JIRA-123" : type === "github" ? "github-issue-number" : "azure-work-item-id";
2012
+ info(`2) Create a story: squad new-story <feature-slug> --id <${idHint}>`);
1941
2013
  info(" squad-kit auto-fetches the title, description, and attachments into the intake.");
1942
2014
  info("3) Review the generated intake.md, then run `squad new-plan --api` to generate the plan.");
1943
2015
  }
@@ -4038,12 +4110,26 @@ async function runPlanner(input5) {
4038
4110
  let retrySkippedReason;
4039
4111
  if (response.stopReason === "error" && response.errorKind === "rate_limit") {
4040
4112
  const asked = response.retryAfterSec;
4113
+ const emitRateLimit = (phase, waitSec) => {
4114
+ bus.emit({
4115
+ kind: "rate_limit",
4116
+ runId,
4117
+ turn: iterations,
4118
+ retryAfterSec: response.retryAfterSec,
4119
+ waitSec,
4120
+ capSec: MAX_RATE_LIMIT_RETRY_SEC,
4121
+ phase,
4122
+ provider: input5.provider.name,
4123
+ rawBody: (response.rawError ?? "").slice(0, 200)
4124
+ });
4125
+ };
4041
4126
  if (asked !== void 0 && asked > MAX_RATE_LIMIT_RETRY_SEC) {
4127
+ emitRateLimit("aborted", asked);
4042
4128
  retrySkippedReason = "retry_after_too_long";
4043
4129
  } else {
4044
4130
  const waitSec = Math.min(asked ?? 10, MAX_RATE_LIMIT_RETRY_SEC);
4131
+ emitRateLimit("retrying", waitSec);
4045
4132
  input5.onRateLimit?.(waitSec);
4046
- bus.emit({ kind: "rate_limit", runId, turn: iterations, waitSec });
4047
4133
  try {
4048
4134
  await sleepWithAbort(waitSec * 1e3, sleepFn, input5.abort);
4049
4135
  } catch (e) {
@@ -4823,9 +4909,11 @@ async function emitViaApi(story, paths, config2, opts) {
4823
4909
  sessionSpinner.current = spinner("thinking\u2026");
4824
4910
  break;
4825
4911
  case "rate_limit":
4826
- sessionSpinner.current?.stop();
4827
- warning(`${planner.provider} rate limit hit \u2014 retrying in ${e.waitSec}s`);
4828
- sessionSpinner.current = spinner("waiting for rate limit to reset\u2026");
4912
+ if (e.phase === "retrying") {
4913
+ sessionSpinner.current?.stop();
4914
+ warning(`${e.provider} rate limit hit \u2014 retrying in ${e.waitSec}s`);
4915
+ sessionSpinner.current = spinner("waiting for rate limit to reset\u2026");
4916
+ }
4829
4917
  break;
4830
4918
  case "usage":
4831
4919
  break;
@@ -6390,7 +6478,7 @@ function mountMetaApi(app, opts) {
6390
6478
  const config2 = loadConfig(opts.paths.configFile);
6391
6479
  const lastRun = await readLastRun(opts.paths);
6392
6480
  return c.json({
6393
- version: "0.6.0",
6481
+ version: readInstalledPackage().version,
6394
6482
  root: opts.paths.root,
6395
6483
  project: config2.project,
6396
6484
  planner: config2.planner ? { provider: config2.planner.provider, enabled: config2.planner.enabled } : null,
@@ -6501,7 +6589,11 @@ function mountRunsApi(app, opts) {
6501
6589
  });
6502
6590
  } catch (err) {
6503
6591
  const durationMs = Date.now() - startedAt;
6504
- bus.emit({ kind: "error", runId, message: err.message });
6592
+ const last = eventBuffer[eventBuffer.length - 1];
6593
+ const isRateLimitAbort = last?.kind === "rate_limit" && last.phase === "aborted";
6594
+ if (!isRateLimitAbort) {
6595
+ bus.emit({ kind: "error", runId, message: err.message });
6596
+ }
6505
6597
  bus.emit({
6506
6598
  kind: "done",
6507
6599
  runId,
@@ -6839,7 +6931,7 @@ function mountDashboardApi(app, opts) {
6839
6931
  const runs = await listRuns(opts.paths);
6840
6932
  const stories = listStories(opts.paths);
6841
6933
  return c.json({
6842
- version: "0.6.0",
6934
+ version: readInstalledPackage().version,
6843
6935
  root: opts.paths.root,
6844
6936
  project: cfg.project,
6845
6937
  planner: cfg.planner ? { provider: cfg.planner.provider, enabled: cfg.planner.enabled } : null,