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 +33 -0
- package/dist/cli.js +102 -10
- package/dist/cli.js.map +1 -1
- package/dist/console-ui/assets/index-C5I4cnED.css +1 -0
- package/dist/console-ui/assets/{index-gaFPpGMN.js → index-DGzIogF0.js} +166 -166
- package/dist/console-ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/console-ui/assets/index-BDlxrwaG.css +0 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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,
|