@bluewombat/slots 0.8.0 → 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.
- package/README.md +190 -122
- package/agents/agent-plugins.mjs +52 -14
- package/agents/claude.mjs +3 -3
- package/agents/cursor.mjs +1 -1
- package/builders/e2e.mjs +124 -0
- package/builders/producer.mjs +6 -6
- package/builders/repair.mjs +4 -3
- package/builders/tests.mjs +139 -0
- package/feature/fix.mjs +134 -0
- package/feature/validate.mjs +205 -0
- package/gates/ci-green.mjs +17 -6
- package/gates/e2e.mjs +538 -0
- package/gates/gitignore-leak.mjs +10 -9
- package/gates/project-checks.mjs +128 -0
- package/gates/tests-frozen.mjs +96 -0
- package/gates/tests-red.mjs +227 -0
- package/gates/workspace-changed.mjs +2 -1
- package/gates/workspace-unchanged.mjs +90 -0
- package/lib/fill.mjs +0 -6
- package/lib/junit.mjs +122 -0
- package/lib/output-tail.mjs +70 -0
- package/messages/conventional.mjs +1 -1
- package/messages/git-producer.mjs +2 -2
- package/package.json +9 -10
- package/publishers/git.mjs +1 -1
- package/refreshers/git.mjs +52 -2
- package/{assembly → ticket}/fix.mjs +5 -5
- package/{assembly → ticket}/validate.mjs +11 -13
- package/planners/one-subtask.mjs +0 -21
- package/planners/producer.mjs +0 -233
package/agents/claude.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { extrasFromClaude } from "./claude-extras.mjs";
|
|
|
30
30
|
// --agent-home / --api-key-env recognised, then refused. Claude is not
|
|
31
31
|
// isolated yet; starting would keep the operator's config
|
|
32
32
|
// --plugin-dir DIR a plugin folder, repeatable (see agent-plugins.mjs)
|
|
33
|
-
// --profile / --plugins-out / --deck-bin recognised, then refused for the
|
|
33
|
+
// --app-deck-profile / --plugins-out / --deck-bin recognised, then refused for the
|
|
34
34
|
// same reason: Claude would also load the operator's
|
|
35
35
|
// and the project's own plugins, so a role's profile
|
|
36
36
|
// would not be what it gets
|
|
@@ -64,12 +64,12 @@ if (
|
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
if (
|
|
67
|
-
textOption(options, "
|
|
67
|
+
textOption(options, "appDeckProfile") !== undefined ||
|
|
68
68
|
textOption(options, "pluginsOut") !== undefined ||
|
|
69
69
|
textOption(options, "deckBin") !== undefined
|
|
70
70
|
) {
|
|
71
71
|
refuse(
|
|
72
|
-
"Claude Code does not isolate the operator home yet, so a deck profile would not be all it loads. Remove --profile; the vendor was not started.",
|
|
72
|
+
"Claude Code does not isolate the operator home yet, so a deck profile would not be all it loads. Remove --app-deck-profile; the vendor was not started.",
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
|
package/agents/cursor.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import { extrasFromCursor } from "./cursor-extras.mjs";
|
|
|
36
36
|
// --deny-shell NAME with --agent-home: also refuse this command to the
|
|
37
37
|
// vendor (`gh`, `vercel`…). Repeatable. ssh and scp are
|
|
38
38
|
// always refused
|
|
39
|
-
// --plugin-dir / --profile / --plugins-out / --deck-bin plugin folders,
|
|
39
|
+
// --plugin-dir / --app-deck-profile / --plugins-out / --deck-bin plugin folders,
|
|
40
40
|
// see agent-plugins.mjs
|
|
41
41
|
// --id / --attempt / --context how a transcript is filed
|
|
42
42
|
|
package/builders/e2e.mjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BUILDER_FLAGS,
|
|
4
|
+
emitFailure,
|
|
5
|
+
fillPrompt,
|
|
6
|
+
finishRun,
|
|
7
|
+
loadPrompt,
|
|
8
|
+
loadRules,
|
|
9
|
+
PROMPT_RULES,
|
|
10
|
+
parseRole,
|
|
11
|
+
spawnFilled,
|
|
12
|
+
take,
|
|
13
|
+
textOption,
|
|
14
|
+
transcriptArgs,
|
|
15
|
+
} from "@bluewombat/slot-kit";
|
|
16
|
+
import { attemptOf } from "../lib/fill.mjs";
|
|
17
|
+
|
|
18
|
+
// The Ticket that holds a Feature's e2e acceptance suite. One command for its first pass and for
|
|
19
|
+
// a pass a Gate refused: the suite is written once from the scenarios of the task, and a refusal
|
|
20
|
+
// is about its form, so there is no second command to tell apart. `{{report}}` and
|
|
21
|
+
// `{{refused_by}}` are empty on a first pass; `{{refusal}}` is a ready-made paragraph about them.
|
|
22
|
+
|
|
23
|
+
const NAME = "builder e2e";
|
|
24
|
+
|
|
25
|
+
const DEFAULT_TEMPLATE = `You are a coding agent working unattended in a checkout of the project.
|
|
26
|
+
|
|
27
|
+
You write the acceptance suite of a feature. The task below lists its scenarios, agreed with the
|
|
28
|
+
operator before development, each with an id (E1, E2, ...).
|
|
29
|
+
|
|
30
|
+
Task:
|
|
31
|
+
{{task}}
|
|
32
|
+
|
|
33
|
+
- Write one test per scenario, in the end-to-end framework this project already uses, and
|
|
34
|
+
start the name of each test with its scenario id ("E1 …"). Follow the scenario's steps and
|
|
35
|
+
expected result as written; do not add, merge or drop one.
|
|
36
|
+
- List the ids in the manifest \`e2e/scenarios.json\`, a JSON array: ["E1", "E2"].
|
|
37
|
+
- Write only inside the suite folder (\`e2e/\`). Touch no file of the application, and add no
|
|
38
|
+
dependency it does not already have.
|
|
39
|
+
- The feature is not finished here and the application is not started: do not run the suite. Check
|
|
40
|
+
only that it compiles, with the project's own typecheck or build command.
|
|
41
|
+
|
|
42
|
+
{{refusal}}
|
|
43
|
+
|
|
44
|
+
{{history}}
|
|
45
|
+
|
|
46
|
+
## When you cannot act
|
|
47
|
+
|
|
48
|
+
If what you were given is not enough to act on (a scenario is ambiguous, the framework is missing,
|
|
49
|
+
the cause is outside this directory), do not invent a change. Change nothing and end your final
|
|
50
|
+
message with exactly one line:
|
|
51
|
+
|
|
52
|
+
MASON_BLOCKED: <what is missing, in one line>
|
|
53
|
+
|
|
54
|
+
A person reads that line and decides. It is a correct answer, not a failure.
|
|
55
|
+
|
|
56
|
+
Rules of this run:
|
|
57
|
+
{{rules}}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const intention = (take(process.argv, "--intention") ?? "").trim();
|
|
61
|
+
if (intention.length === 0) {
|
|
62
|
+
emitFailure("fail-blocking", "The Builder received no --intention.");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const parsed = parseRole(process.argv, BUILDER_FLAGS, NAME);
|
|
67
|
+
if (!parsed.ok) {
|
|
68
|
+
emitFailure("fail-blocking", parsed.reason);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const template = loadPrompt(parsed.options, DEFAULT_TEMPLATE);
|
|
73
|
+
if (!template.ok) {
|
|
74
|
+
emitFailure("fail-blocking", template.reason);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const rules = loadRules(parsed.options, PROMPT_RULES);
|
|
78
|
+
if (!rules.ok) {
|
|
79
|
+
emitFailure("fail-blocking", rules.reason);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const report = (take(process.argv, "--report") ?? "").trim();
|
|
84
|
+
const refusedBy = (take(process.argv, "--report-from") ?? "").trim();
|
|
85
|
+
const refusal =
|
|
86
|
+
report.length === 0
|
|
87
|
+
? ""
|
|
88
|
+
: [
|
|
89
|
+
"What is in this directory is what an earlier attempt left. It was refused",
|
|
90
|
+
`${refusedBy.length > 0 ? `by ${refusedBy}` : "by a check"}:`,
|
|
91
|
+
"",
|
|
92
|
+
"```raw",
|
|
93
|
+
report,
|
|
94
|
+
"```",
|
|
95
|
+
"",
|
|
96
|
+
"Resolve that, change what it takes and nothing more.",
|
|
97
|
+
].join("\n");
|
|
98
|
+
|
|
99
|
+
const rendered = fillPrompt(
|
|
100
|
+
template.value,
|
|
101
|
+
{
|
|
102
|
+
task: intention,
|
|
103
|
+
history: (take(process.argv, "--history") ?? "").trim(),
|
|
104
|
+
refusal,
|
|
105
|
+
report,
|
|
106
|
+
refused_by: refusedBy,
|
|
107
|
+
rules: rules.value.trim(),
|
|
108
|
+
id: take(process.argv, "--id") ?? "",
|
|
109
|
+
attempt: String(attemptOf(process.argv)),
|
|
110
|
+
},
|
|
111
|
+
["task", "rules"],
|
|
112
|
+
textOption(parsed.options, "promptFile") ?? "built-in template",
|
|
113
|
+
);
|
|
114
|
+
if (!rendered.ok) {
|
|
115
|
+
emitFailure("fail-blocking", rendered.reason);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const spawned = await spawnFilled(parsed.runner, rendered.prompt, transcriptArgs(process.argv));
|
|
120
|
+
if (!spawned.ok) {
|
|
121
|
+
emitFailure("fail-blocking", spawned.reason);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
finishRun({ run: spawned.run, bin: spawned.about.bin, name: spawned.about.name });
|
package/builders/producer.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
textOption,
|
|
14
14
|
transcriptArgs,
|
|
15
15
|
} from "@bluewombat/slot-kit";
|
|
16
|
-
import { attemptOf
|
|
16
|
+
import { attemptOf } from "../lib/fill.mjs";
|
|
17
17
|
|
|
18
18
|
const NAME = "builder producer";
|
|
19
19
|
|
|
@@ -22,11 +22,12 @@ const DEFAULT_TEMPLATE = `You are a coding agent working unattended in a checkou
|
|
|
22
22
|
Task:
|
|
23
23
|
{{task}}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
This checkout is the project as it stands. Add or change only what this task
|
|
26
|
+
names, and leave the project's own checks green on the whole tree.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
When the latest commit of this checkout is titled "Tests: …", the tests of this task were
|
|
29
|
+
written before the code and committed. They are the specification: make them pass as they
|
|
30
|
+
are. Do not edit, delete or rename them.
|
|
30
31
|
|
|
31
32
|
{{history}}
|
|
32
33
|
|
|
@@ -82,7 +83,6 @@ const rendered = fillPrompt(
|
|
|
82
83
|
{
|
|
83
84
|
task: intention,
|
|
84
85
|
history: (take(process.argv, "--history") ?? "").trim(),
|
|
85
|
-
done_when: doneWhen(take(process.argv, "--definition-of-done")),
|
|
86
86
|
rules: rules.value.trim(),
|
|
87
87
|
id: take(process.argv, "--id") ?? "",
|
|
88
88
|
attempt: String(attemptOf(process.argv)),
|
package/builders/repair.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
textOption,
|
|
14
14
|
transcriptArgs,
|
|
15
15
|
} from "@bluewombat/slot-kit";
|
|
16
|
-
import { attemptOf
|
|
16
|
+
import { attemptOf } from "../lib/fill.mjs";
|
|
17
17
|
|
|
18
18
|
const NAME = "builder repair";
|
|
19
19
|
|
|
@@ -38,7 +38,9 @@ This was asked in the first place, and still holds:
|
|
|
38
38
|
|
|
39
39
|
{{task}}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
When the latest commit of this checkout is titled "Tests: …", the tests of this task were
|
|
42
|
+
written before the code and committed. They are the specification: make them pass as they
|
|
43
|
+
are. Do not edit, delete or rename them.
|
|
42
44
|
|
|
43
45
|
{{history}}
|
|
44
46
|
|
|
@@ -96,7 +98,6 @@ const rendered = fillPrompt(
|
|
|
96
98
|
report,
|
|
97
99
|
refused_by:
|
|
98
100
|
refusedBy.length > 0 ? refusedBy : "the producer of the last attempt, which did not finish",
|
|
99
|
-
done_when: doneWhen(take(process.argv, "--definition-of-done")),
|
|
100
101
|
rules: rules.value.trim(),
|
|
101
102
|
id: take(process.argv, "--id") ?? "",
|
|
102
103
|
attempt: String(attemptOf(process.argv)),
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BUILDER_FLAGS,
|
|
4
|
+
emitFailure,
|
|
5
|
+
fillPrompt,
|
|
6
|
+
finishRun,
|
|
7
|
+
loadPrompt,
|
|
8
|
+
loadRules,
|
|
9
|
+
PROMPT_RULES,
|
|
10
|
+
parseRole,
|
|
11
|
+
spawnFilled,
|
|
12
|
+
take,
|
|
13
|
+
textOption,
|
|
14
|
+
transcriptArgs,
|
|
15
|
+
} from "@bluewombat/slot-kit";
|
|
16
|
+
import { attemptOf } from "../lib/fill.mjs";
|
|
17
|
+
|
|
18
|
+
const NAME = "builder tests";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TEMPLATE = `You are a coding agent working unattended in a checkout of the project.
|
|
21
|
+
|
|
22
|
+
Task:
|
|
23
|
+
{{task}}
|
|
24
|
+
|
|
25
|
+
YOUR WORK NOW IS TO WRITE THE TESTS OF THIS TASK, AND ONLY THE TESTS. Another agent
|
|
26
|
+
writes the code afterwards and must make your tests pass without editing them. A test it
|
|
27
|
+
cannot pass is not sent back to you: it stops the task and waits for a person. So a test
|
|
28
|
+
you deliver must be one that a correct implementation of the task passes, and you prove
|
|
29
|
+
it before you hand it over (see below).
|
|
30
|
+
|
|
31
|
+
## The cases
|
|
32
|
+
|
|
33
|
+
- The task has a "Tests" section: write one test per case of it, in the file and the
|
|
34
|
+
framework it names (or the ones this repository already uses), each named after the
|
|
35
|
+
case it covers. Test the behaviour at the seam the section names, not how it is built.
|
|
36
|
+
- The task has no "Tests" section: do not look for one elsewhere. Derive the cases from
|
|
37
|
+
its acceptance criteria ("Done when" or the like), one case per criterion, and name each
|
|
38
|
+
test after the criterion and the identifiers it carries. A criterion that describes two
|
|
39
|
+
sets of data (sixty rows for a page, more than a cap for the cap) is two cases, never one
|
|
40
|
+
test with two batches of data.
|
|
41
|
+
- Do not write the behaviour. If the interface under test does not exist yet, create the
|
|
42
|
+
minimal stub — the name and signature the task gives, a body that does nothing useful —
|
|
43
|
+
so that the tests compile and fail on what they check, not on a missing import.
|
|
44
|
+
- Add or update no other test, and no production code beyond the stubs.
|
|
45
|
+
|
|
46
|
+
## How a test stays passable
|
|
47
|
+
|
|
48
|
+
- One data set per test, seeded at the top. Every expectation derives from that set: the
|
|
49
|
+
rows a filter keeps, the order they take, the page a limit and an offset cut out of it.
|
|
50
|
+
Write the expected count from the seeded data, not as a figure recounted by hand.
|
|
51
|
+
- To observe a bound, seed past it: a cap of 200 is only visible with more than 200 rows
|
|
52
|
+
that the filters keep. "At most N" is asserted with \`<= N\`, or by seeding more than N;
|
|
53
|
+
never as exactly N when fewer exist.
|
|
54
|
+
|
|
55
|
+
## Prove them: red, green, red
|
|
56
|
+
|
|
57
|
+
A test you have only seen fail proves nothing about itself: a test no implementation
|
|
58
|
+
could pass fails exactly the same way. The tests must FAIL now, because the behaviour is
|
|
59
|
+
missing, and you prove every one of them passable before you finish:
|
|
60
|
+
|
|
61
|
+
1. **Red.** Run the tests. Each fails on an assertion (not on an import, a type, a syntax
|
|
62
|
+
error).
|
|
63
|
+
2. **Green.** In the stub bodies — the same files, no new file — write the shortest
|
|
64
|
+
implementation of the task that makes every test pass: naive, in memory, unoptimised;
|
|
65
|
+
it is thrown away. Run the tests. A test still red here is wrong, not the task: fix its
|
|
66
|
+
data or its expectation (never drop a case, never replace a value the task gives with a
|
|
67
|
+
looser one), and run again until all are green.
|
|
68
|
+
3. **Red again.** Put every stub body back to no behaviour. Run the tests: each fails
|
|
69
|
+
again on an assertion. Leave the tree in this state.
|
|
70
|
+
|
|
71
|
+
Leave no trace of the green step: no helper, no import, no comment. The only non-test
|
|
72
|
+
lines you deliver are the stubs.
|
|
73
|
+
|
|
74
|
+
{{history}}
|
|
75
|
+
|
|
76
|
+
## When you cannot act
|
|
77
|
+
|
|
78
|
+
If the cases are missing, or contradict the task or the code, do not invent them. Change
|
|
79
|
+
nothing and end your final message with exactly one line:
|
|
80
|
+
|
|
81
|
+
MASON_BLOCKED: <what is missing, in one line>
|
|
82
|
+
|
|
83
|
+
A person reads that line and decides. It is a correct answer, not a failure.
|
|
84
|
+
|
|
85
|
+
Rules of this run:
|
|
86
|
+
{{rules}}
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
// A retry carries the refusal in --history; the flags themselves are consumed so the
|
|
90
|
+
// role options see only their own.
|
|
91
|
+
take(process.argv, "--report");
|
|
92
|
+
take(process.argv, "--report-from");
|
|
93
|
+
|
|
94
|
+
const intention = (take(process.argv, "--intention") ?? "").trim();
|
|
95
|
+
if (intention.length === 0) {
|
|
96
|
+
emitFailure("fail-blocking", "The Builder received no --intention.");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const parsed = parseRole(process.argv, BUILDER_FLAGS, NAME);
|
|
101
|
+
if (!parsed.ok) {
|
|
102
|
+
emitFailure("fail-blocking", parsed.reason);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const template = loadPrompt(parsed.options, DEFAULT_TEMPLATE);
|
|
107
|
+
if (!template.ok) {
|
|
108
|
+
emitFailure("fail-blocking", template.reason);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const rules = loadRules(parsed.options, PROMPT_RULES);
|
|
112
|
+
if (!rules.ok) {
|
|
113
|
+
emitFailure("fail-blocking", rules.reason);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const rendered = fillPrompt(
|
|
118
|
+
template.value,
|
|
119
|
+
{
|
|
120
|
+
task: intention,
|
|
121
|
+
history: (take(process.argv, "--history") ?? "").trim(),
|
|
122
|
+
rules: rules.value.trim(),
|
|
123
|
+
id: take(process.argv, "--id") ?? "",
|
|
124
|
+
attempt: String(attemptOf(process.argv)),
|
|
125
|
+
},
|
|
126
|
+
["task", "rules"],
|
|
127
|
+
textOption(parsed.options, "promptFile") ?? "built-in template",
|
|
128
|
+
);
|
|
129
|
+
if (!rendered.ok) {
|
|
130
|
+
emitFailure("fail-blocking", rendered.reason);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const spawned = await spawnFilled(parsed.runner, rendered.prompt, transcriptArgs(process.argv));
|
|
135
|
+
if (!spawned.ok) {
|
|
136
|
+
emitFailure("fail-blocking", spawned.reason);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
finishRun({ run: spawned.run, bin: spawned.about.bin, name: spawned.about.name });
|
package/feature/fix.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BUILDER_FLAGS,
|
|
4
|
+
emitFailure,
|
|
5
|
+
fillPrompt,
|
|
6
|
+
finishRun,
|
|
7
|
+
loadPrompt,
|
|
8
|
+
loadRules,
|
|
9
|
+
PROMPT_RULES,
|
|
10
|
+
parseRole,
|
|
11
|
+
spawnFilled,
|
|
12
|
+
take,
|
|
13
|
+
textOption,
|
|
14
|
+
transcriptArgs,
|
|
15
|
+
} from "@bluewombat/slot-kit";
|
|
16
|
+
import { attemptOf } from "../lib/fill.mjs";
|
|
17
|
+
|
|
18
|
+
const NAME = "feature fix";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TEMPLATE = `You are a coding agent working unattended in a checkout of the project.
|
|
21
|
+
|
|
22
|
+
This directory is a whole feature: the work of all its tickets, already
|
|
23
|
+
assembled and checked ticket by ticket. It was then brought up to date with the
|
|
24
|
+
branch it will be merged into, and checked again as a whole. Something refused
|
|
25
|
+
it after that: a merge conflict while bringing it up to date, a check that fails
|
|
26
|
+
on the result, or the project's CI. Do not redo the feature, or any ticket of
|
|
27
|
+
it. Fix what the refusal names.
|
|
28
|
+
|
|
29
|
+
Refused by: {{refused_by}}
|
|
30
|
+
|
|
31
|
+
What it said:
|
|
32
|
+
|
|
33
|
+
\`\`\`raw
|
|
34
|
+
{{report}}
|
|
35
|
+
\`\`\`
|
|
36
|
+
|
|
37
|
+
YOUR WORK NOW IS TO RESOLVE THAT. Change what it takes and nothing more; what is
|
|
38
|
+
already here and passes is not to be redone.
|
|
39
|
+
|
|
40
|
+
## According to who refused
|
|
41
|
+
|
|
42
|
+
- **A merge conflict** (refused by \`align\`): the report names the branch the
|
|
43
|
+
feature is merged into. Look at what that branch changed since this feature
|
|
44
|
+
started (compare the two with git), then edit the files in conflict so that
|
|
45
|
+
both sets of changes hold: what the branch added stays, and the feature still
|
|
46
|
+
does what it asks. Do not drop the branch's changes to keep yours. Leave the
|
|
47
|
+
result uncommitted.
|
|
48
|
+
- **A check** (a gate named by its id, such as the typecheck or the tests): run
|
|
49
|
+
the failing command here the way the gate ran it, see the error, fix the
|
|
50
|
+
cause. The error often comes from what the target branch brought in, or from
|
|
51
|
+
two tickets that were each fine alone: adapt the feature's code to it.
|
|
52
|
+
- **The CI**: reproduce the failing job's command here, then fix it. Change the
|
|
53
|
+
work, not the CI definition.
|
|
54
|
+
|
|
55
|
+
## Original request
|
|
56
|
+
|
|
57
|
+
This was asked in the first place, and still holds:
|
|
58
|
+
|
|
59
|
+
{{task}}
|
|
60
|
+
|
|
61
|
+
{{history}}
|
|
62
|
+
|
|
63
|
+
## When you cannot act
|
|
64
|
+
|
|
65
|
+
If what you were given is not enough to act on (the refusal names nothing you
|
|
66
|
+
can read or run here, the request contradicts the rules of this run, or the
|
|
67
|
+
cause is outside this directory), do not invent a change. Change nothing and
|
|
68
|
+
end your final message with exactly one line:
|
|
69
|
+
|
|
70
|
+
MASON_BLOCKED: <what is missing, in one line>
|
|
71
|
+
|
|
72
|
+
A person reads that line and decides. It is a correct answer, not a failure.
|
|
73
|
+
|
|
74
|
+
## Rules of this run
|
|
75
|
+
|
|
76
|
+
{{rules}}
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const report = (take(process.argv, "--report") ?? "").trim();
|
|
80
|
+
if (report.length === 0) {
|
|
81
|
+
emitFailure("fail-blocking", "The feature fix command needs --report.");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const intention = (take(process.argv, "--intention") ?? "").trim();
|
|
86
|
+
if (intention.length === 0) {
|
|
87
|
+
emitFailure("fail-blocking", "The Builder received no --intention.");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parsed = parseRole(process.argv, BUILDER_FLAGS, NAME);
|
|
92
|
+
if (!parsed.ok) {
|
|
93
|
+
emitFailure("fail-blocking", parsed.reason);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const template = loadPrompt(parsed.options, DEFAULT_TEMPLATE);
|
|
98
|
+
if (!template.ok) {
|
|
99
|
+
emitFailure("fail-blocking", template.reason);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const rules = loadRules(parsed.options, PROMPT_RULES);
|
|
103
|
+
if (!rules.ok) {
|
|
104
|
+
emitFailure("fail-blocking", rules.reason);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const refusedBy = (take(process.argv, "--report-from") ?? "").trim();
|
|
109
|
+
const rendered = fillPrompt(
|
|
110
|
+
template.value,
|
|
111
|
+
{
|
|
112
|
+
task: intention,
|
|
113
|
+
history: (take(process.argv, "--history") ?? "").trim(),
|
|
114
|
+
report,
|
|
115
|
+
refused_by:
|
|
116
|
+
refusedBy.length > 0 ? refusedBy : "the producer of the last attempt, which did not finish",
|
|
117
|
+
rules: rules.value.trim(),
|
|
118
|
+
id: take(process.argv, "--id") ?? "",
|
|
119
|
+
attempt: String(attemptOf(process.argv)),
|
|
120
|
+
},
|
|
121
|
+
["task", "report", "rules"],
|
|
122
|
+
textOption(parsed.options, "promptFile") ?? "built-in template",
|
|
123
|
+
);
|
|
124
|
+
if (!rendered.ok) {
|
|
125
|
+
emitFailure("fail-blocking", rendered.reason);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const spawned = await spawnFilled(parsed.runner, rendered.prompt, transcriptArgs(process.argv));
|
|
130
|
+
if (!spawned.ok) {
|
|
131
|
+
emitFailure("fail-blocking", spawned.reason);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
finishRun({ run: spawned.run, bin: spawned.about.bin, name: spawned.about.name });
|