@gr8ful/spf 0.9.2 → 0.10.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 (37) hide show
  1. package/README.md +56 -0
  2. package/assets/defaults/spf.config.yaml +75 -0
  3. package/assets/skill/references/config.md +98 -4
  4. package/dist/chains/index.d.ts +2 -0
  5. package/dist/chains/index.js +4 -0
  6. package/dist/cli/commands/doctor.js +339 -2
  7. package/dist/cli/commands/fanout.d.ts +7 -14
  8. package/dist/cli/commands/fanout.js +45 -39
  9. package/dist/cli/commands/loop.d.ts +2 -0
  10. package/dist/cli/commands/loop.js +198 -0
  11. package/dist/cli/commands/run.js +14 -4
  12. package/dist/cli/commands/watch.d.ts +29 -1
  13. package/dist/cli/commands/watch.js +219 -64
  14. package/dist/cli/index.js +14 -0
  15. package/dist/core/agent_cc.d.ts +11 -0
  16. package/dist/core/agent_cc.js +25 -2
  17. package/dist/core/agent_flue.js +14 -5
  18. package/dist/core/agents.d.ts +61 -1
  19. package/dist/core/agents.js +363 -6
  20. package/dist/core/data_types.d.ts +316 -0
  21. package/dist/core/data_types.js +143 -0
  22. package/dist/core/loop.d.ts +230 -0
  23. package/dist/core/loop.js +290 -0
  24. package/dist/core/quality.d.ts +1 -2
  25. package/dist/core/sandbox.d.ts +236 -0
  26. package/dist/core/sandbox.js +655 -0
  27. package/dist/core/sandbox_cloudflare.d.ts +137 -0
  28. package/dist/core/sandbox_cloudflare.js +505 -0
  29. package/dist/core/sandbox_opensandbox.d.ts +59 -0
  30. package/dist/core/sandbox_opensandbox.js +484 -0
  31. package/dist/core/sandbox_sdk_types.d.ts +171 -0
  32. package/dist/core/sandbox_sdk_types.js +20 -0
  33. package/dist/core/watch.d.ts +56 -0
  34. package/dist/core/watch.js +354 -51
  35. package/dist/core/worktree_data.d.ts +1 -0
  36. package/dist/core/worktree_data.js +37 -0
  37. package/package.json +1 -1
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Hand-written surface of `@alibaba-group/opensandbox` v0.1.11 (SPF #15
3
+ * design doc §4.1's "SDK dependency — no new entry in package.json").
4
+ *
5
+ * NOTHING in this file imports the real package — every shape below is
6
+ * copied from the spike's MEASURED API surface (see the scratchpad's
7
+ * `sandbox-spike.json`, `api_surface` field), not from the package's own
8
+ * `.d.ts` (which is never on disk in this tree: the SDK is a documented user
9
+ * install, §4.1, never a `package.json` dependency). `sandbox_opensandbox.ts`
10
+ * types `loadOpenSandboxSdk`'s return value against these interfaces so the
11
+ * whole tree typechecks with the SDK absent from `node_modules` — exactly
12
+ * the trade `CloudflareSandboxStub` makes deliberately
13
+ * (`dist/cloudflare/index.d.mts:6-12`: "A wrong object fails loudly on the
14
+ * first method call").
15
+ *
16
+ * A shape mismatch against a REAL installed SDK surfaces as a runtime error
17
+ * from the first call that touches the mismatched field, never as a compile
18
+ * error — there is nothing here for `tsc` to check it against.
19
+ */
20
+ export interface OpenSandboxConnectionConfig {
21
+ domain: string;
22
+ protocol?: "http" | "https";
23
+ apiKey?: string;
24
+ /** The SDK's CLIENT-SIDE HTTP timeout for control-plane calls. NOT a bound on a streamed `commands.run`. */
25
+ requestTimeoutSeconds?: number;
26
+ headers?: Record<string, string>;
27
+ useServerProxy?: boolean;
28
+ }
29
+ export interface OpenSandboxNetworkPolicy {
30
+ defaultAction: "allow" | "deny";
31
+ egress?: Array<{
32
+ action: "allow" | "deny";
33
+ target: string;
34
+ }>;
35
+ }
36
+ export interface OpenSandboxCreateOptions {
37
+ connectionConfig: OpenSandboxConnectionConfig;
38
+ image: string;
39
+ entrypoint?: string;
40
+ /** Provider-side lease expiry, in seconds — `sandbox.renew()` extends it while a lease is live. */
41
+ timeoutSeconds?: number;
42
+ env?: Record<string, string>;
43
+ resource?: unknown;
44
+ metadata?: Record<string, string>;
45
+ networkPolicy?: OpenSandboxNetworkPolicy;
46
+ credentialProxy?: unknown;
47
+ extensions?: unknown;
48
+ skipHealthCheck?: boolean;
49
+ healthCheck?: unknown;
50
+ /** Readiness bound — distinct from `requestTimeoutSeconds`. The spike measured a 30-90s cold egress-sidecar start against exactly this knob. */
51
+ readyTimeoutSeconds?: number;
52
+ }
53
+ export interface OpenSandboxCommandOptions {
54
+ /** Maps to the API's `cwd` — the SDK's own field name is `workingDirectory`, not `cwd` (confirmed against the installed SDK's `.d.ts`). */
55
+ workingDirectory?: string;
56
+ /** The SDK's own field name is `envs`, not `env`. */
57
+ envs?: Record<string, string>;
58
+ /**
59
+ * O-1 (open question, design §12): the spike documented `commands.run`'s
60
+ * signature but never measured whether it accepts a per-command deadline.
61
+ * Forwarded best-effort, seconds, rounded UP per `sandbox-api.md:225` —
62
+ * the adapter's OWN host-side timer (§4.1 point 3) is what actually
63
+ * enforces the deadline regardless of whether the SDK honors this field.
64
+ */
65
+ timeoutSeconds?: number;
66
+ }
67
+ export interface OpenSandboxLogChunk {
68
+ text: string;
69
+ }
70
+ export interface OpenSandboxCommandResult {
71
+ logs: {
72
+ stdout: OpenSandboxLogChunk[];
73
+ stderr: OpenSandboxLogChunk[];
74
+ };
75
+ exitCode: number;
76
+ executionTimeMs: number;
77
+ }
78
+ export interface OpenSandboxExecutionHandlers {
79
+ onStdout?: (chunk: OpenSandboxLogChunk) => void;
80
+ onStderr?: (chunk: OpenSandboxLogChunk) => void;
81
+ onExecutionComplete?: (result: OpenSandboxCommandResult) => void;
82
+ }
83
+ export interface OpenSandboxCommandsApi {
84
+ run(command: string, options?: OpenSandboxCommandOptions, handlers?: OpenSandboxExecutionHandlers): Promise<OpenSandboxCommandResult>;
85
+ }
86
+ export interface OpenSandboxDirEntry {
87
+ path: string;
88
+ mode?: number;
89
+ }
90
+ export interface OpenSandboxFileEntry {
91
+ path: string;
92
+ data: string | Uint8Array;
93
+ mode?: number;
94
+ }
95
+ export interface OpenSandboxFilesApi {
96
+ createDirectories(dirs: OpenSandboxDirEntry[]): Promise<void>;
97
+ writeFiles(files: OpenSandboxFileEntry[]): Promise<void>;
98
+ readFile(path: string): Promise<string>;
99
+ search(options: {
100
+ path: string;
101
+ pattern: string;
102
+ }): Promise<unknown>;
103
+ deleteDirectories(paths: string[]): Promise<void>;
104
+ }
105
+ export interface OpenSandboxInfo {
106
+ status: {
107
+ state: string;
108
+ };
109
+ createdAt: string;
110
+ expiresAt: string;
111
+ }
112
+ export interface OpenSandboxEgressRule {
113
+ action: "allow" | "deny";
114
+ target: string;
115
+ }
116
+ export interface OpenSandboxEgressPolicy {
117
+ defaultAction: "allow" | "deny";
118
+ egress: OpenSandboxEgressRule[];
119
+ }
120
+ export interface OpenSandboxInstance {
121
+ readonly id: string;
122
+ commands: OpenSandboxCommandsApi;
123
+ files: OpenSandboxFilesApi;
124
+ getInfo(): Promise<OpenSandboxInfo>;
125
+ pause(): Promise<void>;
126
+ /** Resumes returns a FRESH connected instance — not `this`, per the spike. */
127
+ resume(): Promise<OpenSandboxInstance>;
128
+ renew(seconds: number): Promise<void>;
129
+ /** Terminates the remote sandbox + sidecar. Does NOT release the local HTTP agent — see `close()`. */
130
+ kill(): Promise<void>;
131
+ /** Releases the Node undici keep-alive agent. Separate from `kill()` — BOTH are required teardown calls (spike caveat). */
132
+ close(): Promise<void>;
133
+ getEndpoint(port: number): Promise<{
134
+ endpoint: string;
135
+ }>;
136
+ getEndpointUrl(port: number): Promise<string>;
137
+ /** Talks directly to the egress sidecar's own REST surface on sandbox port 18080 — NOT proxied through the control plane. */
138
+ getEgressPolicy(): Promise<OpenSandboxEgressPolicy>;
139
+ patchEgressRules(rules: OpenSandboxEgressRule[]): Promise<void>;
140
+ }
141
+ export interface OpenSandboxCtor {
142
+ create(options: OpenSandboxCreateOptions): Promise<OpenSandboxInstance>;
143
+ }
144
+ export interface OpenSandboxManagerListItem {
145
+ id: string;
146
+ metadata?: Record<string, string>;
147
+ status?: {
148
+ state: string;
149
+ };
150
+ }
151
+ export interface OpenSandboxManagerListResult {
152
+ items: OpenSandboxManagerListItem[];
153
+ pagination?: unknown;
154
+ }
155
+ export interface OpenSandboxManagerInstance {
156
+ listSandboxInfos(options?: {
157
+ states?: string[];
158
+ pageSize?: number;
159
+ }): Promise<OpenSandboxManagerListResult>;
160
+ close(): Promise<void>;
161
+ }
162
+ export interface OpenSandboxManagerCtor {
163
+ create(options: {
164
+ connectionConfig: OpenSandboxConnectionConfig;
165
+ }): Promise<OpenSandboxManagerInstance>;
166
+ }
167
+ /** The module namespace shape `import(OPENSANDBOX_SPECIFIER)` resolves to. */
168
+ export interface OpenSandboxModuleShape {
169
+ Sandbox: OpenSandboxCtor;
170
+ SandboxManager: OpenSandboxManagerCtor;
171
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Hand-written surface of `@alibaba-group/opensandbox` v0.1.11 (SPF #15
3
+ * design doc §4.1's "SDK dependency — no new entry in package.json").
4
+ *
5
+ * NOTHING in this file imports the real package — every shape below is
6
+ * copied from the spike's MEASURED API surface (see the scratchpad's
7
+ * `sandbox-spike.json`, `api_surface` field), not from the package's own
8
+ * `.d.ts` (which is never on disk in this tree: the SDK is a documented user
9
+ * install, §4.1, never a `package.json` dependency). `sandbox_opensandbox.ts`
10
+ * types `loadOpenSandboxSdk`'s return value against these interfaces so the
11
+ * whole tree typechecks with the SDK absent from `node_modules` — exactly
12
+ * the trade `CloudflareSandboxStub` makes deliberately
13
+ * (`dist/cloudflare/index.d.mts:6-12`: "A wrong object fails loudly on the
14
+ * first method call").
15
+ *
16
+ * A shape mismatch against a REAL installed SDK surfaces as a runtime error
17
+ * from the first call that touches the mismatched field, never as a compile
18
+ * error — there is nothing here for `tsc` to check it against.
19
+ */
20
+ export {};
@@ -1,4 +1,5 @@
1
1
  import type { GitHandle } from "./git_helper.ts";
2
+ import { type AttemptDispatch, type AttemptMetrics } from "./fanout.ts";
2
3
  import type { CodeHostProvider, Issue, IssueComment, IssueProvider, WatchMarker, WatchState } from "./issues/provider.ts";
3
4
  import type { NotifyEvent } from "./notify/channel.ts";
4
5
  import { type RefinedPriority } from "./data_types.ts";
@@ -52,6 +53,50 @@ export interface RefineRunResult {
52
53
  /** What the refiner is asking, read back from its own side-channel file — see `cli/commands/watch.ts`'s `runRefine`. Empty when `!accepted` or when the run published a tree instead of escalating; `gates.refinementWellFormed` guarantees `created` and `questions` are never both non-empty. */
53
54
  questions: RefinedQuestionRef[];
54
55
  }
56
+ /**
57
+ * The fan-out lane's injected trio + the two values `WatchDeps` cannot derive.
58
+ * Exported as a NAMED type (not an inline object literal) because
59
+ * `cli/commands/watch.ts`'s `makeWatchFanoutDispatch` factory returns it and
60
+ * `src/test/watch_fanout_sandbox_wiring.test.ts` names it — an inline literal
61
+ * would have to be duplicated in three places.
62
+ */
63
+ export interface WatchFanoutDeps {
64
+ /** > 1. `cli/commands/watch.ts` passes `undefined` for this whole object when `watch.fanout.n` is 1. */
65
+ n: number;
66
+ /** Attempts IN FLIGHT within one issue — NOT `WatchDeps.concurrency`, which counts issues. Does NOT bound worktrees on disk (see the module's fan-out doc). */
67
+ concurrency: number;
68
+ /** The MAIN repo root. `GitHandle` has no accessor for it — `cli/commands/watch.ts` passes its anchor's `repo_root`, the same value `deps.git` is bound to. */
69
+ repoRoot: string;
70
+ /** One attempt's chain, in that attempt's own worktree — the same ctx `runChain` builds, per attempt. */
71
+ runAttempt: (dispatch: AttemptDispatch) => Promise<number>;
72
+ /** Gate/usage rows for one attempt's adw_id, from the SHARED db. Must not throw. */
73
+ readMetrics: (adwId: string) => AttemptMetrics;
74
+ /**
75
+ * True when NOT ONE of these adw_ids has a session row yet — the same db,
76
+ * the same predicate and the same reasoning as `spf fanout`'s reuse
77
+ * preflight (`cli/commands/fanout.ts`, `preflight.session(id) !== null`).
78
+ * This is what advances the fan-out lane's base-adw_id salt on EVERY claim
79
+ * (see `runIssueFanout`'s salt probe) — the sessions the previous claim's
80
+ * attempts left behind are the only thing that reliably changes on every
81
+ * re-claim, unlike `WatchMarker.attempt`, which only advances on the
82
+ * orphan-retry path.
83
+ *
84
+ * Must not throw, and its safe direction is FALSE: a db it cannot read
85
+ * reports "taken", which burns an id and keeps the selection basis clean,
86
+ * rather than reporting "free" and letting a previous run's rows decide
87
+ * this run's winner.
88
+ */
89
+ adwIdsFree: (adwIds: string[]) => boolean;
90
+ /** The winner's review posture, read back post-hoc — the same two fields `runChain` returns, keyed on a WINNER instead of the sole attempt. */
91
+ reviewFor: (opts: {
92
+ cwd: string;
93
+ adwId: string;
94
+ chainOptions: Record<string, string>;
95
+ }) => {
96
+ reviewRequired: boolean;
97
+ reviewSummary?: string;
98
+ };
99
+ }
55
100
  export interface WatchDeps {
56
101
  provider: IssueProvider;
57
102
  codeHost: CodeHostProvider;
@@ -133,6 +178,17 @@ export interface WatchDeps {
133
178
  * just be noise in a channel.
134
179
  */
135
180
  notify: (event: NotifyEvent) => void;
181
+ /**
182
+ * Best-of-N per claimed issue (`watch.fanout`). `undefined` — the default,
183
+ * and what every existing test constructs — means single dispatch:
184
+ * `runIssue` takes the path it always has.
185
+ *
186
+ * ONE OPTIONAL OBJECT, not several optional fields: everything the fan-out
187
+ * lane needs is required WHEN THE LANE IS ON and meaningless when it is
188
+ * off, so the optionality belongs at the lane, not at each field. There is
189
+ * no "optional but secretly required" member to get wrong.
190
+ */
191
+ fanout?: WatchFanoutDeps;
136
192
  }
137
193
  export interface WatchRunState {
138
194
  inflight: Set<string>;