@gr8ful/spf 0.13.0 → 0.15.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/README.md +52 -4
- package/assets/skill/references/config.md +4 -0
- package/dist/cli/commands/doctor.js +21 -0
- package/dist/cli/commands/watch.js +64 -3
- package/dist/cli/interview.js +18 -1
- package/dist/core/data_types.d.ts +233 -3
- package/dist/core/data_types.js +86 -3
- package/dist/core/issues/github_provider.d.ts +66 -2
- package/dist/core/issues/github_provider.js +161 -2
- package/dist/core/issues/jira_provider.d.ts +50 -9
- package/dist/core/issues/jira_provider.js +62 -2
- package/dist/core/notify/notifier.d.ts +3 -2
- package/dist/core/notify/notifier.js +32 -3
- package/dist/core/refine.js +2 -2
- package/dist/core/utils.d.ts +5 -5
- package/dist/core/utils.js +14 -7
- package/package.json +1 -1
- package/web/assets/index-CRujNW-1.js +11 -0
- package/web/assets/index-Cto6nuQL.css +1 -0
- package/web/assets/overpass-latin-400-normal-BpeLJ0bs.woff2 +0 -0
- package/web/assets/overpass-latin-600-normal-25RhTNCi.woff2 +0 -0
- package/web/assets/overpass-latin-700-normal-CQX2QTgM.woff2 +0 -0
- package/web/assets/overpass-mono-latin-400-normal-VINZG6Js.woff2 +0 -0
- package/web/assets/overpass-mono-latin-700-normal-D6nRBrbd.woff2 +0 -0
- package/web/index.html +33 -2
- package/web/logo.svg +4 -4
- package/web/assets/index-C7nF068F.css +0 -1
- package/web/assets/index-mzSArcnQ.js +0 -11
- package/web/assets/play-latin-400-normal-GKW-4YV7.woff2 +0 -0
- package/web/assets/play-latin-700-normal-DyPlLDbb.woff2 +0 -0
package/README.md
CHANGED
|
@@ -766,9 +766,24 @@ A **classic** PAT (fine-grained tokens use different permission names — not co
|
|
|
766
766
|
| Private | `repo` | Everything above, full read/write |
|
|
767
767
|
| Public only | `public_repo` | The same, restricted to public repos |
|
|
768
768
|
|
|
769
|
-
|
|
769
|
+
There's no dedicated "issues" or "pull requests" scope on classic PATs — GitHub bundles both into `repo`/`public_repo`, which is why that table covers everything by default.
|
|
770
770
|
|
|
771
|
-
|
|
771
|
+
Native GitHub Projects v2 board status (the board's Status column) is a separate, optional layer on top, off by default — labels alone don't move it, so an issue's Status sits wherever it started unless you opt in with `watch.github.project_number` and `watch.github.status_map`:
|
|
772
|
+
|
|
773
|
+
```yaml
|
|
774
|
+
watch:
|
|
775
|
+
issue_provider: github
|
|
776
|
+
repo: owner/name
|
|
777
|
+
github:
|
|
778
|
+
project_number: 3 # owner's Projects v2 board number — see the board's own URL
|
|
779
|
+
status_map: # optional — unset by default, per state
|
|
780
|
+
ready: Todo
|
|
781
|
+
working: In Progress
|
|
782
|
+
review: In Review
|
|
783
|
+
done: Done
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Only the states you map are touched; an unmapped state keeps today's label-only behavior, and `project_number: 0` (the default) disables sync outright regardless of `status_map`. This needs the **`project`** scope in addition to `repo`/`public_repo` — Projects v2 has no REST API at all, only GraphQL, and that surface is gated by its own scope; grant it only if you're using `status_map`. A configured option name with no matching Status option (or a rejected mutation) is logged and skipped, never thrown — the label update is what `spf watch` actually depends on; status sync is a best-effort convenience on top of it. `spf watch init` and `spf watch`'s own startup check both validate a configured `status_map` against the real project's Status options, read-only, and exit non-zero on a mismatch, the same way Jira's `issue_types` is validated below.
|
|
772
787
|
|
|
773
788
|
### Jira (`issue_provider: jira`)
|
|
774
789
|
|
|
@@ -777,9 +792,26 @@ export JIRA_EMAIL=you@example.com
|
|
|
777
792
|
export JIRA_API_TOKEN=... # id.atlassian.com -> Security -> API tokens
|
|
778
793
|
```
|
|
779
794
|
|
|
780
|
-
State is modeled as Jira **labels** (`<prefix>:ready`, etc.), mirroring GitHub exactly
|
|
795
|
+
State is modeled as Jira **labels** (`<prefix>:ready`, etc.), mirroring GitHub exactly — labels are spf's actual state machine and always get written unconditionally, with zero per-project setup. One caveat: colons are a legal Jira label character and JQL matches on them fine, but they won't show up in Jira's own label autocomplete UI — cosmetic only.
|
|
781
796
|
|
|
782
|
-
|
|
797
|
+
Native Jira workflow status (the board's Status column) is a separate, optional layer on top, off by default — labels alone don't move it, so an issue's Status sits wherever it started unless you opt in with `watch.jira.status_map`:
|
|
798
|
+
|
|
799
|
+
```yaml
|
|
800
|
+
watch:
|
|
801
|
+
issue_provider: jira
|
|
802
|
+
jira:
|
|
803
|
+
base_url: https://your-domain.atlassian.net
|
|
804
|
+
project_key: PROJ
|
|
805
|
+
status_map: # optional — unset by default, per state
|
|
806
|
+
ready: To Do
|
|
807
|
+
working: In Progress
|
|
808
|
+
review: In Review
|
|
809
|
+
done: Done
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
Only the states you map are touched; an unmapped state keeps today's label-only behavior. This is opt-in rather than automatic because Jira workflows vary by project/scheme — status names and which transitions are reachable from where isn't something spf can assume, unlike labels. A configured name with no reachable transition (or a rejected one) is logged and skipped, never thrown — the label update is what `spf watch` actually depends on; status sync is a best-effort convenience on top of it.
|
|
813
|
+
|
|
814
|
+
`spf watch init` still doesn't create any labels here (Jira labels are freeform strings with no color/description registry to seed, unlike GitHub's) — it reports the labels this run will use. With `watch.refine.enabled`, it also validates `watch.jira.issue_types` against the real project's issue types, read-only, and exits non-zero on a mismatch — see "Refining specs" above. Independent of `refine.enabled`, if `watch.jira.status_map` has any entry configured, both `spf watch init` and `spf watch`'s own startup check validate it against the real project's statuses the same way, and exit non-zero on a mismatch.
|
|
783
815
|
|
|
784
816
|
### Bitbucket (`code_host: bitbucket`)
|
|
785
817
|
|
|
@@ -839,6 +871,22 @@ Getting each channel's URL:
|
|
|
839
871
|
- **webhook** — any endpoint that accepts a JSON POST of the event: Discord,
|
|
840
872
|
n8n, Zapier, a homegrown receiver.
|
|
841
873
|
|
|
874
|
+
One webhook, many `spf` instances: if several repos' `spf watch` (or
|
|
875
|
+
`spf run`) all post to the same Slack/Teams/webhook endpoint, set
|
|
876
|
+
`notifications.project` to a short label so messages from each are
|
|
877
|
+
distinguishable — it prefixes every title (`[api] watch: reconcileOrphans
|
|
878
|
+
error`) and adds a `repo` field. Left unset, it falls back to `watch.repo`,
|
|
879
|
+
so most `spf watch` setups need nothing extra; set it explicitly when
|
|
880
|
+
`watch.repo` is blank or two watched repos share a basename.
|
|
881
|
+
|
|
882
|
+
```yaml
|
|
883
|
+
notifications:
|
|
884
|
+
events: attention
|
|
885
|
+
project: api # optional; defaults to watch.repo
|
|
886
|
+
channels:
|
|
887
|
+
- kind: slack
|
|
888
|
+
```
|
|
889
|
+
|
|
842
890
|
Delivery never blocks or fails a run: an unconfigured/misconfigured channel
|
|
843
891
|
is skipped with one warning, and a failed POST logs one line and is
|
|
844
892
|
swallowed — never changes a run's exit code. One thing worth knowing under
|
|
@@ -160,6 +160,9 @@ Full mechanism: the main README's "`spf watch`" section. Field reference:
|
|
|
160
160
|
| `chain_options` | map of string -> string | Options passed straight through to `chain` (and `refine.chain`) for every unattended dispatch — the same shape an interactive `spf <chain> --suite <name>` builds, e.g. `{suite: strict}` or `{agent: some-agent}`. Default `{}`. Only useful for a chain whose behavior actually reads the option (a step-derived chain's `--suite`; an imperative chain ignores an option it doesn't know about). |
|
|
161
161
|
| `jira.base_url` / `jira.project_key` | string | Only consulted when `issue_provider: jira`. |
|
|
162
162
|
| `jira.issue_types` | map: `epic`/`feature`/`story`/`bug`/`task`/`spec` -> string | Only consulted when `issue_provider: jira` AND `refine.enabled`. What each `RefinedIssue.kind` (plus `spec`, a standalone spec proposed by a split — see below) creates as on Jira — defaults `epic`/`feature` → `Epic`, `story`/`spec` → `Story`, `bug` → `Bug`, `task` → `Task`, overridable per kind. Validated against the real project by both `spf watch init` and `spf watch`'s own startup check. |
|
|
163
|
+
| `jira.status_map` | map: `spec-ready`/`ready`/`working`/`review`/`done`/`blocked` -> string | Only consulted when `issue_provider: jira`. Optional, empty (unset) by default — when a state has no entry, `spf watch` updates only the `<prefix>:<state>` label on that transition, exactly as before this field existed. When a state IS mapped to a real Jira status name (e.g. `working: "In Progress"`), `spf watch` also transitions the issue's native Status field there, best-effort: no reachable transition, or a rejected one, is logged and skipped rather than failing the run — the label update is what `spf watch` actually depends on, status sync is a convenience on top. Validated against the real project's statuses by both `spf watch init` and `spf watch`'s own startup check, same as `issue_types`. |
|
|
164
|
+
| `github.project_number` | int ≥0 | Only consulted when `issue_provider: github`. Which of the repo OWNER's Projects v2 boards `status_map` syncs against (Projects v2 numbers are per-owner, not per-repo — see the board's own URL). Default `0`, which disables status sync outright regardless of `status_map`; there's no sane "guess the board" default. |
|
|
165
|
+
| `github.status_map` | map: `spec-ready`/`ready`/`working`/`review`/`done`/`blocked` -> string | Only consulted when `issue_provider: github` AND `project_number` is set. Optional, empty (unset) by default — an unmapped state stays label-only, a mapped one also sets the Projects v2 "Status" field, best-effort (no matching Status option, or a rejected mutation, is logged and skipped, never thrown — the label update is what `spf watch` actually depends on, status sync is a convenience on top). Needs `GITHUB_TOKEN` to carry the `project` scope in addition to `repo`/`public_repo` — Projects v2 is GraphQL-only. Validated against the real project's Status options by both `spf watch init` and `spf watch`'s own startup check. |
|
|
163
166
|
| `refine.enabled` | bool | Turns on the second lane: decompose a `<prefix>:spec-ready` product spec into a feature/story-or-bug tree of real issues, instead of running `chain` against it directly (a spec isn't individually workable). Default `false` — off by default, so an existing `watch:` config is unaffected by upgrading. Needs `issue_provider: github` or `"jira"` — both implement issue authoring (create + link a hierarchy); any other value fails loudly at startup. |
|
|
164
167
|
| `refine.chain` | string | Which registered chain runs per claimed spec. Default `refine`. |
|
|
165
168
|
| `refine.concurrency` | int ≥1 | The refine lane's own budget, separate from `concurrency`. Default `1`. |
|
|
@@ -277,6 +280,7 @@ default; adding it is entirely additive.
|
|
|
277
280
|
|---|---|---|
|
|
278
281
|
| `events` | `"off"` \| `"errors"` \| `"attention"` \| `"all"` | The whole filter, narrowest to widest. `off` (default): nothing. `errors`: only true failures — failed runs/phases (`run_failed`/`phase_failed`), `watch_error`. `attention`: `errors` plus anything needing a human but not itself a failure — a blocked issue (`issue_blocked`) or a spec needing feedback (`spec_needs_feedback`). `all`: every curated milestone (run started, issue claimed, PR opened, ...) plus `attention` and `errors`. |
|
|
279
282
|
| `timeout_ms` | int | Per-request timeout for a channel's HTTP POST. Default `5000`. |
|
|
283
|
+
| `project` | string | Label prefixed onto every outbound title (`[api] watch: ...`) and added as a `repo` field — for disambiguating multiple `spf` instances that share one webhook. Default `""`, which falls back to `watch.repo`. |
|
|
280
284
|
| `channels[]` | array | See below. |
|
|
281
285
|
|
|
282
286
|
`channels[].kind`: `"slack"` \| `"teams"` \| `"webhook"`. `channels[].events`
|
|
@@ -861,6 +861,14 @@ export async function doctorCommand(argv) {
|
|
|
861
861
|
? "set"
|
|
862
862
|
: 'not set — spf watch needs a classic PAT with "repo" scope (or "public_repo" for a public-only repo); see README.md\'s "GITHUB_TOKEN scope" section');
|
|
863
863
|
}
|
|
864
|
+
if (cfg.watch.issue_provider === "github") {
|
|
865
|
+
const statusMapEntries = Object.entries(cfg.watch.github.status_map).filter(([, v]) => Boolean(v));
|
|
866
|
+
check(report, "watch.github.status_map", true, statusMapEntries.length > 0
|
|
867
|
+
? cfg.watch.github.project_number
|
|
868
|
+
? `${statusMapEntries.length} state(s) configured to sync Projects v2 #${cfg.watch.github.project_number}'s Status field — run \`spf watch init\` to validate them, and make sure GITHUB_TOKEN has the "project" scope`
|
|
869
|
+
: `${statusMapEntries.length} state(s) configured but watch.github.project_number is unset — status sync stays disabled until it's set`
|
|
870
|
+
: "not configured — spf watch will only update labels on this repo, never a Projects v2 board (optional, off by default)", "info");
|
|
871
|
+
}
|
|
864
872
|
if (cfg.watch.issue_provider === "jira") {
|
|
865
873
|
check(report, "watch.jira", Boolean(cfg.watch.jira.base_url.trim() && cfg.watch.jira.project_key.trim()), cfg.watch.jira.base_url.trim() && cfg.watch.jira.project_key.trim()
|
|
866
874
|
? `${cfg.watch.jira.base_url} (${cfg.watch.jira.project_key})`
|
|
@@ -868,6 +876,10 @@ export async function doctorCommand(argv) {
|
|
|
868
876
|
check(report, "JIRA_EMAIL / JIRA_API_TOKEN", Boolean(process.env["JIRA_EMAIL"] && process.env["JIRA_API_TOKEN"]), process.env["JIRA_EMAIL"] && process.env["JIRA_API_TOKEN"]
|
|
869
877
|
? "set"
|
|
870
878
|
: 'not set — spf watch needs an Atlassian account email plus an API token (id.atlassian.com -> Security -> API tokens); see README.md\'s "spf watch" section');
|
|
879
|
+
const statusMapEntries = Object.entries(cfg.watch.jira.status_map).filter(([, v]) => Boolean(v));
|
|
880
|
+
check(report, "watch.jira.status_map", true, statusMapEntries.length > 0
|
|
881
|
+
? `${statusMapEntries.length} state(s) configured to sync a native Jira status — run \`spf watch init\` to validate them against the real project's statuses`
|
|
882
|
+
: "not configured — spf watch will only update labels on this project, never the Jira Status field (optional, off by default)", "info");
|
|
871
883
|
}
|
|
872
884
|
if (cfg.watch.code_host === "bitbucket") {
|
|
873
885
|
check(report, "BITBUCKET_EMAIL / BITBUCKET_API_TOKEN", Boolean(process.env["BITBUCKET_EMAIL"] && process.env["BITBUCKET_API_TOKEN"]), process.env["BITBUCKET_EMAIL"] && process.env["BITBUCKET_API_TOKEN"]
|
|
@@ -1023,6 +1035,15 @@ export async function doctorCommand(argv) {
|
|
|
1023
1035
|
const label = ch.name ? `${ch.kind} (${ch.name})` : ch.kind;
|
|
1024
1036
|
check(report, `notifications: ${label}`, Boolean(process.env[envKey]), process.env[envKey] ? `${envKey} set` : `${envKey} is not set`);
|
|
1025
1037
|
}
|
|
1038
|
+
// See `NotificationsConfigSchema.project`'s doc comment / `resolveNotifier`'s
|
|
1039
|
+
// same fallback — informational only (never fails doctor), since an
|
|
1040
|
+
// unset project tag is harmless unless this webhook ends up shared.
|
|
1041
|
+
if (cfg.notifications.channels.length > 0) {
|
|
1042
|
+
const project = cfg.notifications.project.trim() || cfg.watch.repo.trim();
|
|
1043
|
+
check(report, "notifications.project", true, project
|
|
1044
|
+
? `tag: "${project}"`
|
|
1045
|
+
: "not set, and watch.repo is empty — outbound messages won't carry a repo/project tag; if this webhook is ever shared across multiple spf instances, set notifications.project to tell them apart", project ? "info" : "warn");
|
|
1046
|
+
}
|
|
1026
1047
|
}
|
|
1027
1048
|
return finish(report, flags["json"]);
|
|
1028
1049
|
}
|
|
@@ -100,7 +100,7 @@ export function resolveIssueProvider(cfg) {
|
|
|
100
100
|
console.error('GITHUB_TOKEN is not set — spf watch needs a classic PAT with "repo" scope (or "public_repo" for a public-only repo). See README.md\'s "GITHUB_TOKEN scope" section.');
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
|
-
return new GitHubProvider(repo, cfg.watch.label_prefix, token);
|
|
103
|
+
return new GitHubProvider(repo, cfg.watch.label_prefix, token, cfg.watch.github.project_number, cfg.watch.github.status_map);
|
|
104
104
|
}
|
|
105
105
|
if (cfg.watch.issue_provider === "jira") {
|
|
106
106
|
if (!cfg.watch.jira.base_url.trim() || !cfg.watch.jira.project_key.trim()) {
|
|
@@ -113,7 +113,7 @@ export function resolveIssueProvider(cfg) {
|
|
|
113
113
|
console.error('JIRA_EMAIL and JIRA_API_TOKEN must both be set — spf watch needs an Atlassian account email plus an API token (id.atlassian.com -> Security -> API tokens). See README.md\'s "spf watch" section.');
|
|
114
114
|
return null;
|
|
115
115
|
}
|
|
116
|
-
return new JiraProvider(cfg.watch.jira.base_url, cfg.watch.jira.project_key, cfg.watch.label_prefix, email, token, cfg.watch.jira.issue_types);
|
|
116
|
+
return new JiraProvider(cfg.watch.jira.base_url, cfg.watch.jira.project_key, cfg.watch.label_prefix, email, token, cfg.watch.jira.issue_types, cfg.watch.jira.status_map);
|
|
117
117
|
}
|
|
118
118
|
console.error(`watch.issue_provider ${JSON.stringify(cfg.watch.issue_provider)} is not supported`);
|
|
119
119
|
return null;
|
|
@@ -136,7 +136,7 @@ function resolveCodeHostProvider(cfg) {
|
|
|
136
136
|
console.error('GITHUB_TOKEN is not set — spf watch needs a classic PAT with "repo" scope (or "public_repo" for a public-only repo). See README.md\'s "GITHUB_TOKEN scope" section.');
|
|
137
137
|
return null;
|
|
138
138
|
}
|
|
139
|
-
return new GitHubProvider(cfg.watch.repo, cfg.watch.label_prefix, token);
|
|
139
|
+
return new GitHubProvider(cfg.watch.repo, cfg.watch.label_prefix, token, cfg.watch.github.project_number, cfg.watch.github.status_map);
|
|
140
140
|
}
|
|
141
141
|
if (cfg.watch.code_host === "bitbucket") {
|
|
142
142
|
const email = process.env["BITBUCKET_EMAIL"];
|
|
@@ -196,6 +196,24 @@ export async function watchInitCommand(argv) {
|
|
|
196
196
|
if (checks.some((c) => !c.exists))
|
|
197
197
|
return 1;
|
|
198
198
|
}
|
|
199
|
+
if (cfg.watch.issue_provider === "jira" && provider instanceof JiraProvider && Object.values(cfg.watch.jira.status_map).some(Boolean)) {
|
|
200
|
+
const checks = await provider.validateStatusMap();
|
|
201
|
+
console.log(`\nvalidating watch.jira.status_map against ${cfg.watch.jira.project_key}:`);
|
|
202
|
+
for (const c of checks) {
|
|
203
|
+
console.log(` ${c.exists ? "✓" : "✗"} ${c.state} → ${c.jiraStatus}${c.exists ? "" : ` — no status named "${c.jiraStatus}" in ${cfg.watch.jira.project_key}`}`);
|
|
204
|
+
}
|
|
205
|
+
if (checks.some((c) => !c.exists))
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
if (cfg.watch.issue_provider === "github" && provider instanceof GitHubProvider && Object.values(cfg.watch.github.status_map).some(Boolean)) {
|
|
209
|
+
const checks = await provider.validateStatusMap();
|
|
210
|
+
console.log(`\nvalidating watch.github.status_map against Projects v2 #${cfg.watch.github.project_number}:`);
|
|
211
|
+
for (const c of checks) {
|
|
212
|
+
console.log(` ${c.exists ? "✓" : "✗"} ${c.state} → ${c.githubStatus}${c.exists ? "" : ` — no Status option named "${c.githubStatus}" on Projects v2 #${cfg.watch.github.project_number}`}`);
|
|
213
|
+
}
|
|
214
|
+
if (checks.some((c) => !c.exists))
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
199
217
|
return 0;
|
|
200
218
|
}
|
|
201
219
|
/** Shared by `runChain`/`runRefine`/the fan-out lane's dispatch: best-effort enrichment of a generic "didn't succeed" message with the first phase that actually failed, read back from the worktree's own (symlinked) trace db. Top-level (not a `watchCommand` local) so `makeWatchFanoutDispatch` below can share it — see that factory's own doc comment for why. */
|
|
@@ -396,6 +414,49 @@ export async function watchCommand(argv) {
|
|
|
396
414
|
}
|
|
397
415
|
}
|
|
398
416
|
}
|
|
417
|
+
// watch.jira.status_map applies to every build-lane transition, independent
|
|
418
|
+
// of watch.refine.enabled — validated at startup for the same reason
|
|
419
|
+
// issue_types is: a bad status name should stop the daemon before it
|
|
420
|
+
// starts, not fail silently (well, log-and-skip — see jira_provider.ts's
|
|
421
|
+
// syncStatus()) on every single transition once it's running. Silent when
|
|
422
|
+
// status_map is empty (the default, opt-in feature) or has no mismatches.
|
|
423
|
+
if (cfg.watch.issue_provider === "jira" && provider instanceof JiraProvider) {
|
|
424
|
+
const configured = Object.values(cfg.watch.jira.status_map).some(Boolean);
|
|
425
|
+
if (configured) {
|
|
426
|
+
const checks = await provider.validateStatusMap();
|
|
427
|
+
const mismatches = checks.filter((c) => !c.exists);
|
|
428
|
+
if (mismatches.length > 0) {
|
|
429
|
+
console.error(`watch.jira.status_map has ${mismatches.length} mismatch(es) against ${cfg.watch.jira.project_key}:`);
|
|
430
|
+
for (const c of checks) {
|
|
431
|
+
console.error(` ${c.exists ? "✓" : "✗"} ${c.state} → ${c.jiraStatus}${c.exists ? "" : ` — no status named "${c.jiraStatus}" in ${cfg.watch.jira.project_key}`}`);
|
|
432
|
+
}
|
|
433
|
+
console.error(`Fix watch.jira.status_map, or the project's statuses, before running spf watch unattended. Run \`spf watch init\` any time to re-check.`);
|
|
434
|
+
return 1;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// watch.github.status_map applies to every build-lane transition,
|
|
439
|
+
// independent of watch.refine.enabled — validated at startup for the same
|
|
440
|
+
// reason watch.jira.issue_types is: a bad Status option name should stop
|
|
441
|
+
// the daemon before it starts, not fail silently (well, log-and-skip —
|
|
442
|
+
// see github_provider.ts's syncStatus()) on every single transition once
|
|
443
|
+
// it's running. Silent when status_map is empty (the default, opt-in
|
|
444
|
+
// feature) or has no mismatches.
|
|
445
|
+
if (cfg.watch.issue_provider === "github" && provider instanceof GitHubProvider) {
|
|
446
|
+
const configured = Object.values(cfg.watch.github.status_map).some(Boolean);
|
|
447
|
+
if (configured) {
|
|
448
|
+
const checks = await provider.validateStatusMap();
|
|
449
|
+
const mismatches = checks.filter((c) => !c.exists);
|
|
450
|
+
if (mismatches.length > 0) {
|
|
451
|
+
console.error(`watch.github.status_map has ${mismatches.length} mismatch(es) against Projects v2 #${cfg.watch.github.project_number}:`);
|
|
452
|
+
for (const c of checks) {
|
|
453
|
+
console.error(` ${c.exists ? "✓" : "✗"} ${c.state} → ${c.githubStatus}${c.exists ? "" : ` — no Status option named "${c.githubStatus}" on Projects v2 #${cfg.watch.github.project_number}`}`);
|
|
454
|
+
}
|
|
455
|
+
console.error(`Fix watch.github.status_map (and watch.github.project_number), or the project's Status options, before running spf watch unattended. Run \`spf watch init\` any time to re-check.`);
|
|
456
|
+
return 1;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
399
460
|
// watch.fanout.n > 1 startup gates — all guarded so watch.fanout.n: 1 (the
|
|
400
461
|
// default) reaches none of them, and a bad configuration stops the daemon
|
|
401
462
|
// before it starts rather than failing silently every tick.
|
package/dist/cli/interview.js
CHANGED
|
@@ -412,14 +412,22 @@ export async function runInterview(asker, ctx) {
|
|
|
412
412
|
});
|
|
413
413
|
const projectKey = await asker.text("Jira project key", { validate: (val) => (val.trim() ? null : "required") });
|
|
414
414
|
watch.jira = { base_url: baseUrl.replace(/\/+$/, ""), project_key: projectKey.toUpperCase() };
|
|
415
|
+
asker.note("Jira workflow status (the board's Status column) stays untouched by default — labels alone drive spf watch. " +
|
|
416
|
+
"Opt in with watch.jira.status_map (per-state -> Jira status name) to also sync it, then run `spf watch init` to validate.");
|
|
415
417
|
}
|
|
416
418
|
const needsGithub = issueProvider === "github" || codeHost === "github";
|
|
417
419
|
if (needsGithub) {
|
|
418
|
-
asker.note(
|
|
420
|
+
asker.note(issueProvider === "github"
|
|
421
|
+
? 'classic PAT, "repo" scope (private) or "public_repo" (public-only) — add "project" too if you plan to use watch.github.status_map (native Projects v2 board sync).'
|
|
422
|
+
: 'classic PAT, "repo" scope (private) or "public_repo" (public-only).');
|
|
419
423
|
const token = await asker.secret("GITHUB_TOKEN", { current: ctx.existingEnv.get("GITHUB_TOKEN") });
|
|
420
424
|
if (token)
|
|
421
425
|
env["GITHUB_TOKEN"] = token;
|
|
422
426
|
envExampleKeys.push("GITHUB_TOKEN");
|
|
427
|
+
if (issueProvider === "github") {
|
|
428
|
+
asker.note("GitHub Projects v2 board status (the board's Status column) stays untouched by default — labels alone drive spf watch. " +
|
|
429
|
+
"Opt in with watch.github.project_number + watch.github.status_map (per-state -> Status option name) to also sync it, then run `spf watch init` to validate.");
|
|
430
|
+
}
|
|
423
431
|
}
|
|
424
432
|
if (issueProvider === "jira") {
|
|
425
433
|
const email = await asker.text("JIRA_EMAIL", { default: ctx.gitEmail ?? "" });
|
|
@@ -527,6 +535,15 @@ export async function runInterview(asker, ctx) {
|
|
|
527
535
|
const timeoutMs = await asker.text("notifications.timeout_ms", { default: "5000" });
|
|
528
536
|
if (timeoutMs !== "5000")
|
|
529
537
|
notifications.timeout_ms = Number(timeoutMs);
|
|
538
|
+
// Only matters when one Slack/Teams/webhook endpoint is shared across
|
|
539
|
+
// several `spf` instances — defaults to watch.repo (resolveNotifier's
|
|
540
|
+
// own fallback), so most single-repo setups can just accept it and
|
|
541
|
+
// write nothing extra into the generated config.
|
|
542
|
+
const repoDefault = watch?.repo || "";
|
|
543
|
+
asker.note("Tags every outbound title/field so messages are distinguishable if this webhook is shared across repos — defaults to watch.repo.");
|
|
544
|
+
const project = await asker.text("notifications.project", { default: repoDefault });
|
|
545
|
+
if (project !== repoDefault)
|
|
546
|
+
notifications.project = project;
|
|
530
547
|
}
|
|
531
548
|
}
|
|
532
549
|
// ── 6. review + confirm ─────────────────────────────────────────────────────
|
|
@@ -839,14 +839,43 @@ export declare const JiraIssueTypeMapSchema: v.ObjectSchema<{
|
|
|
839
839
|
readonly spec: v.OptionalSchema<v.StringSchema<undefined>, "Story">;
|
|
840
840
|
}, undefined>;
|
|
841
841
|
export type JiraIssueTypeMap = v.InferOutput<typeof JiraIssueTypeMapSchema>;
|
|
842
|
+
/**
|
|
843
|
+
* Optional, per-project `WatchState` -> Jira workflow-status-name map.
|
|
844
|
+
* Unlike `JiraIssueTypeMapSchema`, this has no sane universal default:
|
|
845
|
+
* "To Do" vs "Open" vs "Backlog" (and everything in between) is entirely
|
|
846
|
+
* per-project workflow configuration in Jira, so every field defaults to
|
|
847
|
+
* unset. A `WatchState` with no entry here keeps today's behavior exactly —
|
|
848
|
+
* `jira_provider.ts`'s `transition()`/`claim()` update the label only and
|
|
849
|
+
* never attempt a status change for it.
|
|
850
|
+
*
|
|
851
|
+
* Deliberately covers only the states a human's Jira board status would
|
|
852
|
+
* plausibly want to reflect (the build lane, `spec-ready` through
|
|
853
|
+
* `blocked`) — the refine-lane states (`refining`, `split-proposed`, ...)
|
|
854
|
+
* are internal bookkeeping for `core/refine.ts`, not board-visible work.
|
|
855
|
+
*
|
|
856
|
+
* `jira_provider.ts`'s `syncStatus()` is the reader; `validateStatusMap()`
|
|
857
|
+
* is what `spf watch init` and `spf watch`'s own startup check should call
|
|
858
|
+
* to confirm each configured name is a real status on the project before an
|
|
859
|
+
* unattended run relies on it — same shape as `issue_types`/
|
|
860
|
+
* `validateIssueTypes()`.
|
|
861
|
+
*/
|
|
862
|
+
export declare const JiraStatusMapSchema: v.ObjectSchema<{
|
|
863
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
864
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
865
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
866
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
867
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
868
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
869
|
+
}, undefined>;
|
|
870
|
+
export type JiraStatusMap = v.InferOutput<typeof JiraStatusMapSchema>;
|
|
842
871
|
/**
|
|
843
872
|
* Only consulted when `issue_provider: jira`. Auth is `JIRA_EMAIL` +
|
|
844
873
|
* `JIRA_API_TOKEN` env vars, checked at startup like `GITHUB_TOKEN`. Whole-
|
|
845
874
|
* object replace on config-file-layer merge, like `refine`/
|
|
846
875
|
* `observability.otel` (see `agents.ts`'s `mergeRawConfig`) — an override
|
|
847
|
-
* file that touches `watch.jira` at all must repeat `issue_types
|
|
848
|
-
* wants to keep a customized mapping, same caveat
|
|
849
|
-
* `base_url`/`project_key` today.
|
|
876
|
+
* file that touches `watch.jira` at all must repeat `issue_types`/
|
|
877
|
+
* `status_map` too if it wants to keep a customized mapping, same caveat
|
|
878
|
+
* that already applies to `base_url`/`project_key` today.
|
|
850
879
|
*/
|
|
851
880
|
export declare const WatchJiraConfigSchema: v.ObjectSchema<{
|
|
852
881
|
readonly base_url: v.OptionalSchema<v.StringSchema<undefined>, "">;
|
|
@@ -873,8 +902,85 @@ export declare const WatchJiraConfigSchema: v.ObjectSchema<{
|
|
|
873
902
|
task: string;
|
|
874
903
|
spec: string;
|
|
875
904
|
}>;
|
|
905
|
+
readonly status_map: v.OptionalSchema<v.ObjectSchema<{
|
|
906
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
907
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
908
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
909
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
910
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
911
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
912
|
+
}, undefined>, () => {
|
|
913
|
+
"spec-ready"?: string | undefined;
|
|
914
|
+
ready?: string | undefined;
|
|
915
|
+
working?: string | undefined;
|
|
916
|
+
review?: string | undefined;
|
|
917
|
+
done?: string | undefined;
|
|
918
|
+
blocked?: string | undefined;
|
|
919
|
+
}>;
|
|
876
920
|
}, undefined>;
|
|
877
921
|
export type WatchJiraConfig = v.InferOutput<typeof WatchJiraConfigSchema>;
|
|
922
|
+
/**
|
|
923
|
+
* Optional, per-repo `WatchState` -> GitHub Projects v2 "Status" option-name
|
|
924
|
+
* map — the GitHub-side twin of `JiraIssueTypeMap`'s sibling on the Jira
|
|
925
|
+
* provider (see `jira_provider.ts`'s `status_map`). No sane universal
|
|
926
|
+
* default: a board's Status column options ("Todo"/"In Progress"/"Done", or
|
|
927
|
+
* anything else) are per-project configuration, so every field defaults to
|
|
928
|
+
* unset. A `WatchState` with no entry keeps today's behavior exactly —
|
|
929
|
+
* `github_provider.ts`'s `transition()`/`claim()` update the label only and
|
|
930
|
+
* never touch Projects v2 for it.
|
|
931
|
+
*
|
|
932
|
+
* Same six build-lane states as Jira's map — the refine-lane's own
|
|
933
|
+
* bookkeeping states (`refining`, `split-proposed`, ...) aren't board-visible
|
|
934
|
+
* work, same reasoning as `jira_provider.ts`'s `JiraStatusMapSchema`.
|
|
935
|
+
*
|
|
936
|
+
* `github_provider.ts`'s `syncStatus()` is the reader; `validateStatusMap()`
|
|
937
|
+
* is what `spf watch init` and `spf watch`'s own startup check call to
|
|
938
|
+
* confirm each configured name is a real Status option on the configured
|
|
939
|
+
* project before an unattended run relies on it.
|
|
940
|
+
*/
|
|
941
|
+
export declare const GithubStatusMapSchema: v.ObjectSchema<{
|
|
942
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
943
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
944
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
945
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
946
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
947
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
948
|
+
}, undefined>;
|
|
949
|
+
export type GithubStatusMap = v.InferOutput<typeof GithubStatusMapSchema>;
|
|
950
|
+
/**
|
|
951
|
+
* Only consulted when `issue_provider: github` (or `code_host: github`) AND
|
|
952
|
+
* `status_map` actually has an entry configured — `project_number: 0` (the
|
|
953
|
+
* default) disables status sync outright regardless of `status_map`, since
|
|
954
|
+
* there's no sane "guess the board" default: a repo can have zero, one, or
|
|
955
|
+
* many Projects v2 boards, and none of them is canonical. Scoped to the
|
|
956
|
+
* repo's OWNER (`watch.repo`'s "owner/name", the owner half) — GitHub
|
|
957
|
+
* Projects v2 numbers are per-owner, not per-repo, so `project_number: 3`
|
|
958
|
+
* means owner's project #3, which may or may not have this repo's issues on
|
|
959
|
+
* it yet (`github_provider.ts`'s `syncStatus()` adds the issue to the
|
|
960
|
+
* project itself the first time it needs to). Whole-object replace on
|
|
961
|
+
* config-file-layer merge, like `jira`/`refine` above — an override file
|
|
962
|
+
* that touches `watch.github` at all must repeat `status_map` too if it
|
|
963
|
+
* wants to keep a customized mapping.
|
|
964
|
+
*/
|
|
965
|
+
export declare const WatchGithubConfigSchema: v.ObjectSchema<{
|
|
966
|
+
readonly project_number: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, 0>;
|
|
967
|
+
readonly status_map: v.OptionalSchema<v.ObjectSchema<{
|
|
968
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
969
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
970
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
971
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
972
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
973
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
974
|
+
}, undefined>, () => {
|
|
975
|
+
"spec-ready"?: string | undefined;
|
|
976
|
+
ready?: string | undefined;
|
|
977
|
+
working?: string | undefined;
|
|
978
|
+
review?: string | undefined;
|
|
979
|
+
done?: string | undefined;
|
|
980
|
+
blocked?: string | undefined;
|
|
981
|
+
}>;
|
|
982
|
+
}, undefined>;
|
|
983
|
+
export type WatchGithubConfig = v.InferOutput<typeof WatchGithubConfigSchema>;
|
|
878
984
|
/**
|
|
879
985
|
* The second `spf watch` lane: decompose a `<prefix>:spec-ready` product
|
|
880
986
|
* spec into a feature/story tree of real issues, instead of running
|
|
@@ -1024,6 +1130,21 @@ export declare const WatchConfigSchema: v.ObjectSchema<{
|
|
|
1024
1130
|
task: string;
|
|
1025
1131
|
spec: string;
|
|
1026
1132
|
}>;
|
|
1133
|
+
readonly status_map: v.OptionalSchema<v.ObjectSchema<{
|
|
1134
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1135
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1136
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1137
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1138
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1139
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1140
|
+
}, undefined>, () => {
|
|
1141
|
+
"spec-ready"?: string | undefined;
|
|
1142
|
+
ready?: string | undefined;
|
|
1143
|
+
working?: string | undefined;
|
|
1144
|
+
review?: string | undefined;
|
|
1145
|
+
done?: string | undefined;
|
|
1146
|
+
blocked?: string | undefined;
|
|
1147
|
+
}>;
|
|
1027
1148
|
}, undefined>, () => {
|
|
1028
1149
|
base_url: string;
|
|
1029
1150
|
project_key: string;
|
|
@@ -1035,6 +1156,42 @@ export declare const WatchConfigSchema: v.ObjectSchema<{
|
|
|
1035
1156
|
task: string;
|
|
1036
1157
|
spec: string;
|
|
1037
1158
|
};
|
|
1159
|
+
status_map: {
|
|
1160
|
+
"spec-ready"?: string | undefined;
|
|
1161
|
+
ready?: string | undefined;
|
|
1162
|
+
working?: string | undefined;
|
|
1163
|
+
review?: string | undefined;
|
|
1164
|
+
done?: string | undefined;
|
|
1165
|
+
blocked?: string | undefined;
|
|
1166
|
+
};
|
|
1167
|
+
}>;
|
|
1168
|
+
readonly github: v.OptionalSchema<v.ObjectSchema<{
|
|
1169
|
+
readonly project_number: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, 0>;
|
|
1170
|
+
readonly status_map: v.OptionalSchema<v.ObjectSchema<{
|
|
1171
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1172
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1173
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1174
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1175
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1176
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1177
|
+
}, undefined>, () => {
|
|
1178
|
+
"spec-ready"?: string | undefined;
|
|
1179
|
+
ready?: string | undefined;
|
|
1180
|
+
working?: string | undefined;
|
|
1181
|
+
review?: string | undefined;
|
|
1182
|
+
done?: string | undefined;
|
|
1183
|
+
blocked?: string | undefined;
|
|
1184
|
+
}>;
|
|
1185
|
+
}, undefined>, () => {
|
|
1186
|
+
project_number: number;
|
|
1187
|
+
status_map: {
|
|
1188
|
+
"spec-ready"?: string | undefined;
|
|
1189
|
+
ready?: string | undefined;
|
|
1190
|
+
working?: string | undefined;
|
|
1191
|
+
review?: string | undefined;
|
|
1192
|
+
done?: string | undefined;
|
|
1193
|
+
blocked?: string | undefined;
|
|
1194
|
+
};
|
|
1038
1195
|
}>;
|
|
1039
1196
|
readonly refine: v.OptionalSchema<v.ObjectSchema<{
|
|
1040
1197
|
readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
@@ -1099,6 +1256,7 @@ export declare const NotificationsConfigSchema: v.ObjectSchema<{
|
|
|
1099
1256
|
readonly events: v.OptionalSchema<v.NullableSchema<v.PicklistSchema<["off", "errors", "attention", "all"], undefined>, undefined>, undefined>;
|
|
1100
1257
|
readonly name: v.OptionalSchema<v.StringSchema<undefined>, "">;
|
|
1101
1258
|
}, undefined>, undefined>, () => never[]>;
|
|
1259
|
+
readonly project: v.OptionalSchema<v.StringSchema<undefined>, "">;
|
|
1102
1260
|
}, undefined>;
|
|
1103
1261
|
export type NotificationsConfig = v.InferOutput<typeof NotificationsConfigSchema>;
|
|
1104
1262
|
/**
|
|
@@ -1362,6 +1520,21 @@ export declare const SFConfigSchema: v.ObjectSchema<{
|
|
|
1362
1520
|
task: string;
|
|
1363
1521
|
spec: string;
|
|
1364
1522
|
}>;
|
|
1523
|
+
readonly status_map: v.OptionalSchema<v.ObjectSchema<{
|
|
1524
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1525
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1526
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1527
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1528
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1529
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1530
|
+
}, undefined>, () => {
|
|
1531
|
+
"spec-ready"?: string | undefined;
|
|
1532
|
+
ready?: string | undefined;
|
|
1533
|
+
working?: string | undefined;
|
|
1534
|
+
review?: string | undefined;
|
|
1535
|
+
done?: string | undefined;
|
|
1536
|
+
blocked?: string | undefined;
|
|
1537
|
+
}>;
|
|
1365
1538
|
}, undefined>, () => {
|
|
1366
1539
|
base_url: string;
|
|
1367
1540
|
project_key: string;
|
|
@@ -1373,6 +1546,42 @@ export declare const SFConfigSchema: v.ObjectSchema<{
|
|
|
1373
1546
|
task: string;
|
|
1374
1547
|
spec: string;
|
|
1375
1548
|
};
|
|
1549
|
+
status_map: {
|
|
1550
|
+
"spec-ready"?: string | undefined;
|
|
1551
|
+
ready?: string | undefined;
|
|
1552
|
+
working?: string | undefined;
|
|
1553
|
+
review?: string | undefined;
|
|
1554
|
+
done?: string | undefined;
|
|
1555
|
+
blocked?: string | undefined;
|
|
1556
|
+
};
|
|
1557
|
+
}>;
|
|
1558
|
+
readonly github: v.OptionalSchema<v.ObjectSchema<{
|
|
1559
|
+
readonly project_number: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, 0>;
|
|
1560
|
+
readonly status_map: v.OptionalSchema<v.ObjectSchema<{
|
|
1561
|
+
readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1562
|
+
readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1563
|
+
readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1564
|
+
readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1565
|
+
readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1566
|
+
readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
1567
|
+
}, undefined>, () => {
|
|
1568
|
+
"spec-ready"?: string | undefined;
|
|
1569
|
+
ready?: string | undefined;
|
|
1570
|
+
working?: string | undefined;
|
|
1571
|
+
review?: string | undefined;
|
|
1572
|
+
done?: string | undefined;
|
|
1573
|
+
blocked?: string | undefined;
|
|
1574
|
+
}>;
|
|
1575
|
+
}, undefined>, () => {
|
|
1576
|
+
project_number: number;
|
|
1577
|
+
status_map: {
|
|
1578
|
+
"spec-ready"?: string | undefined;
|
|
1579
|
+
ready?: string | undefined;
|
|
1580
|
+
working?: string | undefined;
|
|
1581
|
+
review?: string | undefined;
|
|
1582
|
+
done?: string | undefined;
|
|
1583
|
+
blocked?: string | undefined;
|
|
1584
|
+
};
|
|
1376
1585
|
}>;
|
|
1377
1586
|
readonly refine: v.OptionalSchema<v.ObjectSchema<{
|
|
1378
1587
|
readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
@@ -1420,6 +1629,25 @@ export declare const SFConfigSchema: v.ObjectSchema<{
|
|
|
1420
1629
|
task: string;
|
|
1421
1630
|
spec: string;
|
|
1422
1631
|
};
|
|
1632
|
+
status_map: {
|
|
1633
|
+
"spec-ready"?: string | undefined;
|
|
1634
|
+
ready?: string | undefined;
|
|
1635
|
+
working?: string | undefined;
|
|
1636
|
+
review?: string | undefined;
|
|
1637
|
+
done?: string | undefined;
|
|
1638
|
+
blocked?: string | undefined;
|
|
1639
|
+
};
|
|
1640
|
+
};
|
|
1641
|
+
github: {
|
|
1642
|
+
project_number: number;
|
|
1643
|
+
status_map: {
|
|
1644
|
+
"spec-ready"?: string | undefined;
|
|
1645
|
+
ready?: string | undefined;
|
|
1646
|
+
working?: string | undefined;
|
|
1647
|
+
review?: string | undefined;
|
|
1648
|
+
done?: string | undefined;
|
|
1649
|
+
blocked?: string | undefined;
|
|
1650
|
+
};
|
|
1423
1651
|
};
|
|
1424
1652
|
refine: {
|
|
1425
1653
|
enabled: boolean;
|
|
@@ -1443,6 +1671,7 @@ export declare const SFConfigSchema: v.ObjectSchema<{
|
|
|
1443
1671
|
readonly events: v.OptionalSchema<v.NullableSchema<v.PicklistSchema<["off", "errors", "attention", "all"], undefined>, undefined>, undefined>;
|
|
1444
1672
|
readonly name: v.OptionalSchema<v.StringSchema<undefined>, "">;
|
|
1445
1673
|
}, undefined>, undefined>, () => never[]>;
|
|
1674
|
+
readonly project: v.OptionalSchema<v.StringSchema<undefined>, "">;
|
|
1446
1675
|
}, undefined>, () => {
|
|
1447
1676
|
events: "all" | "attention" | "errors" | "off";
|
|
1448
1677
|
timeout_ms: number;
|
|
@@ -1452,6 +1681,7 @@ export declare const SFConfigSchema: v.ObjectSchema<{
|
|
|
1452
1681
|
events?: "all" | "attention" | "errors" | "off" | null | undefined;
|
|
1453
1682
|
name: string;
|
|
1454
1683
|
}[];
|
|
1684
|
+
project: string;
|
|
1455
1685
|
}>;
|
|
1456
1686
|
readonly review: v.OptionalSchema<v.ObjectSchema<{
|
|
1457
1687
|
readonly require_human_signoff: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|