@gr8ful/spf 0.14.0 → 0.16.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 (77) hide show
  1. package/README.md +51 -9
  2. package/assets/skill/references/config.md +12 -5
  3. package/assets/skill/references/observability.md +57 -12
  4. package/assets/templates/ts-opencode.spf.config.yaml +54 -0
  5. package/dist/chains/index.js +1 -1
  6. package/dist/chains/simple_sdlc.d.ts +2 -2
  7. package/dist/chains/simple_sdlc.js +13 -13
  8. package/dist/chains/steps.d.ts +2 -2
  9. package/dist/chains/steps.js +35 -19
  10. package/dist/cli/commands/abort.d.ts +1 -1
  11. package/dist/cli/commands/abort.js +30 -3
  12. package/dist/cli/commands/doctor.js +121 -8
  13. package/dist/cli/commands/estimate.js +3 -3
  14. package/dist/cli/commands/events.js +4 -4
  15. package/dist/cli/commands/fanout.js +93 -21
  16. package/dist/cli/commands/loop.js +31 -32
  17. package/dist/cli/commands/migrate.js +8 -1
  18. package/dist/cli/commands/phases.js +2 -2
  19. package/dist/cli/commands/sessions.js +2 -2
  20. package/dist/cli/commands/trace.d.ts +28 -8
  21. package/dist/cli/commands/trace.js +28 -15
  22. package/dist/cli/commands/ui.js +15 -5
  23. package/dist/cli/commands/watch.js +91 -30
  24. package/dist/cli/index.js +3 -1
  25. package/dist/cli/interview.d.ts +1 -0
  26. package/dist/cli/interview.js +95 -5
  27. package/dist/core/agent_opencode.d.ts +247 -0
  28. package/dist/core/agent_opencode.js +590 -0
  29. package/dist/core/agents.d.ts +12 -12
  30. package/dist/core/agents.js +113 -46
  31. package/dist/core/console.d.ts +12 -12
  32. package/dist/core/console.js +25 -25
  33. package/dist/core/data_types.d.ts +356 -15
  34. package/dist/core/data_types.js +180 -7
  35. package/dist/core/fanout.d.ts +1 -1
  36. package/dist/core/fanout.js +1 -1
  37. package/dist/core/gates.js +14 -1
  38. package/dist/core/issues/github_provider.d.ts +66 -2
  39. package/dist/core/issues/github_provider.js +161 -2
  40. package/dist/core/issues/jira_provider.d.ts +50 -9
  41. package/dist/core/issues/jira_provider.js +62 -2
  42. package/dist/core/paths.d.ts +41 -4
  43. package/dist/core/paths.js +32 -3
  44. package/dist/core/quality.d.ts +7 -7
  45. package/dist/core/quality.js +16 -10
  46. package/dist/core/refine.js +2 -2
  47. package/dist/core/runner.d.ts +9 -3
  48. package/dist/core/runner.js +39 -27
  49. package/dist/core/session.d.ts +2 -2
  50. package/dist/core/session.js +39 -18
  51. package/dist/core/sqlite.d.ts +14 -7
  52. package/dist/core/sqlite.js +14 -7
  53. package/dist/core/trace_db.d.ts +118 -0
  54. package/dist/core/trace_db.js +278 -0
  55. package/dist/core/tracer.d.ts +64 -34
  56. package/dist/core/tracer.js +141 -69
  57. package/dist/core/watch.d.ts +4 -4
  58. package/dist/core/watch.js +2 -2
  59. package/dist/ui/server/app.js +10 -10
  60. package/dist/ui/server/db.d.ts +89 -21
  61. package/dist/ui/server/db.js +235 -99
  62. package/dist/ui/server/serve.d.ts +5 -1
  63. package/dist/ui/server/serve.js +4 -5
  64. package/package.json +1 -1
  65. package/web/assets/index-CQ3k1Y1-.css +1 -0
  66. package/web/assets/index-CU8tom6S.js +21 -0
  67. package/web/assets/overpass-latin-400-normal-BpeLJ0bs.woff2 +0 -0
  68. package/web/assets/overpass-latin-600-normal-25RhTNCi.woff2 +0 -0
  69. package/web/assets/overpass-latin-700-normal-CQX2QTgM.woff2 +0 -0
  70. package/web/assets/overpass-mono-latin-400-normal-VINZG6Js.woff2 +0 -0
  71. package/web/assets/overpass-mono-latin-700-normal-D6nRBrbd.woff2 +0 -0
  72. package/web/index.html +33 -2
  73. package/web/logo.svg +4 -4
  74. package/web/assets/index-C7nF068F.css +0 -1
  75. package/web/assets/index-mzSArcnQ.js +0 -11
  76. package/web/assets/play-latin-400-normal-GKW-4YV7.woff2 +0 -0
  77. package/web/assets/play-latin-700-normal-DyPlLDbb.woff2 +0 -0
@@ -671,7 +671,7 @@ export interface SandboxSpec {
671
671
  }
672
672
  export declare const AgentConfigSchema: v.ObjectSchema<{
673
673
  readonly name: v.StringSchema<undefined>;
674
- readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code"], undefined>, "flue">;
674
+ readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code", "opencode"], undefined>, "flue">;
675
675
  readonly model: v.OptionalSchema<v.StringSchema<undefined>, "google/gemini-3.6-flash">;
676
676
  readonly thinking: v.OptionalSchema<v.PicklistSchema<["off", "minimal", "low", "medium", "high", "xhigh", "max"], undefined>, "medium">;
677
677
  readonly color: v.OptionalSchema<v.StringSchema<undefined>, "">;
@@ -688,7 +688,7 @@ export declare const AgentConfigSchema: v.ObjectSchema<{
688
688
  }, undefined>;
689
689
  export type AgentConfig = v.InferOutput<typeof AgentConfigSchema>;
690
690
  export declare const ConfigDefaultsSchema: v.ObjectSchema<{
691
- readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code"], undefined>, "flue">;
691
+ readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code", "opencode"], undefined>, "flue">;
692
692
  readonly model: v.OptionalSchema<v.StringSchema<undefined>, "google/gemini-3.6-flash">;
693
693
  readonly thinking: v.OptionalSchema<v.PicklistSchema<["off", "minimal", "low", "medium", "high", "xhigh", "max"], undefined>, "medium">;
694
694
  readonly color: v.OptionalSchema<v.StringSchema<undefined>, "">;
@@ -763,8 +763,106 @@ export declare const OTelConfigSchema: v.ObjectSchema<{
763
763
  readonly service_name: v.OptionalSchema<v.StringSchema<undefined>, "spf">;
764
764
  }, undefined>;
765
765
  export type OTelConfig = v.InferOutput<typeof OTelConfigSchema>;
766
+ /**
767
+ * MIGRATION NOTE (BT-issue #66, 3 PRs): `observability.db` used to be ONLY a
768
+ * bare string (a local sqlite path, defaulting to ".spf/data/spf.db"). This
769
+ * is now a discriminated shape that additionally accepts an explicit
770
+ * `{ kind: "sqlite", path? }` object (equivalent to the string form, just
771
+ * spelled out) and a `{ kind: "d1", database_id, ... }` object that names a
772
+ * remote Cloudflare D1 database instead of a local file. The bare-string
773
+ * form is unchanged and remains the default for every existing config —
774
+ * nothing in the wild needs to change. `resolveObservabilityDb` below
775
+ * normalizes all three accepted forms to one shape.
776
+ *
777
+ * PR 1 (this schema + normalizer + `paths.resolveDataPaths` plumbing) left
778
+ * `resolveDataPaths` refusing a `kind: "d1"` config outright — no adapter
779
+ * existed yet. PR 2 (`core/trace_db.ts`'s `TraceDb` interface, `LocalTraceDb`,
780
+ * `D1TraceDb`, and `createTraceDb()`, wired through `Tracer`/`SfDb`) removes
781
+ * that refusal: `resolveDataPaths` now returns a usable descriptor for
782
+ * either kind, and both backends are real. PR 3 wires `spf doctor`/`spf
783
+ * init`'s interview flow to the new option.
784
+ */
785
+ export declare const SqliteDbConfigSchema: v.ObjectSchema<{
786
+ readonly kind: v.LiteralSchema<"sqlite", undefined>;
787
+ readonly path: v.OptionalSchema<v.StringSchema<undefined>, ".spf/data/spf.db">;
788
+ }, undefined>;
789
+ export type SqliteDbConfig = v.InferOutput<typeof SqliteDbConfigSchema>;
790
+ /**
791
+ * `account_id_env`/`api_token_env` default to the SAME env var names
792
+ * `core/cloudflare_provider.ts` already reads for the Cloudflare Workers AI
793
+ * provider (`CLOUDFLARE_ACCOUNT_ID`/`CLOUDFLARE_API_TOKEN`, see
794
+ * `core/providers.ts`'s `PROVIDER_ENV_KEYS.cloudflare`) — one Cloudflare
795
+ * account's credentials, read from the same two env vars, cover both
796
+ * Workers AI and a D1-backed trace db unless a config explicitly points
797
+ * elsewhere (e.g. a second Cloudflare account) by naming different env vars.
798
+ */
799
+ export declare const D1DbConfigSchema: v.ObjectSchema<{
800
+ readonly kind: v.LiteralSchema<"d1", undefined>;
801
+ readonly database_id: v.StringSchema<undefined>;
802
+ readonly account_id_env: v.OptionalSchema<v.StringSchema<undefined>, "CLOUDFLARE_ACCOUNT_ID">;
803
+ readonly api_token_env: v.OptionalSchema<v.StringSchema<undefined>, "CLOUDFLARE_API_TOKEN">;
804
+ }, undefined>;
805
+ export type D1DbConfig = v.InferOutput<typeof D1DbConfigSchema>;
806
+ export declare const ObservabilityDbSchema: v.SchemaWithPipe<readonly [v.UnknownSchema, v.RawTransformAction<unknown, string | {
807
+ kind: "sqlite";
808
+ path: string;
809
+ } | {
810
+ kind: "d1";
811
+ database_id: string;
812
+ account_id_env: string;
813
+ api_token_env: string;
814
+ }>]>;
815
+ export type ObservabilityDbConfig = v.InferOutput<typeof ObservabilityDbSchema>;
816
+ /**
817
+ * What `resolveObservabilityDb` ACCEPTS — deliberately more permissive than
818
+ * `ObservabilityDbConfig` (that type is the schema's OUTPUT, post-default,
819
+ * so every optional field reads as required). This is the pre-default INPUT
820
+ * shape — `path`/`account_id_env`/`api_token_env` all optional, exactly
821
+ * what a hand-written `{kind:"sqlite"}` or `{kind:"d1", database_id:"..."}`
822
+ * literal looks like before `v.parse` fills its defaults in. An already-
823
+ * parsed `ObservabilityDbConfig` (every optional field present) satisfies
824
+ * this too, so the one normalizer works on config fresh off `v.parse` AND
825
+ * on a literal built by hand (e.g. in a test, or by a future caller that
826
+ * hasn't gone through the schema).
827
+ */
828
+ export type ObservabilityDbInput = string | {
829
+ kind: "sqlite";
830
+ path?: string;
831
+ } | {
832
+ kind: "d1";
833
+ database_id: string;
834
+ account_id_env?: string;
835
+ api_token_env?: string;
836
+ };
837
+ /** What `resolveObservabilityDb` normalizes any of the three accepted `db:` forms to. PR 2's D1 adapter is built against this shape. */
838
+ export type NormalizedObservabilityDb = {
839
+ kind: "sqlite";
840
+ path: string;
841
+ } | {
842
+ kind: "d1";
843
+ database_id: string;
844
+ account_id_env: string;
845
+ api_token_env: string;
846
+ };
847
+ /**
848
+ * Normalizes `observability.db` — bare string, `{kind:"sqlite",...}`, or
849
+ * `{kind:"d1",...}` — to one shape. Pure and side-effect-free: does NOT
850
+ * resolve `path` to an absolute path (that's `paths.resolveDataPaths`'s job,
851
+ * which needs `repo_root` to do it) and does NOT read the D1 env vars it
852
+ * names (that's PR 2's adapter's job, at the point it actually needs to
853
+ * authenticate).
854
+ */
855
+ export declare function resolveObservabilityDb(db: ObservabilityDbInput): NormalizedObservabilityDb;
766
856
  export declare const ObservabilityConfigSchema: v.ObjectSchema<{
767
- readonly db: v.OptionalSchema<v.StringSchema<undefined>, ".spf/data/spf.db">;
857
+ readonly db: v.OptionalSchema<v.SchemaWithPipe<readonly [v.UnknownSchema, v.RawTransformAction<unknown, string | {
858
+ kind: "sqlite";
859
+ path: string;
860
+ } | {
861
+ kind: "d1";
862
+ database_id: string;
863
+ account_id_env: string;
864
+ api_token_env: string;
865
+ }>]>, ".spf/data/spf.db">;
768
866
  readonly poll_ms: v.OptionalSchema<v.NumberSchema<undefined>, 500>;
769
867
  readonly otel: v.OptionalSchema<v.ObjectSchema<{
770
868
  readonly endpoint: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.UrlAction<string, undefined>]>;
@@ -839,14 +937,43 @@ export declare const JiraIssueTypeMapSchema: v.ObjectSchema<{
839
937
  readonly spec: v.OptionalSchema<v.StringSchema<undefined>, "Story">;
840
938
  }, undefined>;
841
939
  export type JiraIssueTypeMap = v.InferOutput<typeof JiraIssueTypeMapSchema>;
940
+ /**
941
+ * Optional, per-project `WatchState` -> Jira workflow-status-name map.
942
+ * Unlike `JiraIssueTypeMapSchema`, this has no sane universal default:
943
+ * "To Do" vs "Open" vs "Backlog" (and everything in between) is entirely
944
+ * per-project workflow configuration in Jira, so every field defaults to
945
+ * unset. A `WatchState` with no entry here keeps today's behavior exactly —
946
+ * `jira_provider.ts`'s `transition()`/`claim()` update the label only and
947
+ * never attempt a status change for it.
948
+ *
949
+ * Deliberately covers only the states a human's Jira board status would
950
+ * plausibly want to reflect (the build lane, `spec-ready` through
951
+ * `blocked`) — the refine-lane states (`refining`, `split-proposed`, ...)
952
+ * are internal bookkeeping for `core/refine.ts`, not board-visible work.
953
+ *
954
+ * `jira_provider.ts`'s `syncStatus()` is the reader; `validateStatusMap()`
955
+ * is what `spf watch init` and `spf watch`'s own startup check should call
956
+ * to confirm each configured name is a real status on the project before an
957
+ * unattended run relies on it — same shape as `issue_types`/
958
+ * `validateIssueTypes()`.
959
+ */
960
+ export declare const JiraStatusMapSchema: v.ObjectSchema<{
961
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
962
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
963
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
964
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
965
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
966
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
967
+ }, undefined>;
968
+ export type JiraStatusMap = v.InferOutput<typeof JiraStatusMapSchema>;
842
969
  /**
843
970
  * Only consulted when `issue_provider: jira`. Auth is `JIRA_EMAIL` +
844
971
  * `JIRA_API_TOKEN` env vars, checked at startup like `GITHUB_TOKEN`. Whole-
845
972
  * object replace on config-file-layer merge, like `refine`/
846
973
  * `observability.otel` (see `agents.ts`'s `mergeRawConfig`) — an override
847
- * file that touches `watch.jira` at all must repeat `issue_types` too if it
848
- * wants to keep a customized mapping, same caveat that already applies to
849
- * `base_url`/`project_key` today.
974
+ * file that touches `watch.jira` at all must repeat `issue_types`/
975
+ * `status_map` too if it wants to keep a customized mapping, same caveat
976
+ * that already applies to `base_url`/`project_key` today.
850
977
  */
851
978
  export declare const WatchJiraConfigSchema: v.ObjectSchema<{
852
979
  readonly base_url: v.OptionalSchema<v.StringSchema<undefined>, "">;
@@ -873,8 +1000,85 @@ export declare const WatchJiraConfigSchema: v.ObjectSchema<{
873
1000
  task: string;
874
1001
  spec: string;
875
1002
  }>;
1003
+ readonly status_map: v.OptionalSchema<v.ObjectSchema<{
1004
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1005
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1006
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1007
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1008
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1009
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1010
+ }, undefined>, () => {
1011
+ "spec-ready"?: string | undefined;
1012
+ ready?: string | undefined;
1013
+ working?: string | undefined;
1014
+ review?: string | undefined;
1015
+ done?: string | undefined;
1016
+ blocked?: string | undefined;
1017
+ }>;
876
1018
  }, undefined>;
877
1019
  export type WatchJiraConfig = v.InferOutput<typeof WatchJiraConfigSchema>;
1020
+ /**
1021
+ * Optional, per-repo `WatchState` -> GitHub Projects v2 "Status" option-name
1022
+ * map — the GitHub-side twin of `JiraIssueTypeMap`'s sibling on the Jira
1023
+ * provider (see `jira_provider.ts`'s `status_map`). No sane universal
1024
+ * default: a board's Status column options ("Todo"/"In Progress"/"Done", or
1025
+ * anything else) are per-project configuration, so every field defaults to
1026
+ * unset. A `WatchState` with no entry keeps today's behavior exactly —
1027
+ * `github_provider.ts`'s `transition()`/`claim()` update the label only and
1028
+ * never touch Projects v2 for it.
1029
+ *
1030
+ * Same six build-lane states as Jira's map — the refine-lane's own
1031
+ * bookkeeping states (`refining`, `split-proposed`, ...) aren't board-visible
1032
+ * work, same reasoning as `jira_provider.ts`'s `JiraStatusMapSchema`.
1033
+ *
1034
+ * `github_provider.ts`'s `syncStatus()` is the reader; `validateStatusMap()`
1035
+ * is what `spf watch init` and `spf watch`'s own startup check call to
1036
+ * confirm each configured name is a real Status option on the configured
1037
+ * project before an unattended run relies on it.
1038
+ */
1039
+ export declare const GithubStatusMapSchema: v.ObjectSchema<{
1040
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1041
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1042
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1043
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1044
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1045
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1046
+ }, undefined>;
1047
+ export type GithubStatusMap = v.InferOutput<typeof GithubStatusMapSchema>;
1048
+ /**
1049
+ * Only consulted when `issue_provider: github` (or `code_host: github`) AND
1050
+ * `status_map` actually has an entry configured — `project_number: 0` (the
1051
+ * default) disables status sync outright regardless of `status_map`, since
1052
+ * there's no sane "guess the board" default: a repo can have zero, one, or
1053
+ * many Projects v2 boards, and none of them is canonical. Scoped to the
1054
+ * repo's OWNER (`watch.repo`'s "owner/name", the owner half) — GitHub
1055
+ * Projects v2 numbers are per-owner, not per-repo, so `project_number: 3`
1056
+ * means owner's project #3, which may or may not have this repo's issues on
1057
+ * it yet (`github_provider.ts`'s `syncStatus()` adds the issue to the
1058
+ * project itself the first time it needs to). Whole-object replace on
1059
+ * config-file-layer merge, like `jira`/`refine` above — an override file
1060
+ * that touches `watch.github` at all must repeat `status_map` too if it
1061
+ * wants to keep a customized mapping.
1062
+ */
1063
+ export declare const WatchGithubConfigSchema: v.ObjectSchema<{
1064
+ readonly project_number: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, 0>;
1065
+ readonly status_map: v.OptionalSchema<v.ObjectSchema<{
1066
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1067
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1068
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1069
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1070
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1071
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1072
+ }, undefined>, () => {
1073
+ "spec-ready"?: string | undefined;
1074
+ ready?: string | undefined;
1075
+ working?: string | undefined;
1076
+ review?: string | undefined;
1077
+ done?: string | undefined;
1078
+ blocked?: string | undefined;
1079
+ }>;
1080
+ }, undefined>;
1081
+ export type WatchGithubConfig = v.InferOutput<typeof WatchGithubConfigSchema>;
878
1082
  /**
879
1083
  * The second `spf watch` lane: decompose a `<prefix>:spec-ready` product
880
1084
  * spec into a feature/story tree of real issues, instead of running
@@ -1024,6 +1228,21 @@ export declare const WatchConfigSchema: v.ObjectSchema<{
1024
1228
  task: string;
1025
1229
  spec: string;
1026
1230
  }>;
1231
+ readonly status_map: v.OptionalSchema<v.ObjectSchema<{
1232
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1233
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1234
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1235
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1236
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1237
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1238
+ }, undefined>, () => {
1239
+ "spec-ready"?: string | undefined;
1240
+ ready?: string | undefined;
1241
+ working?: string | undefined;
1242
+ review?: string | undefined;
1243
+ done?: string | undefined;
1244
+ blocked?: string | undefined;
1245
+ }>;
1027
1246
  }, undefined>, () => {
1028
1247
  base_url: string;
1029
1248
  project_key: string;
@@ -1035,6 +1254,42 @@ export declare const WatchConfigSchema: v.ObjectSchema<{
1035
1254
  task: string;
1036
1255
  spec: string;
1037
1256
  };
1257
+ status_map: {
1258
+ "spec-ready"?: string | undefined;
1259
+ ready?: string | undefined;
1260
+ working?: string | undefined;
1261
+ review?: string | undefined;
1262
+ done?: string | undefined;
1263
+ blocked?: string | undefined;
1264
+ };
1265
+ }>;
1266
+ readonly github: v.OptionalSchema<v.ObjectSchema<{
1267
+ readonly project_number: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, 0>;
1268
+ readonly status_map: v.OptionalSchema<v.ObjectSchema<{
1269
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1270
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1271
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1272
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1273
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1274
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1275
+ }, undefined>, () => {
1276
+ "spec-ready"?: string | undefined;
1277
+ ready?: string | undefined;
1278
+ working?: string | undefined;
1279
+ review?: string | undefined;
1280
+ done?: string | undefined;
1281
+ blocked?: string | undefined;
1282
+ }>;
1283
+ }, undefined>, () => {
1284
+ project_number: number;
1285
+ status_map: {
1286
+ "spec-ready"?: string | undefined;
1287
+ ready?: string | undefined;
1288
+ working?: string | undefined;
1289
+ review?: string | undefined;
1290
+ done?: string | undefined;
1291
+ blocked?: string | undefined;
1292
+ };
1038
1293
  }>;
1039
1294
  readonly refine: v.OptionalSchema<v.ObjectSchema<{
1040
1295
  readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
@@ -1147,7 +1402,7 @@ export type ReviewConfig = v.InferOutput<typeof ReviewConfigSchema>;
1147
1402
  */
1148
1403
  export declare const TierSchema: v.ObjectSchema<{
1149
1404
  readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
1150
- readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code"], undefined>, "flue">;
1405
+ readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code", "opencode"], undefined>, "flue">;
1151
1406
  readonly model: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
1152
1407
  }, undefined>;
1153
1408
  export type Tier = v.InferOutput<typeof TierSchema>;
@@ -1182,7 +1437,7 @@ export declare const TieringConfigSchema: v.ObjectSchema<{
1182
1437
  readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
1183
1438
  readonly tiers: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
1184
1439
  readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
1185
- readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code"], undefined>, "flue">;
1440
+ readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code", "opencode"], undefined>, "flue">;
1186
1441
  readonly model: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
1187
1442
  }, undefined>, undefined>, () => never[]>;
1188
1443
  readonly roles: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.StringSchema<undefined>, undefined>, () => {}>;
@@ -1191,7 +1446,7 @@ export type TieringConfig = v.InferOutput<typeof TieringConfigSchema>;
1191
1446
  export declare const SFConfigSchema: v.ObjectSchema<{
1192
1447
  readonly env: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.StringSchema<undefined>, undefined>, () => {}>;
1193
1448
  readonly defaults: v.OptionalSchema<v.ObjectSchema<{
1194
- readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code"], undefined>, "flue">;
1449
+ readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code", "opencode"], undefined>, "flue">;
1195
1450
  readonly model: v.OptionalSchema<v.StringSchema<undefined>, "google/gemini-3.6-flash">;
1196
1451
  readonly thinking: v.OptionalSchema<v.PicklistSchema<["off", "minimal", "low", "medium", "high", "xhigh", "max"], undefined>, "medium">;
1197
1452
  readonly color: v.OptionalSchema<v.StringSchema<undefined>, "">;
@@ -1244,7 +1499,7 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1244
1499
  readonly max_run_cost: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.GtValueAction<number, 0, undefined>]>, undefined>;
1245
1500
  readonly max_run_tokens: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.GtValueAction<number, 0, undefined>]>, undefined>;
1246
1501
  }, undefined>, () => {
1247
- coding_agent: "claude_code" | "flue";
1502
+ coding_agent: "claude_code" | "flue" | "opencode";
1248
1503
  model: string;
1249
1504
  thinking: "high" | "low" | "max" | "medium" | "minimal" | "off" | "xhigh";
1250
1505
  color: string;
@@ -1256,7 +1511,15 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1256
1511
  max_run_tokens?: number | undefined;
1257
1512
  }>;
1258
1513
  readonly observability: v.OptionalSchema<v.ObjectSchema<{
1259
- readonly db: v.OptionalSchema<v.StringSchema<undefined>, ".spf/data/spf.db">;
1514
+ readonly db: v.OptionalSchema<v.SchemaWithPipe<readonly [v.UnknownSchema, v.RawTransformAction<unknown, string | {
1515
+ kind: "sqlite";
1516
+ path: string;
1517
+ } | {
1518
+ kind: "d1";
1519
+ database_id: string;
1520
+ account_id_env: string;
1521
+ api_token_env: string;
1522
+ }>]>, ".spf/data/spf.db">;
1260
1523
  readonly poll_ms: v.OptionalSchema<v.NumberSchema<undefined>, 500>;
1261
1524
  readonly otel: v.OptionalSchema<v.ObjectSchema<{
1262
1525
  readonly endpoint: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.UrlAction<string, undefined>]>;
@@ -1264,7 +1527,15 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1264
1527
  readonly service_name: v.OptionalSchema<v.StringSchema<undefined>, "spf">;
1265
1528
  }, undefined>, undefined>;
1266
1529
  }, undefined>, () => {
1267
- db: string;
1530
+ db: string | {
1531
+ kind: "sqlite";
1532
+ path: string;
1533
+ } | {
1534
+ kind: "d1";
1535
+ database_id: string;
1536
+ account_id_env: string;
1537
+ api_token_env: string;
1538
+ };
1268
1539
  poll_ms: number;
1269
1540
  otel?: {
1270
1541
  endpoint: string;
@@ -1276,7 +1547,7 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1276
1547
  }>;
1277
1548
  readonly agents: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
1278
1549
  readonly name: v.StringSchema<undefined>;
1279
- readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code"], undefined>, "flue">;
1550
+ readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code", "opencode"], undefined>, "flue">;
1280
1551
  readonly model: v.OptionalSchema<v.StringSchema<undefined>, "google/gemini-3.6-flash">;
1281
1552
  readonly thinking: v.OptionalSchema<v.PicklistSchema<["off", "minimal", "low", "medium", "high", "xhigh", "max"], undefined>, "medium">;
1282
1553
  readonly color: v.OptionalSchema<v.StringSchema<undefined>, "">;
@@ -1363,6 +1634,21 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1363
1634
  task: string;
1364
1635
  spec: string;
1365
1636
  }>;
1637
+ readonly status_map: v.OptionalSchema<v.ObjectSchema<{
1638
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1639
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1640
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1641
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1642
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1643
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1644
+ }, undefined>, () => {
1645
+ "spec-ready"?: string | undefined;
1646
+ ready?: string | undefined;
1647
+ working?: string | undefined;
1648
+ review?: string | undefined;
1649
+ done?: string | undefined;
1650
+ blocked?: string | undefined;
1651
+ }>;
1366
1652
  }, undefined>, () => {
1367
1653
  base_url: string;
1368
1654
  project_key: string;
@@ -1374,6 +1660,42 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1374
1660
  task: string;
1375
1661
  spec: string;
1376
1662
  };
1663
+ status_map: {
1664
+ "spec-ready"?: string | undefined;
1665
+ ready?: string | undefined;
1666
+ working?: string | undefined;
1667
+ review?: string | undefined;
1668
+ done?: string | undefined;
1669
+ blocked?: string | undefined;
1670
+ };
1671
+ }>;
1672
+ readonly github: v.OptionalSchema<v.ObjectSchema<{
1673
+ readonly project_number: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, 0>;
1674
+ readonly status_map: v.OptionalSchema<v.ObjectSchema<{
1675
+ readonly "spec-ready": v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1676
+ readonly ready: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1677
+ readonly working: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1678
+ readonly review: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1679
+ readonly done: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1680
+ readonly blocked: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1681
+ }, undefined>, () => {
1682
+ "spec-ready"?: string | undefined;
1683
+ ready?: string | undefined;
1684
+ working?: string | undefined;
1685
+ review?: string | undefined;
1686
+ done?: string | undefined;
1687
+ blocked?: string | undefined;
1688
+ }>;
1689
+ }, undefined>, () => {
1690
+ project_number: number;
1691
+ status_map: {
1692
+ "spec-ready"?: string | undefined;
1693
+ ready?: string | undefined;
1694
+ working?: string | undefined;
1695
+ review?: string | undefined;
1696
+ done?: string | undefined;
1697
+ blocked?: string | undefined;
1698
+ };
1377
1699
  }>;
1378
1700
  readonly refine: v.OptionalSchema<v.ObjectSchema<{
1379
1701
  readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
@@ -1421,6 +1743,25 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1421
1743
  task: string;
1422
1744
  spec: string;
1423
1745
  };
1746
+ status_map: {
1747
+ "spec-ready"?: string | undefined;
1748
+ ready?: string | undefined;
1749
+ working?: string | undefined;
1750
+ review?: string | undefined;
1751
+ done?: string | undefined;
1752
+ blocked?: string | undefined;
1753
+ };
1754
+ };
1755
+ github: {
1756
+ project_number: number;
1757
+ status_map: {
1758
+ "spec-ready"?: string | undefined;
1759
+ ready?: string | undefined;
1760
+ working?: string | undefined;
1761
+ review?: string | undefined;
1762
+ done?: string | undefined;
1763
+ blocked?: string | undefined;
1764
+ };
1424
1765
  };
1425
1766
  refine: {
1426
1767
  enabled: boolean;
@@ -1467,7 +1808,7 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1467
1808
  readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
1468
1809
  readonly tiers: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
1469
1810
  readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
1470
- readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code"], undefined>, "flue">;
1811
+ readonly coding_agent: v.OptionalSchema<v.PicklistSchema<["flue", "claude_code", "opencode"], undefined>, "flue">;
1471
1812
  readonly model: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
1472
1813
  }, undefined>, undefined>, () => never[]>;
1473
1814
  readonly roles: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.StringSchema<undefined>, undefined>, () => {}>;
@@ -1475,7 +1816,7 @@ export declare const SFConfigSchema: v.ObjectSchema<{
1475
1816
  enabled: boolean;
1476
1817
  tiers: {
1477
1818
  name: string;
1478
- coding_agent: "claude_code" | "flue";
1819
+ coding_agent: "claude_code" | "flue" | "opencode";
1479
1820
  model: string;
1480
1821
  }[];
1481
1822
  roles: {