@gr8ful/spf 0.3.0 → 0.5.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 (91) hide show
  1. package/README.md +177 -9
  2. package/assets/defaults/spf.config.yaml +22 -0
  3. package/assets/prompts/refiner/system.md +53 -0
  4. package/assets/prompts/refiner/user.md +70 -0
  5. package/assets/prompts/reviewer/system.md +1 -1
  6. package/assets/skill/SKILL.md +1 -0
  7. package/assets/skill/cookbooks/authoring_chains.md +90 -7
  8. package/assets/skill/cookbooks/ocr_reviewer.md +196 -0
  9. package/assets/skill/cookbooks/roster.md +15 -4
  10. package/assets/skill/cookbooks/spf_overview.md +1 -0
  11. package/assets/skill/references/config.md +104 -3
  12. package/assets/skill/references/observability.md +11 -2
  13. package/assets/templates/ts-cc.spf.config.yaml +3 -3
  14. package/assets/templates/ts-flue-ollama.spf.config.yaml +67 -0
  15. package/assets/templates/ts.spf.config.yaml +15 -2
  16. package/dist/chains/context.d.ts +39 -0
  17. package/dist/chains/index.d.ts +94 -10
  18. package/dist/chains/index.js +75 -5
  19. package/dist/chains/repo_chains.d.ts +139 -0
  20. package/dist/chains/repo_chains.js +428 -0
  21. package/dist/chains/simple_sdlc.d.ts +74 -1
  22. package/dist/chains/simple_sdlc.js +134 -4
  23. package/dist/chains/steps.d.ts +237 -18
  24. package/dist/chains/steps.js +477 -58
  25. package/dist/cli/ask.d.ts +14 -1
  26. package/dist/cli/ask.js +32 -2
  27. package/dist/cli/commands/doctor.d.ts +1 -1
  28. package/dist/cli/commands/doctor.js +324 -10
  29. package/dist/cli/commands/init.d.ts +12 -0
  30. package/dist/cli/commands/init.js +108 -4
  31. package/dist/cli/commands/install-skill.js +5 -2
  32. package/dist/cli/commands/list.js +43 -5
  33. package/dist/cli/commands/run.js +29 -2
  34. package/dist/cli/commands/watch.d.ts +18 -0
  35. package/dist/cli/commands/watch.js +214 -16
  36. package/dist/cli/index.js +63 -6
  37. package/dist/cli/interview.js +81 -9
  38. package/dist/core/agent_cc.d.ts +40 -1
  39. package/dist/core/agent_cc.js +51 -4
  40. package/dist/core/agent_flue.js +28 -4
  41. package/dist/core/agents.d.ts +8 -0
  42. package/dist/core/agents.js +43 -3
  43. package/dist/core/data_types.d.ts +182 -4
  44. package/dist/core/data_types.js +141 -2
  45. package/dist/core/gates.d.ts +13 -0
  46. package/dist/core/gates.js +103 -0
  47. package/dist/core/git_helper.d.ts +29 -0
  48. package/dist/core/git_helper.js +41 -1
  49. package/dist/core/issues/github_provider.d.ts +35 -9
  50. package/dist/core/issues/github_provider.js +76 -28
  51. package/dist/core/issues/jira_provider.d.ts +14 -1
  52. package/dist/core/issues/jira_provider.js +9 -7
  53. package/dist/core/issues/provider.d.ts +77 -15
  54. package/dist/core/issues/provider.js +7 -4
  55. package/dist/core/notify/channel.d.ts +1 -1
  56. package/dist/core/ollama_provider.d.ts +70 -0
  57. package/dist/core/ollama_provider.js +208 -0
  58. package/dist/core/otel.d.ts +352 -0
  59. package/dist/core/otel.js +793 -0
  60. package/dist/core/providers.js +4 -0
  61. package/dist/core/refine.d.ts +39 -0
  62. package/dist/core/refine.js +152 -0
  63. package/dist/core/session.js +39 -2
  64. package/dist/core/tracer.d.ts +31 -2
  65. package/dist/core/tracer.js +69 -11
  66. package/dist/core/watch.d.ts +67 -1
  67. package/dist/core/watch.js +217 -13
  68. package/dist/test/chains.test.js +9 -3
  69. package/dist/test/data_types.test.js +140 -2
  70. package/dist/test/git_helper.test.d.ts +1 -0
  71. package/dist/test/git_helper.test.js +59 -0
  72. package/dist/test/hermetic_git.d.ts +1 -0
  73. package/dist/test/hermetic_git.js +22 -0
  74. package/dist/test/init_command.test.d.ts +14 -1
  75. package/dist/test/init_command.test.js +71 -1
  76. package/dist/test/interview.test.d.ts +15 -1
  77. package/dist/test/interview.test.js +131 -3
  78. package/dist/test/ollama_provider.test.d.ts +1 -0
  79. package/dist/test/ollama_provider.test.js +103 -0
  80. package/dist/test/otel.test.d.ts +26 -0
  81. package/dist/test/otel.test.js +512 -0
  82. package/dist/test/refine.test.d.ts +1 -0
  83. package/dist/test/refine.test.js +189 -0
  84. package/dist/test/repo_chains.test.d.ts +21 -0
  85. package/dist/test/repo_chains.test.js +416 -0
  86. package/dist/test/signoff.test.d.ts +1 -0
  87. package/dist/test/signoff.test.js +329 -0
  88. package/dist/test/ui_server.test.d.ts +7 -1
  89. package/dist/test/ui_server.test.js +1 -0
  90. package/dist/test/watch.test.js +297 -6
  91. package/package.json +5 -5
@@ -1,14 +1,34 @@
1
1
  /**
2
- * The chain registry — the one place a CLI-facing short name maps to a
3
- * chain module. `spf list` reads this; `spf <name>`/`spf run <name>` dispatch
4
- * through it. Replaces guessing a module filename from the CLI argument.
2
+ * The chain registry — the one place a CLI-facing short name resolves to a
3
+ * chain. `spf list` reads it; `spf <name>`/`spf run <name>` dispatch through
4
+ * it. Replaces guessing a module filename from the CLI argument.
5
5
  *
6
- * Every chain but `simple-sdlc` is a flat `steps` list composed from
7
- * `./steps.ts`'s primitives — no module of its own, and no hand-written
8
- * `phases`/`requiredAgents`/`requiredSuites` to keep in sync with what
9
- * actually runs; those are derived from the step list itself.
10
- * `simple-sdlc`'s three-commit, pinned-baseline shape is still imperative
11
- * see `./simple_sdlc.ts` via the `run` escape hatch below.
6
+ * TWO TIERS, and the distinction is load-bearing:
7
+ *
8
+ * 1. BUILT-INS — `CHAINS`, below. Compiled in, always present, the same in
9
+ * every repo spf is ever run against. Every one but `simple-sdlc` is a
10
+ * flat `steps` list composed from `./steps.ts`'s primitives no module
11
+ * of its own, and no hand-written `phases`/`requiredAgents`/
12
+ * `requiredSuites` to keep in sync with what actually runs; those are
13
+ * derived from the step list itself. `simple-sdlc`'s three-commit,
14
+ * pinned-baseline shape is still imperative — see `./simple_sdlc.ts` —
15
+ * via the `run` escape hatch below.
16
+ * 2. REPO-LOCAL CHAINS — registered at CLI startup from
17
+ * `<repo>/.spf/chains/*.yaml` by `registerRepoChains()`, having been
18
+ * read by `./repo_chains.ts`. These are DATA: a yaml file names built-in
19
+ * step factories and passes them params. It cannot introduce code, and
20
+ * it runs on the same `steps.runSteps()` driver as a built-in, because
21
+ * "agent proposes, code disposes" only means anything if the code that
22
+ * disposes is SPF's code — not the target repo's.
23
+ *
24
+ * `CHAINS` stays a built-in-only const: nothing ever pushes into it, and
25
+ * `src/test/chains.test.ts` pins its exact name set. Repo chains live in a
26
+ * separate module-level list that `registerRepoChains()` REPLACES (never
27
+ * appends to), so a second registration — a long-lived process, `spf watch`
28
+ * re-reading after an edit, a test — cannot double-register a name. Read
29
+ * both tiers through `allChains()`; built-ins always come first, and
30
+ * `findChain()` resolves them first, so a built-in name can never be
31
+ * shadowed by a file on disk.
12
32
  */
13
33
  import type { ChainContext } from "./context.ts";
14
34
  import * as steps from "./steps.ts";
@@ -19,15 +39,79 @@ export interface ChainDefinition {
19
39
  phases: string;
20
40
  /** Static for most chains; prompt's depends on --agent, so it's a function there. */
21
41
  requiredAgents: string[] | ((options: Record<string, string>) => string[]);
22
- requiredSuites: string[];
42
+ /** Static unless a step's suite can be overridden by --suite (qualityCheck/fixLoop), in which case it's a function — see steps.deriveRequiredSuites. */
43
+ requiredSuites: string[] | ((options: Record<string, string>) => string[]);
23
44
  /** The declarative path — a flat step list, run by runChain() via steps.runSteps(). */
24
45
  steps?: steps.Step[];
25
46
  /** The imperative escape hatch for a chain too shaped by its own logic to be a flat list (simple-sdlc). */
26
47
  run?: (ctx: ChainContext, options?: Record<string, string>) => Promise<number>;
48
+ /**
49
+ * Absolute path of the `.spf/chains/*.yaml` this chain was loaded from —
50
+ * set only for a repo-local chain, `undefined` for a built-in (a built-in
51
+ * has no source file: it IS spf). The CLI copies it into
52
+ * `ChainContext.chain_source` so the run's trace records whose definition
53
+ * ran, and `spf list`/`spf doctor` use its presence to mark which chains
54
+ * came from the repo.
55
+ */
56
+ source?: string;
27
57
  }
58
+ /**
59
+ * Build a step-based ChainDefinition, deriving phases/requiredAgents/
60
+ * requiredSuites from its steps.
61
+ *
62
+ * Exported so `./repo_chains.ts` builds a yaml-defined chain through the
63
+ * EXACT same function as a built-in. That is deliberate and should not be
64
+ * "simplified" into a second, parallel constructor: a repo chain must be
65
+ * indistinguishable downstream — same derivation of phases/agents/suites,
66
+ * same `steps` array, same `runSteps` driver, one run path — or the
67
+ * guarantees the built-ins are tested for stop applying to it.
68
+ */
69
+ export declare function stepChain(name: string, describe: string, list: steps.Step[]): ChainDefinition;
28
70
  export declare const CHAINS: ChainDefinition[];
71
+ /**
72
+ * Every built-in name, reserved. `repo_chains.ts` checks against this to
73
+ * refuse a repo chain that reuses one — a collision is reported as a
74
+ * problem, never resolved by shadowing either way round.
75
+ */
76
+ export declare const BUILTIN_CHAIN_NAMES: ReadonlySet<string>;
77
+ /**
78
+ * Install the repo-local chains (and the problems found reading them) for
79
+ * this process. Call once, early, from the CLI entry point.
80
+ *
81
+ * REPLACES rather than appends, so it is idempotent: calling it twice with
82
+ * the same input leaves exactly one copy of each chain. That matters because
83
+ * a long-running `spf watch` may re-read `.spf/chains/` after an edit, and
84
+ * because a partial second registration (say, one file fixed) must not leave
85
+ * the previous file's stale definition behind.
86
+ *
87
+ * The `problems` list is stored, not thrown and not printed: loading is
88
+ * never allowed to fail a command that has nothing to do with the broken
89
+ * file (`spf trace`, `spf sessions`, `spf --version` must all still work in
90
+ * a repo with one malformed yaml). `spf list` and `spf doctor` surface them;
91
+ * dispatch surfaces them when the name someone typed is missing.
92
+ */
93
+ export declare function registerRepoChains(chains: ChainDefinition[], problems: {
94
+ file: string;
95
+ message: string;
96
+ }[]): void;
97
+ /** Built-ins first, then repo-local chains — the order `spf list` prints and `findChain` resolves in. */
98
+ export declare function allChains(): ChainDefinition[];
99
+ /** What went wrong reading `.spf/chains/*.yaml`, for `spf list`/`spf doctor` to report. */
100
+ export declare function repoChainProblems(): {
101
+ file: string;
102
+ message: string;
103
+ }[];
104
+ /**
105
+ * Resolve a CLI-facing name. Built-ins are consulted FIRST and win
106
+ * unconditionally — a repo chain that reuses a built-in name is rejected at
107
+ * load time (see `repo_chains.ts`), so this ordering is a second line of
108
+ * defence rather than the policy itself, and it is what makes `spf build` in
109
+ * any repo mean what the docs say it means.
110
+ */
29
111
  export declare function findChain(name: string): ChainDefinition | undefined;
30
112
  export declare function resolveRequiredAgents(chain: ChainDefinition, options: Record<string, string>): string[];
113
+ /** Same idea as resolveRequiredAgents(), for the suite(s) --suite can override before validate() ever runs. */
114
+ export declare function resolveRequiredSuites(chain: ChainDefinition, options: Record<string, string>): string[];
31
115
  /**
32
116
  * Run a chain, whichever path it defines: `run` (the imperative escape
33
117
  * hatch) if it has one, otherwise `steps` through the shared driver. Both
@@ -1,7 +1,17 @@
1
1
  import * as steps from "./steps.js";
2
2
  import * as simpleSdlc from "./simple_sdlc.js";
3
- /** Build a step-based ChainDefinition, deriving phases/requiredAgents/requiredSuites from its steps. */
4
- function stepChain(name, describe, list) {
3
+ /**
4
+ * Build a step-based ChainDefinition, deriving phases/requiredAgents/
5
+ * requiredSuites from its steps.
6
+ *
7
+ * Exported so `./repo_chains.ts` builds a yaml-defined chain through the
8
+ * EXACT same function as a built-in. That is deliberate and should not be
9
+ * "simplified" into a second, parallel constructor: a repo chain must be
10
+ * indistinguishable downstream — same derivation of phases/agents/suites,
11
+ * same `steps` array, same `runSteps` driver, one run path — or the
12
+ * guarantees the built-ins are tested for stop applying to it.
13
+ */
14
+ export function stepChain(name, describe, list) {
5
15
  return {
6
16
  name,
7
17
  describe,
@@ -54,23 +64,83 @@ export const CHAINS = [
54
64
  steps.qualityCheck({ suite: "all" }),
55
65
  ]),
56
66
  stepChain("document", "write up the work that was just done, from the diff", [steps.request(), steps.changes(), steps.document()]),
67
+ stepChain("refine", "decompose a product spec into a feature/story-or-bug tree of tracker issues — spf watch's spec-ready lane", [
68
+ steps.request(),
69
+ steps.refine(),
70
+ steps.publishIssues(),
71
+ ]),
57
72
  {
58
73
  name: "simple-sdlc",
59
74
  describe: "the work is real and its shape is not obvious — plan, build, test, review, document; 3 commits",
60
75
  phases: "engineer(request) -> planner -> git(commit_plan) -> builder -> code(test) [-> builder(fix) -> code(test) ...] " +
61
- "-> reviewer [-> builder(revise) -> reviewer ...] -> code(retest, if revised) -> git(commit_build) " +
76
+ "-> reviewer [-> builder(revise) -> reviewer ...] -> code(retest, if revised) -> engineer(signoff) -> git(commit_build) " +
62
77
  "-> code(changes) -> documenter -> git(commit_docs)",
63
78
  requiredAgents: simpleSdlc.REQUIRED_AGENTS,
64
79
  requiredSuites: simpleSdlc.REQUIRED_SUITES,
65
80
  run: simpleSdlc.main,
66
81
  },
67
82
  ];
83
+ /**
84
+ * Every built-in name, reserved. `repo_chains.ts` checks against this to
85
+ * refuse a repo chain that reuses one — a collision is reported as a
86
+ * problem, never resolved by shadowing either way round.
87
+ */
88
+ export const BUILTIN_CHAIN_NAMES = new Set(CHAINS.map((c) => c.name));
89
+ // ── tier 2: repo-local chains, registered at startup ────────────────────
90
+ /**
91
+ * Module-level, not a parameter threaded through every call site: `spf list`,
92
+ * `spf <chain>`, `spf watch`, `spf doctor` and the interview all resolve
93
+ * chains by name from unrelated places, and passing a registry object to
94
+ * each would mean four more chances for one of them to be looking at the
95
+ * built-ins only. One process, one repo, one registration.
96
+ */
97
+ let REPO_CHAINS = [];
98
+ let REPO_CHAIN_PROBLEMS = [];
99
+ /**
100
+ * Install the repo-local chains (and the problems found reading them) for
101
+ * this process. Call once, early, from the CLI entry point.
102
+ *
103
+ * REPLACES rather than appends, so it is idempotent: calling it twice with
104
+ * the same input leaves exactly one copy of each chain. That matters because
105
+ * a long-running `spf watch` may re-read `.spf/chains/` after an edit, and
106
+ * because a partial second registration (say, one file fixed) must not leave
107
+ * the previous file's stale definition behind.
108
+ *
109
+ * The `problems` list is stored, not thrown and not printed: loading is
110
+ * never allowed to fail a command that has nothing to do with the broken
111
+ * file (`spf trace`, `spf sessions`, `spf --version` must all still work in
112
+ * a repo with one malformed yaml). `spf list` and `spf doctor` surface them;
113
+ * dispatch surfaces them when the name someone typed is missing.
114
+ */
115
+ export function registerRepoChains(chains, problems) {
116
+ REPO_CHAINS = [...chains];
117
+ REPO_CHAIN_PROBLEMS = [...problems];
118
+ }
119
+ /** Built-ins first, then repo-local chains — the order `spf list` prints and `findChain` resolves in. */
120
+ export function allChains() {
121
+ return [...CHAINS, ...REPO_CHAINS];
122
+ }
123
+ /** What went wrong reading `.spf/chains/*.yaml`, for `spf list`/`spf doctor` to report. */
124
+ export function repoChainProblems() {
125
+ return [...REPO_CHAIN_PROBLEMS];
126
+ }
127
+ /**
128
+ * Resolve a CLI-facing name. Built-ins are consulted FIRST and win
129
+ * unconditionally — a repo chain that reuses a built-in name is rejected at
130
+ * load time (see `repo_chains.ts`), so this ordering is a second line of
131
+ * defence rather than the policy itself, and it is what makes `spf build` in
132
+ * any repo mean what the docs say it means.
133
+ */
68
134
  export function findChain(name) {
69
- return CHAINS.find((c) => c.name === name);
135
+ return CHAINS.find((c) => c.name === name) ?? REPO_CHAINS.find((c) => c.name === name);
70
136
  }
71
137
  export function resolveRequiredAgents(chain, options) {
72
138
  return typeof chain.requiredAgents === "function" ? chain.requiredAgents(options) : chain.requiredAgents;
73
139
  }
140
+ /** Same idea as resolveRequiredAgents(), for the suite(s) --suite can override before validate() ever runs. */
141
+ export function resolveRequiredSuites(chain, options) {
142
+ return typeof chain.requiredSuites === "function" ? chain.requiredSuites(options) : chain.requiredSuites;
143
+ }
74
144
  /**
75
145
  * Run a chain, whichever path it defines: `run` (the imperative escape
76
146
  * hatch) if it has one, otherwise `steps` through the shared driver. Both
@@ -81,5 +151,5 @@ export function resolveRequiredAgents(chain, options) {
81
151
  export async function runChain(chain, ctx, options = {}) {
82
152
  if (chain.run)
83
153
  return chain.run(ctx, options);
84
- return steps.runSteps(ctx, resolveRequiredAgents(chain, options), chain.requiredSuites, chain.steps, options);
154
+ return steps.runSteps(ctx, resolveRequiredAgents(chain, options), resolveRequiredSuites(chain, options), chain.steps, options);
85
155
  }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Repo-local chains: `<repo>/.spf/chains/*.yaml` read as DATA.
3
+ *
4
+ * WHAT THIS IS. A target repo can compose its own chain out of the step
5
+ * factories `./steps.ts` already exports, by naming them in a yaml file and
6
+ * passing them params. One file, one chain:
7
+ *
8
+ * # .spf/chains/ship-it.yaml
9
+ * name: ship-it
10
+ * describe: plan, build, test, land — with our own reviewer in the loop
11
+ * steps:
12
+ * - step: request
13
+ * - step: plan
14
+ * owner: architect
15
+ * - step: build
16
+ * retries: 2
17
+ * extraGates: [jsonParses]
18
+ * - step: fixLoop
19
+ * suite: test
20
+ * - step: commit
21
+ * onlyIfAccepted: true
22
+ *
23
+ * WHY DATA, AND NOT CODE. SPF's contract is "agent proposes, code disposes",
24
+ * and the code that disposes is SPF'S code. A chain that could `import`
25
+ * something out of the target repo would move the disposer into the repo
26
+ * being worked on — the agent's own blast radius — and there would be
27
+ * nothing left holding the line. So a repo chain names existing factories
28
+ * and nothing else: no expressions, no shell, no module paths, no way to
29
+ * introduce behavior that isn't already compiled into spf and covered by
30
+ * spf's tests. It is also what keeps the promise that a target repo is
31
+ * zero-setup and language-agnostic: the only thing it ever gains is `.spf/`,
32
+ * with no build step, no dependency on this package, and nothing to compile.
33
+ *
34
+ * The counterpart rule lives in `steps.ts` (see GATE_ALLOWLIST): params may
35
+ * only ADD. A repo chain can demand more checking than a built-in does; it
36
+ * can never demand less.
37
+ *
38
+ * ONE RUN PATH. Every chain here is built with `index.ts`'s `stepChain()` —
39
+ * the very same function the built-ins use — so it is a plain
40
+ * `ChainDefinition` with a `steps` array, its `phases`/`requiredAgents`/
41
+ * `requiredSuites` derived by the same code, executed by the same
42
+ * `steps.runSteps()` driver. There is deliberately no second interpreter,
43
+ * no "yaml runtime", and no branch anywhere downstream on "is this a repo
44
+ * chain": the only difference a repo chain carries is `source`, the file it
45
+ * came from, which exists so a run can be traced back to a definition that
46
+ * may since have been edited.
47
+ *
48
+ * NEVER THROWS. `loadRepoChains()` returns `{ chains, problems }`. That is
49
+ * not politeness: this function runs at CLI startup for EVERY command (see
50
+ * `cli/index.ts`), so a single malformed yaml must not be able to break
51
+ * `spf sessions`, `spf trace`, `spf doctor` or `spf --version` in a repo
52
+ * that has one. Anything that goes wrong — unreadable directory, invalid
53
+ * yaml, unknown step, wrong param type, bad description, unknown gate,
54
+ * colliding name — becomes a `{ file, message }` problem carrying enough
55
+ * detail to fix the file, and the other files still load.
56
+ *
57
+ * THE VOCABULARY IS THE FACTORY SIGNATURE. A step's yaml name is its
58
+ * exported function name (`fixLoop`, `promptOnly`, `publishIssues`) and its
59
+ * params are that function's `opts` keys, camelCase and all (`extraGates`,
60
+ * `onlyIfAccepted`, `fromPlan`). No snake_case aliasing, no renaming layer:
61
+ * a second spelling of the same thing is a second thing to keep in sync, and
62
+ * a chain author reading `steps.ts` (or `spf list`) would be reading a
63
+ * vocabulary that isn't the one they type. The schemas below are the ONE
64
+ * place that mapping is written down; each mirrors exactly one factory's
65
+ * `opts`, and adding a param to a factory without adding it here simply
66
+ * means yaml cannot reach it yet (a safe, loud default: unknown params are
67
+ * rejected, never ignored).
68
+ */
69
+ import * as v from "valibot";
70
+ import type * as paths from "../core/paths.ts";
71
+ import * as steps from "./steps.ts";
72
+ import { type ChainDefinition } from "./index.ts";
73
+ /** What went wrong in one file, and which file. */
74
+ export interface RepoChainProblem {
75
+ /** Absolute path — the thing the operator has to open and edit. */
76
+ file: string;
77
+ message: string;
78
+ }
79
+ /**
80
+ * A step's yaml surface: the schema its params must satisfy, and how to
81
+ * build the actual `Step` from them.
82
+ *
83
+ * `params` is redundant with the schema's own entries, and kept anyway: it
84
+ * lets an unknown param be reported as `unknown param "ownr" — allowed:
85
+ * owner, description, ...`, which is what an author needs, instead of
86
+ * valibot's structural complaint about an unexpected key.
87
+ */
88
+ export interface StepSpec {
89
+ schema: v.GenericSchema;
90
+ build: (params: any) => steps.Step;
91
+ params: readonly string[];
92
+ }
93
+ /**
94
+ * One entry per exported flat-step factory in `steps.ts`. The key is what a
95
+ * yaml `step:` names.
96
+ *
97
+ * `qualityCheck`/`fixLoop` default their `suite` HERE as well as in the
98
+ * factory signature: the factory's default lives on the whole `opts` object
99
+ * (`opts = { suite: "test" }`), which a yaml-supplied `{}` would satisfy
100
+ * without ever supplying `suite`. Defaulting in the schema is what makes
101
+ * `- step: fixLoop` with no params mean the same thing as `fixLoop()`.
102
+ */
103
+ export declare const STEP_SPECS: Record<string, StepSpec>;
104
+ /** Every step name a yaml file may use — for schema validation and for error messages. */
105
+ export declare const STEP_NAMES: readonly string[];
106
+ /**
107
+ * The whole file. One document, one chain — deliberately not a list: the
108
+ * filename then documents which chain lives where, and a problem can point
109
+ * at a file the operator can open, which is the only handle they have.
110
+ *
111
+ * `steps` is validated loosely here (each entry only has to be an object
112
+ * naming a `step`); the per-step params are validated against that step's
113
+ * own schema afterwards, because which schema applies depends on the value
114
+ * of `step`.
115
+ */
116
+ export declare const RepoChainFileSchema: v.StrictObjectSchema<{
117
+ readonly name: v.SchemaWithPipe<readonly [v.StringSchema<"name is required — it is what `spf <name>` types">, v.RegexAction<string, "name must be lowercase letters/digits/._- and start with a letter or digit (it is typed on the command line)">]>;
118
+ readonly describe: v.SchemaWithPipe<readonly [v.StringSchema<"describe is required — it is the line `spf list` prints">, v.MinLengthAction<string, 1, "describe must not be empty">]>;
119
+ readonly steps: v.SchemaWithPipe<readonly [v.ArraySchema<v.LooseObjectSchema<{
120
+ readonly step: v.StringSchema<"each step entry needs a `step:` naming a step factory">;
121
+ }, undefined>, "steps must be a list">, v.MinLengthAction<({
122
+ step: string;
123
+ } & {
124
+ [key: string]: unknown;
125
+ })[], 1, "steps must name at least one step — a chain with no steps would run nothing and report success">]>;
126
+ }, undefined>;
127
+ export type RepoChainFile = v.InferOutput<typeof RepoChainFileSchema>;
128
+ /**
129
+ * Read every chain in `<anchor.spf_dir>/chains/`.
130
+ *
131
+ * Never throws (see the module header). Returns the chains that loaded and a
132
+ * problem per file that didn't; a file contributes at most one chain and, on
133
+ * failure, exactly one problem — the first thing wrong with it, because the
134
+ * second is usually a consequence of the first.
135
+ */
136
+ export declare function loadRepoChains(anchor: paths.RepoAnchor): {
137
+ chains: ChainDefinition[];
138
+ problems: RepoChainProblem[];
139
+ };