@gr8ful/spf 0.1.7 → 0.3.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 +57 -2
- package/assets/skill/cookbooks/authoring_chains.md +96 -84
- package/assets/skill/cookbooks/roster.md +3 -1
- package/assets/skill/references/config.md +52 -5
- package/assets/templates/ts.spf.config.yaml +12 -0
- package/dist/chains/context.d.ts +2 -0
- package/dist/chains/index.d.ts +21 -2
- package/dist/chains/index.js +73 -104
- package/dist/chains/{adw_simple_sdlc.d.ts → simple_sdlc.d.ts} +7 -1
- package/dist/chains/{adw_simple_sdlc.js → simple_sdlc.js} +19 -30
- package/dist/chains/steps.d.ts +117 -0
- package/dist/chains/steps.js +299 -0
- package/dist/cli/ask.d.ts +27 -0
- package/dist/cli/ask.js +125 -0
- package/dist/cli/commands/doctor.js +14 -24
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +96 -9
- package/dist/cli/commands/run.d.ts +1 -1
- package/dist/cli/commands/run.js +3 -1
- package/dist/cli/commands/watch.js +20 -3
- package/dist/cli/env_file.d.ts +18 -0
- package/dist/cli/env_file.js +99 -0
- package/dist/cli/index.js +6 -2
- package/dist/cli/interview.d.ts +26 -0
- package/dist/cli/interview.js +417 -0
- package/dist/core/agents.js +4 -1
- package/dist/core/console.d.ts +13 -1
- package/dist/core/console.js +51 -1
- package/dist/core/data_types.d.ts +55 -0
- package/dist/core/data_types.js +30 -0
- package/dist/core/notify/channel.d.ts +32 -0
- package/dist/core/notify/channel.js +14 -0
- package/dist/core/notify/notifier.d.ts +42 -0
- package/dist/core/notify/notifier.js +100 -0
- package/dist/core/notify/slack_channel.d.ts +13 -0
- package/dist/core/notify/slack_channel.js +30 -0
- package/dist/core/notify/teams_channel.d.ts +17 -0
- package/dist/core/notify/teams_channel.js +38 -0
- package/dist/core/notify/webhook_channel.d.ts +13 -0
- package/dist/core/notify/webhook_channel.js +19 -0
- package/dist/core/prompts.d.ts +2 -0
- package/dist/core/prompts.js +2 -0
- package/dist/core/providers.d.ts +12 -0
- package/dist/core/providers.js +24 -0
- package/dist/core/quality.d.ts +9 -0
- package/dist/core/quality.js +10 -0
- package/dist/core/runner.d.ts +7 -0
- package/dist/core/runner.js +4 -1
- package/dist/core/session.d.ts +6 -1
- package/dist/core/session.js +10 -3
- package/dist/core/tracer.js +1 -1
- package/dist/core/utils.d.ts +6 -2
- package/dist/core/utils.js +11 -2
- package/dist/core/watch.d.ts +10 -0
- package/dist/core/watch.js +67 -4
- package/dist/test/chains.test.d.ts +12 -0
- package/dist/test/chains.test.js +86 -0
- package/dist/test/data_types.test.js +34 -1
- package/dist/test/env_file.test.d.ts +1 -0
- package/dist/test/env_file.test.js +74 -0
- package/dist/test/fake_asker.d.ts +23 -0
- package/dist/test/fake_asker.js +30 -0
- package/dist/test/init_command.test.d.ts +1 -0
- package/dist/test/init_command.test.js +66 -0
- package/dist/test/interview.test.d.ts +1 -0
- package/dist/test/interview.test.js +297 -0
- package/dist/test/notify.test.d.ts +1 -0
- package/dist/test/notify.test.js +174 -0
- package/dist/test/ui_server.test.js +1 -1
- package/dist/test/watch.test.js +114 -1
- package/dist/ui/shared/types.d.ts +1 -1
- package/package.json +5 -2
- package/dist/chains/adw_build.d.ts +0 -12
- package/dist/chains/adw_build.js +0 -27
- package/dist/chains/adw_build_review.d.ts +0 -21
- package/dist/chains/adw_build_review.js +0 -55
- package/dist/chains/adw_build_test.d.ts +0 -21
- package/dist/chains/adw_build_test.js +0 -67
- package/dist/chains/adw_document.d.ts +0 -23
- package/dist/chains/adw_document.js +0 -59
- package/dist/chains/adw_plan.d.ts +0 -12
- package/dist/chains/adw_plan.js +0 -27
- package/dist/chains/adw_plan_build.d.ts +0 -12
- package/dist/chains/adw_plan_build.js +0 -30
- package/dist/chains/adw_plan_build_test.d.ts +0 -16
- package/dist/chains/adw_plan_build_test.js +0 -65
- package/dist/chains/adw_plan_build_test_quality.d.ts +0 -18
- package/dist/chains/adw_plan_build_test_quality.js +0 -66
- package/dist/chains/adw_prompt.d.ts +0 -12
- package/dist/chains/adw_prompt.js +0 -25
- package/dist/chains/adw_quality.d.ts +0 -12
- package/dist/chains/adw_quality.js +0 -32
- package/dist/chains/adw_scout.d.ts +0 -12
- package/dist/chains/adw_scout.js +0 -27
package/dist/chains/index.js
CHANGED
|
@@ -1,111 +1,68 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import * as steps from "./steps.js";
|
|
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) {
|
|
5
|
+
return {
|
|
6
|
+
name,
|
|
7
|
+
describe,
|
|
8
|
+
phases: steps.derivePhases(list),
|
|
9
|
+
requiredAgents: steps.deriveRequiredAgents(list),
|
|
10
|
+
requiredSuites: steps.deriveRequiredSuites(list),
|
|
11
|
+
steps: list,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
13
14
|
export const CHAINS = [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
describe: "there is a suite to satisfy — build, test, bounded fix loop",
|
|
57
|
-
phases: "engineer(request) -> builder -> code(test) [-> builder(fix) -> code(test) ...]",
|
|
58
|
-
requiredAgents: adwBuildTest.REQUIRED_AGENTS,
|
|
59
|
-
requiredSuites: adwBuildTest.REQUIRED_SUITES,
|
|
60
|
-
run: adwBuildTest.main,
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: "plan-build-test",
|
|
64
|
-
describe: "the standard chain — plan, build, test, commit",
|
|
65
|
-
phases: "engineer(request) -> planner -> builder -> code(test) [-> fix loop] -> git(commit)",
|
|
66
|
-
requiredAgents: adwPlanBuildTest.REQUIRED_AGENTS,
|
|
67
|
-
requiredSuites: adwPlanBuildTest.REQUIRED_SUITES,
|
|
68
|
-
run: adwPlanBuildTest.main,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: "plan-build-test-quality",
|
|
72
|
-
describe: "the repo has quality commands worth enforcing beyond tests — same, plus lint/typecheck/build gates",
|
|
73
|
-
phases: "engineer(request) -> planner -> builder -> code(quality:all) [-> fix loop] -> git(commit)",
|
|
74
|
-
requiredAgents: adwPlanBuildTestQuality.REQUIRED_AGENTS,
|
|
75
|
-
requiredSuites: adwPlanBuildTestQuality.REQUIRED_SUITES,
|
|
76
|
-
run: adwPlanBuildTestQuality.main,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "build-review",
|
|
80
|
-
describe: "\"is this what was asked for\" matters more than \"does it run\"",
|
|
81
|
-
phases: "engineer(request) -> builder -> reviewer [-> revise loop]",
|
|
82
|
-
requiredAgents: adwBuildReview.REQUIRED_AGENTS,
|
|
83
|
-
requiredSuites: adwBuildReview.REQUIRED_SUITES,
|
|
84
|
-
run: adwBuildReview.main,
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
name: "quality",
|
|
88
|
-
describe: "lint, typecheck, build — no agents at all",
|
|
89
|
-
phases: "engineer(request) -> code(quality:all)",
|
|
90
|
-
requiredAgents: adwQuality.REQUIRED_AGENTS,
|
|
91
|
-
requiredSuites: adwQuality.REQUIRED_SUITES,
|
|
92
|
-
run: adwQuality.main,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: "document",
|
|
96
|
-
describe: "write up the work that was just done, from the diff",
|
|
97
|
-
phases: "engineer(request) -> code(changes) -> documenter",
|
|
98
|
-
requiredAgents: adwDocument.REQUIRED_AGENTS,
|
|
99
|
-
requiredSuites: adwDocument.REQUIRED_SUITES,
|
|
100
|
-
run: adwDocument.main,
|
|
101
|
-
},
|
|
15
|
+
stepChain("prompt", "one agent, one prompt, traced end to end — --agent <name> picks who (default: builder)", [
|
|
16
|
+
steps.request(),
|
|
17
|
+
steps.promptOnly(),
|
|
18
|
+
]),
|
|
19
|
+
stepChain("scout", "read-only recon; nothing changes", [steps.request(), steps.scout()]),
|
|
20
|
+
stepChain("plan", "turn a request into an implementable plan", [steps.request(), steps.plan()]),
|
|
21
|
+
stepChain("build", "implement an existing plan", [steps.request(), steps.build({ fromPlan: false, retries: 1 })]),
|
|
22
|
+
stepChain("plan-build", "small, well-understood work — plan, build, commit", [
|
|
23
|
+
steps.request(),
|
|
24
|
+
steps.plan(),
|
|
25
|
+
steps.build(),
|
|
26
|
+
steps.commit(),
|
|
27
|
+
]),
|
|
28
|
+
stepChain("build-test", "there is a suite to satisfy — build, test, bounded fix loop", [
|
|
29
|
+
steps.request(),
|
|
30
|
+
steps.build({ fromPlan: false }),
|
|
31
|
+
steps.fixLoop({ suite: "test" }),
|
|
32
|
+
]),
|
|
33
|
+
stepChain("plan-build-test", "the standard chain — plan, build, test, commit", [
|
|
34
|
+
steps.request(),
|
|
35
|
+
steps.plan(),
|
|
36
|
+
steps.build(),
|
|
37
|
+
steps.fixLoop({ suite: "test" }),
|
|
38
|
+
steps.commit({ onlyIfAccepted: true }),
|
|
39
|
+
]),
|
|
40
|
+
stepChain("plan-build-test-quality", "the repo has quality commands worth enforcing beyond tests — same, plus lint/typecheck/build gates", [
|
|
41
|
+
steps.request(),
|
|
42
|
+
steps.plan(),
|
|
43
|
+
steps.build(),
|
|
44
|
+
steps.fixLoop({ suite: "all" }),
|
|
45
|
+
steps.commit({ onlyIfAccepted: true }),
|
|
46
|
+
]),
|
|
47
|
+
stepChain("build-review", '"is this what was asked for" matters more than "does it run"', [
|
|
48
|
+
steps.request(),
|
|
49
|
+
steps.build({ fromPlan: false }),
|
|
50
|
+
steps.reviseLoop(),
|
|
51
|
+
]),
|
|
52
|
+
stepChain("quality", "lint, typecheck, build — no agents at all", [
|
|
53
|
+
steps.request({ description: "Capture why quality verification was requested" }),
|
|
54
|
+
steps.qualityCheck({ suite: "all" }),
|
|
55
|
+
]),
|
|
56
|
+
stepChain("document", "write up the work that was just done, from the diff", [steps.request(), steps.changes(), steps.document()]),
|
|
102
57
|
{
|
|
103
58
|
name: "simple-sdlc",
|
|
104
59
|
describe: "the work is real and its shape is not obvious — plan, build, test, review, document; 3 commits",
|
|
105
|
-
phases: "engineer(request) -> planner -> builder -> code(test) ->
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
60
|
+
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) " +
|
|
62
|
+
"-> code(changes) -> documenter -> git(commit_docs)",
|
|
63
|
+
requiredAgents: simpleSdlc.REQUIRED_AGENTS,
|
|
64
|
+
requiredSuites: simpleSdlc.REQUIRED_SUITES,
|
|
65
|
+
run: simpleSdlc.main,
|
|
109
66
|
},
|
|
110
67
|
];
|
|
111
68
|
export function findChain(name) {
|
|
@@ -114,3 +71,15 @@ export function findChain(name) {
|
|
|
114
71
|
export function resolveRequiredAgents(chain, options) {
|
|
115
72
|
return typeof chain.requiredAgents === "function" ? chain.requiredAgents(options) : chain.requiredAgents;
|
|
116
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Run a chain, whichever path it defines: `run` (the imperative escape
|
|
76
|
+
* hatch) if it has one, otherwise `steps` through the shared driver. Both
|
|
77
|
+
* CLI dispatch sites (`spf <chain>` and `spf watch`) go through this, never
|
|
78
|
+
* `chain.run(...)` directly — the whole reason to route through here is that
|
|
79
|
+
* a `steps`-only chain has no `run` to call.
|
|
80
|
+
*/
|
|
81
|
+
export async function runChain(chain, ctx, options = {}) {
|
|
82
|
+
if (chain.run)
|
|
83
|
+
return chain.run(ctx, options);
|
|
84
|
+
return steps.runSteps(ctx, resolveRequiredAgents(chain, options), chain.requiredSuites, chain.steps, options);
|
|
85
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Simple SDLC — plan, build, test, review, document, committing as it goes.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* spf simple-sdlc "<prompt or path/to/prompt.md>" [--config <path>] [--adw-id a1b2c3d4]
|
|
@@ -36,6 +36,12 @@
|
|
|
36
36
|
* The documenter measures against the commit this run STARTED from, not against
|
|
37
37
|
* `main`, because by then the run has moved `main` itself. That baseline is
|
|
38
38
|
* pinned before the first commit phase and printed in the request phase.
|
|
39
|
+
*
|
|
40
|
+
* This is the one chain still shaped by hand rather than by a `steps` list —
|
|
41
|
+
* three commits, a pinned baseline, and a conditional retest don't collapse
|
|
42
|
+
* cleanly into the flat, loop-free vocabulary `./steps.ts` provides for every
|
|
43
|
+
* other chain. It still reuses that module's shared helpers (`startRun`,
|
|
44
|
+
* `commitEnvelope`, `logChangeset`) rather than keeping its own copies.
|
|
39
45
|
*/
|
|
40
46
|
import type { ChainContext } from "./context.ts";
|
|
41
47
|
export declare const REQUIRED_AGENTS: string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Simple SDLC — plan, build, test, review, document, committing as it goes.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* spf simple-sdlc "<prompt or path/to/prompt.md>" [--config <path>] [--adw-id a1b2c3d4]
|
|
@@ -36,39 +36,32 @@
|
|
|
36
36
|
* The documenter measures against the commit this run STARTED from, not against
|
|
37
37
|
* `main`, because by then the run has moved `main` itself. That baseline is
|
|
38
38
|
* pinned before the first commit phase and printed in the request phase.
|
|
39
|
+
*
|
|
40
|
+
* This is the one chain still shaped by hand rather than by a `steps` list —
|
|
41
|
+
* three commits, a pinned baseline, and a conditional retest don't collapse
|
|
42
|
+
* cleanly into the flat, loop-free vocabulary `./steps.ts` provides for every
|
|
43
|
+
* other chain. It still reuses that module's shared helpers (`startRun`,
|
|
44
|
+
* `commitEnvelope`, `logChangeset`) rather than keeping its own copies.
|
|
39
45
|
*/
|
|
40
|
-
import * as agents from "../core/agents.js";
|
|
41
46
|
import * as changes from "../core/changes.js";
|
|
42
47
|
import * as gates from "../core/gates.js";
|
|
43
48
|
import * as quality from "../core/quality.js";
|
|
44
|
-
import
|
|
49
|
+
import { DOCUMENT_NOTES } from "../core/prompts.js";
|
|
50
|
+
import { commitEnvelope, logChangeset, startRun } from "./steps.js";
|
|
45
51
|
import { BuildOutput, DocumentOutput, PlanOutput, ReviewOutput, makeAgentCall, makeChangeCapture, makePhaseParams, } from "../core/data_types.js";
|
|
46
52
|
export const REQUIRED_AGENTS = ["planner", "builder", "reviewer", "documenter"];
|
|
47
53
|
export const REQUIRED_SUITES = ["test"];
|
|
48
54
|
const MAX_FIX_LOOPS = 3;
|
|
49
55
|
const MAX_REVISION_LOOPS = 2;
|
|
50
|
-
const DOCUMENT_NOTES = "Read diff_path in full before writing. Document only what the diff shows, then copy the write-up into app_docs/ as your task describes.";
|
|
51
|
-
/** Commit what the preceding phase produced, in that agent's own words. */
|
|
52
|
-
function commit(run, ph, envelope) {
|
|
53
|
-
const message = envelope.commit_message || `spf(${run.adw_id}): ${envelope.summary}`;
|
|
54
|
-
ph.log({ sha: run.git.commitAll(message), message });
|
|
55
|
-
}
|
|
56
|
-
/** Log a deterministic block's verdict — the same shape every ADW uses. */
|
|
57
|
-
function record(ph, result) {
|
|
58
|
-
const passed = result.checks.filter((c) => c.passed).length;
|
|
59
|
-
ph.log({ passed: result.passed, checks: `${passed}/${result.checks.length}`, artifacts: result.artifacts.join(", ") });
|
|
60
|
-
}
|
|
61
56
|
export async function main(ctx) {
|
|
62
|
-
const { prompt
|
|
63
|
-
const
|
|
64
|
-
agents.validate(cfg, REQUIRED_AGENTS, REQUIRED_SUITES, cwd);
|
|
65
|
-
const run = session.ensure(cfg, adw_id, cwd);
|
|
57
|
+
const { prompt } = ctx;
|
|
58
|
+
const run = startRun(ctx, REQUIRED_AGENTS, REQUIRED_SUITES);
|
|
66
59
|
const baseline = run.git.rev("HEAD"); // pinned before this run commits anything
|
|
67
60
|
await run.phase(makePhaseParams({ name: "request", kind: "engineer", owner: run.engineer, description: "Capture the incoming ask" }), async (ph) => {
|
|
68
61
|
ph.log({ input: prompt, baseline: run.git.shortSha(baseline) });
|
|
69
62
|
});
|
|
70
63
|
const plan = await run.phase(makePhaseParams({ name: "plan", kind: "agent", owner: "planner", description: "Turn the request into an implementable plan" }), (ph) => ph.call(makeAgentCall({ output_type: PlanOutput, prompt, gates: [gates.artifactsExist, gates.filesNonEmpty] })));
|
|
71
|
-
await run.phase(makePhaseParams({ name: "commit_plan", kind: "code", owner: "git", description: "Put the spec on record before any code exists to blur it" }), async (ph) =>
|
|
64
|
+
await run.phase(makePhaseParams({ name: "commit_plan", kind: "code", owner: "git", description: "Put the spec on record before any code exists to blur it" }), async (ph) => commitEnvelope(run, ph, plan));
|
|
72
65
|
let build = await run.phase(makePhaseParams({ name: "build", kind: "agent", owner: "builder", description: "Implement the plan exactly" }), (ph) => ph.call(makeAgentCall({ output_type: BuildOutput, prompt, previous: plan, gates: [gates.diffMatchesClaims] })));
|
|
73
66
|
let test = null;
|
|
74
67
|
for (let i = 1; i <= MAX_FIX_LOOPS; i++) {
|
|
@@ -79,11 +72,13 @@ export async function main(ctx) {
|
|
|
79
72
|
description: "Run the suite — a known command, so code runs it and no agent has to rediscover it",
|
|
80
73
|
}), async (ph) => {
|
|
81
74
|
const result = quality.runTests(run);
|
|
82
|
-
record(ph, result);
|
|
75
|
+
quality.record(ph, result);
|
|
83
76
|
return result;
|
|
84
77
|
});
|
|
85
78
|
if (test.passed)
|
|
86
79
|
break;
|
|
80
|
+
if (i === MAX_FIX_LOOPS)
|
|
81
|
+
break; // never leave an unverified fix on the table
|
|
87
82
|
build = await run.phase(makePhaseParams({
|
|
88
83
|
name: `fix_${i}`,
|
|
89
84
|
kind: "agent",
|
|
@@ -111,7 +106,7 @@ export async function main(ctx) {
|
|
|
111
106
|
description: "Re-run the suite — the revision changed code after the last green result",
|
|
112
107
|
}), async (ph) => {
|
|
113
108
|
const result = quality.runTests(run);
|
|
114
|
-
record(ph, result);
|
|
109
|
+
quality.record(ph, result);
|
|
115
110
|
return result;
|
|
116
111
|
});
|
|
117
112
|
}
|
|
@@ -120,16 +115,10 @@ export async function main(ctx) {
|
|
|
120
115
|
// describing yet. The plan commit stands — it is a record of what was asked.
|
|
121
116
|
const verified = test !== null && test.passed && review !== null && review.approved;
|
|
122
117
|
if (verified) {
|
|
123
|
-
await run.phase(makePhaseParams({ name: "commit_build", kind: "code", owner: "git", description: "Land the code only now: green suite, approved review" }), async (ph) =>
|
|
118
|
+
await run.phase(makePhaseParams({ name: "commit_build", kind: "code", owner: "git", description: "Land the code only now: green suite, approved review" }), async (ph) => commitEnvelope(run, ph, build));
|
|
124
119
|
const changeset = await run.phase(makePhaseParams({ name: "changes", kind: "code", owner: "git", description: "Diff the whole run against its pinned baseline, for the documenter" }), async (ph) => {
|
|
125
120
|
const result = changes.capture(run, makeChangeCapture({ base: baseline }));
|
|
126
|
-
ph
|
|
127
|
-
base: `${result.base.label} @ ${result.base.commit.slice(0, 7)}`,
|
|
128
|
-
reason: result.base.reason,
|
|
129
|
-
files: result.files.length + result.untracked.length,
|
|
130
|
-
lines: `+${result.insertions} -${result.deletions}`,
|
|
131
|
-
diff: result.diff_path,
|
|
132
|
-
});
|
|
121
|
+
logChangeset(ph, result);
|
|
133
122
|
if (result.empty) {
|
|
134
123
|
throw new Error(`nothing changed since ${result.base.label} (${result.base.reason}) — there is nothing to document.`);
|
|
135
124
|
}
|
|
@@ -141,7 +130,7 @@ export async function main(ctx) {
|
|
|
141
130
|
previous: changes.asEnvelope(changeset, DOCUMENT_NOTES),
|
|
142
131
|
gates: [gates.artifactsExist, gates.filesNonEmpty],
|
|
143
132
|
})));
|
|
144
|
-
await run.phase(makePhaseParams({ name: "commit_docs", kind: "code", owner: "git", description: "Ship the write-up in its own commit, beside the code it describes" }), async (ph) =>
|
|
133
|
+
await run.phase(makePhaseParams({ name: "commit_docs", kind: "code", owner: "git", description: "Ship the write-up in its own commit, beside the code it describes" }), async (ph) => commitEnvelope(run, ph, document));
|
|
145
134
|
}
|
|
146
135
|
return run.finish(verified, "the suite or the review never came back clean");
|
|
147
136
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step primitives: named, reusable pieces of chain composition.
|
|
3
|
+
*
|
|
4
|
+
* Every chain used to be a hand-written module repeating the same prologue,
|
|
5
|
+
* the same `request` phase, and — where chains overlapped — byte-identical
|
|
6
|
+
* `plan`/`build`/test-fix/review-revise/commit phases. This file is that
|
|
7
|
+
* code, extracted once: a `Step` is one `run.phase(...)` call (or a small
|
|
8
|
+
* bounded group of them) lifted out verbatim and parameterized only where
|
|
9
|
+
* the chains genuinely differed.
|
|
10
|
+
*
|
|
11
|
+
* A Step reads and writes a shared `ChainState` — the local variables a
|
|
12
|
+
* hand-written chain would otherwise thread from one `run.phase` call to the
|
|
13
|
+
* next (the last envelope, the last quality result, whether the run is
|
|
14
|
+
* accepted). Composing a chain is then an ordered array of Steps.
|
|
15
|
+
*
|
|
16
|
+
* The loops (`fixLoop`, `reviseLoop`) own their bounded iteration and dynamic
|
|
17
|
+
* phase naming (`test_1`, `fix_1`, ...) INTERNALLY — a chain's step list is
|
|
18
|
+
* therefore always flat, with no loop or conditional syntax at the
|
|
19
|
+
* composition layer. That is what would make a future declarative (YAML)
|
|
20
|
+
* chain tractable: it would only ever need to name steps and pass them
|
|
21
|
+
* tuning params, never express control flow.
|
|
22
|
+
*/
|
|
23
|
+
import { type EnvelopeBase, type QualityResult, type ReviewOutputT } from "../core/data_types.ts";
|
|
24
|
+
import type { ChangeSet } from "../core/data_types.ts";
|
|
25
|
+
import { Run, type PhaseHandle } from "../core/runner.ts";
|
|
26
|
+
import type { ChainContext } from "./context.ts";
|
|
27
|
+
/** What one step hands the next — the parts of a hand-written chain's local variables. */
|
|
28
|
+
export interface ChainState {
|
|
29
|
+
prompt: string;
|
|
30
|
+
options: Record<string, string>;
|
|
31
|
+
/** The last agent envelope produced — what the next agent step feeds forward as `previous`. */
|
|
32
|
+
previous: EnvelopeBase | null;
|
|
33
|
+
quality: QualityResult | null;
|
|
34
|
+
review: ReviewOutputT | null;
|
|
35
|
+
changeset: ChangeSet | null;
|
|
36
|
+
/** HEAD, pinned by request({logBaseline: true}) before the run commits anything. */
|
|
37
|
+
baseline: string;
|
|
38
|
+
/** The run's own acceptance criterion — distinct from "every phase succeeded"; see Run.finish(). */
|
|
39
|
+
accepted: boolean;
|
|
40
|
+
/** Why not, when accepted is false — passed straight to run.finish(). */
|
|
41
|
+
reason: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One phase (or a small bounded group of them). Callable like a plain
|
|
45
|
+
* function; the optional properties are how a step declares what it needs
|
|
46
|
+
* so `deriveRequiredAgents`/`deriveRequiredSuites`/`derivePhases` can build
|
|
47
|
+
* `ChainDefinition.requiredAgents`/`requiredSuites`/`phases` from a step list
|
|
48
|
+
* instead of those being a second, separately maintained set of exports.
|
|
49
|
+
*/
|
|
50
|
+
export interface Step {
|
|
51
|
+
(run: Run, state: ChainState): Promise<void>;
|
|
52
|
+
requiredAgents?: string[] | ((options: Record<string, string>) => string[]);
|
|
53
|
+
requiredSuites?: string[];
|
|
54
|
+
/** Display fragment for derivePhases() — e.g. "planner", "git(commit)". */
|
|
55
|
+
label?: string;
|
|
56
|
+
}
|
|
57
|
+
/** The identical loadConfig -> validate -> session.ensure prologue every chain repeated. */
|
|
58
|
+
export declare function startRun(ctx: ChainContext, requiredAgents: string[], requiredSuites: string[]): Run;
|
|
59
|
+
/** Commit an envelope in its own author's words — the message-fallback four chains repeated. */
|
|
60
|
+
export declare function commitEnvelope(run: Run, ph: PhaseHandle, envelope: EnvelopeBase & {
|
|
61
|
+
commit_message?: string;
|
|
62
|
+
}): void;
|
|
63
|
+
/** Log a change-capture result the same way every chain that captures one did. */
|
|
64
|
+
export declare function logChangeset(ph: PhaseHandle, result: ChangeSet): void;
|
|
65
|
+
/** The engineer(request) phase every chain opens with. */
|
|
66
|
+
export declare function request(opts?: {
|
|
67
|
+
description?: string;
|
|
68
|
+
logBaseline?: boolean;
|
|
69
|
+
}): Step;
|
|
70
|
+
export declare function plan(): Step;
|
|
71
|
+
/**
|
|
72
|
+
* `fromPlan` only changes the description — whether a plan() step precedes
|
|
73
|
+
* this one already decides whether `state.previous` is a plan or null, so
|
|
74
|
+
* there is nothing else for this flag to gate.
|
|
75
|
+
*/
|
|
76
|
+
export declare function build(opts?: {
|
|
77
|
+
fromPlan?: boolean;
|
|
78
|
+
retries?: number;
|
|
79
|
+
}): Step;
|
|
80
|
+
export declare function scout(): Step;
|
|
81
|
+
/** The `prompt` chain's one step: --agent picks who, at run time and at requiredAgents-derivation time alike. */
|
|
82
|
+
export declare function promptOnly(): Step;
|
|
83
|
+
/** One deterministic quality block, standalone — never throws; sets state.accepted for run.finish() to check. */
|
|
84
|
+
export declare function qualityCheck(opts?: {
|
|
85
|
+
suite: "test" | "all";
|
|
86
|
+
description?: string;
|
|
87
|
+
}): Step;
|
|
88
|
+
/**
|
|
89
|
+
* Bounded check -> fix loop: a known command finds the failure, the builder
|
|
90
|
+
* repairs it. Always guards the last iteration — a fix on the final attempt
|
|
91
|
+
* is never re-verified, so it is never spawned. Sets state.accepted/reason.
|
|
92
|
+
*/
|
|
93
|
+
export declare function fixLoop(opts?: {
|
|
94
|
+
suite: "test" | "all";
|
|
95
|
+
max?: number;
|
|
96
|
+
owner?: string;
|
|
97
|
+
}): Step;
|
|
98
|
+
/** Bounded review -> revise loop. Sets state.accepted/reason from the final verdict. */
|
|
99
|
+
export declare function reviseLoop(opts?: {
|
|
100
|
+
max?: number;
|
|
101
|
+
}): Step;
|
|
102
|
+
/** Commit the last agent step's envelope. `onlyIfAccepted` gates it on state.accepted (a preceding fixLoop/reviseLoop). */
|
|
103
|
+
export declare function commit(opts?: {
|
|
104
|
+
onlyIfAccepted?: boolean;
|
|
105
|
+
}): Step;
|
|
106
|
+
/** Diff the working tree against a base ref — code, not judgement. `--base` (default "main") if opts.base is unset. */
|
|
107
|
+
export declare function changes(opts?: {
|
|
108
|
+
base?: string;
|
|
109
|
+
}): Step;
|
|
110
|
+
/** Write up the captured change. Requires a preceding changes() step. */
|
|
111
|
+
export declare function document(): Step;
|
|
112
|
+
export declare function deriveRequiredAgents(steps: Step[]): string[] | ((options: Record<string, string>) => string[]);
|
|
113
|
+
export declare function deriveRequiredSuites(steps: Step[]): string[];
|
|
114
|
+
/** A display string for `spf list` — derived so it can no longer drift from what actually runs. */
|
|
115
|
+
export declare function derivePhases(steps: Step[]): string;
|
|
116
|
+
/** Run a chain's step list start to finish: prologue, every step in order, then run.finish(). */
|
|
117
|
+
export declare function runSteps(ctx: ChainContext, requiredAgents: string[], requiredSuites: string[], steps: Step[], options?: Record<string, string>): Promise<number>;
|