@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21
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/CHANGELOG.md +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- package/engine/jev/jev-client.ts +0 -173
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine config's shape: the schema of `.luca/config.json` and the
|
|
3
|
+
* pieces it is built from (test results, the `muninn` and `decision_model`
|
|
4
|
+
* blocks, and the `models` block with its floors).
|
|
5
|
+
*/
|
|
6
|
+
import { isAbsolute, normalize } from 'node:path'
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_DECISION_MODEL,
|
|
12
|
+
DecisionModelIdSchema,
|
|
13
|
+
} from '../jev/jev-schemas'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The engine config file, relative to the root of the repo a run works on.
|
|
17
|
+
* `muninn.vault` sits at the same path old Luca used, because memory tooling
|
|
18
|
+
* outside the engine reads it from there.
|
|
19
|
+
*/
|
|
20
|
+
export const ENGINE_CONFIG_FILE = '.luca/config.json'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* How the engine reads a test command's results: `bun` for bun's per-test
|
|
24
|
+
* results (the red check and baseline use these), `pass_fail` for just its
|
|
25
|
+
* exit code.
|
|
26
|
+
*/
|
|
27
|
+
export const TestResultsSchema = z.enum(['bun', 'pass_fail'])
|
|
28
|
+
|
|
29
|
+
export type TestResults = z.infer<typeof TestResultsSchema>
|
|
30
|
+
|
|
31
|
+
/** One entry of a `checks.test` list: a command, or a command and its kind. */
|
|
32
|
+
const TestCommandEntrySchema = z.union([
|
|
33
|
+
z.string().min(1),
|
|
34
|
+
z.object({
|
|
35
|
+
run: z.string().min(1),
|
|
36
|
+
results: TestResultsSchema.optional(),
|
|
37
|
+
}),
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
/** A test command setting: one command, or a list of them (see `testCommands`). */
|
|
41
|
+
const TestCommandsSchema = z.union([
|
|
42
|
+
z.string().min(1),
|
|
43
|
+
z.array(TestCommandEntrySchema),
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
/** The config's `muninn` block. */
|
|
47
|
+
export const MuninnConfigSchema = z.object({
|
|
48
|
+
/** The project's memory vault. */
|
|
49
|
+
vault: z.string().min(1),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The config's `decision_model` block (#534): the Clef model shadow mode
|
|
54
|
+
* asks, `@cf/cloudflare/clef` (the accurate one, the default) or
|
|
55
|
+
* `@cf/cloudflare/clef-flash` (the fast one).
|
|
56
|
+
*/
|
|
57
|
+
export const DecisionModelConfigSchema = z.object({
|
|
58
|
+
model: DecisionModelIdSchema.default(DEFAULT_DECISION_MODEL),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The models an agent may launch on, by short name, strongest first: the
|
|
63
|
+
* one list the launcher, the floors, and the decision model's options read.
|
|
64
|
+
* Claude Code turns each into its newest version; the session's init names
|
|
65
|
+
* the exact one.
|
|
66
|
+
*/
|
|
67
|
+
export const ModelNameSchema = z.enum(['opus', 'sonnet', 'haiku'])
|
|
68
|
+
|
|
69
|
+
export type ModelName = z.infer<typeof ModelNameSchema>
|
|
70
|
+
|
|
71
|
+
/** The model reviewers run on, and every fallback of the model pick (#603). */
|
|
72
|
+
export const DEFAULT_MODEL: ModelName = ModelNameSchema.enum.opus
|
|
73
|
+
|
|
74
|
+
/** The roles with a model floor (#603); every other role always runs Opus. */
|
|
75
|
+
export const ROUTED_ROLES = ['test-writer', 'implementer', 'learner'] as const
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The cheapest model each routed role may run on; a role left out is
|
|
79
|
+
* `opus`. A floor for any other role, a reviewer or a lens, is refused.
|
|
80
|
+
*/
|
|
81
|
+
const ModelFloorsSchema = z.strictObject(
|
|
82
|
+
{
|
|
83
|
+
'test-writer': ModelNameSchema.default(DEFAULT_MODEL),
|
|
84
|
+
implementer: ModelNameSchema.default(DEFAULT_MODEL),
|
|
85
|
+
learner: ModelNameSchema.default(DEFAULT_MODEL),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
error: (issue) =>
|
|
89
|
+
issue.code === 'unrecognized_keys'
|
|
90
|
+
? `${issue.keys.join(', ')} can't have a model floor: reviewers always run Opus. Only ${ROUTED_ROLES.join(', ')} have floors.`
|
|
91
|
+
: undefined,
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The config's `models` block (#603): each routed role's model floor, and
|
|
97
|
+
* how sure the decision model must be (`confidence_bar`) before its model
|
|
98
|
+
* pick is used.
|
|
99
|
+
*/
|
|
100
|
+
export const ModelsConfigSchema = z.object({
|
|
101
|
+
floors: ModelFloorsSchema.prefault({}),
|
|
102
|
+
confidence_bar: z.number().min(0).max(1).default(0.8),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
export type ModelsConfig = z.infer<typeof ModelsConfigSchema>
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether a config path names a file inside the repo (#583): not absolute,
|
|
109
|
+
* not ending in `/` (a folder), and, once cleaned (`normalize`), neither the
|
|
110
|
+
* repo root nor a path that climbs out of it with `..`.
|
|
111
|
+
*/
|
|
112
|
+
const isFileInsideRepo = (path: string): boolean => {
|
|
113
|
+
const clean = normalize(path)
|
|
114
|
+
return (
|
|
115
|
+
!isAbsolute(path) &&
|
|
116
|
+
!path.endsWith('/') &&
|
|
117
|
+
clean !== '.' &&
|
|
118
|
+
clean !== '..' &&
|
|
119
|
+
!clean.startsWith('../')
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The per-repo engine config: the gate commands, where tests live, the rule
|
|
125
|
+
* files for the rules lens, the project's memory vault, the decision model,
|
|
126
|
+
* the model floors, and the run budget.
|
|
127
|
+
*
|
|
128
|
+
* Unknown keys, such as old Luca's, are dropped when the file is read.
|
|
129
|
+
*
|
|
130
|
+
* The test command is optional here on purpose. A config without it still
|
|
131
|
+
* loads, and intake refuses the run with a clear message instead.
|
|
132
|
+
* `checks.test` is one command or a list of them (see `testCommands`); it is
|
|
133
|
+
* kept as written. `checks.slow_test` has the same shape (see
|
|
134
|
+
* `slowTestCommands`, #563).
|
|
135
|
+
*/
|
|
136
|
+
export const EngineConfigSchema = z.object({
|
|
137
|
+
checks: z
|
|
138
|
+
.object({
|
|
139
|
+
test: TestCommandsSchema.optional(),
|
|
140
|
+
/**
|
|
141
|
+
* The slow tests (#563): the tests `test` leaves out, run only at
|
|
142
|
+
* the final review. Same shape as `test`. Left out, nothing
|
|
143
|
+
* changes.
|
|
144
|
+
*/
|
|
145
|
+
slow_test: TestCommandsSchema.optional(),
|
|
146
|
+
types: z.string().min(1).optional(),
|
|
147
|
+
lint: z.string().min(1).optional(),
|
|
148
|
+
})
|
|
149
|
+
.default({}),
|
|
150
|
+
/**
|
|
151
|
+
* A command the engine runs in a checkout before every test run there
|
|
152
|
+
* (baseline, red check, gates), for repos whose tests need build outputs
|
|
153
|
+
* that are gitignored, such as `bun run build:rom`. Agents never run it.
|
|
154
|
+
*/
|
|
155
|
+
prepare: z.string().min(1).optional(),
|
|
156
|
+
/**
|
|
157
|
+
* How long the prepare command may run, in milliseconds; left out, the
|
|
158
|
+
* engine's default (`DEFAULT_PREPARE_TIMEOUT_MS`, 30 minutes).
|
|
159
|
+
*/
|
|
160
|
+
prepare_timeout_ms: z.number().int().positive().optional(),
|
|
161
|
+
/**
|
|
162
|
+
* How many prepare runs may go at once across the run; left out, the
|
|
163
|
+
* engine's default (`DEFAULT_PREPARE_CONCURRENCY`, 1).
|
|
164
|
+
*/
|
|
165
|
+
prepare_concurrency: z.number().int().positive().optional(),
|
|
166
|
+
/**
|
|
167
|
+
* How many test runs may go at once across the run (#544): the
|
|
168
|
+
* baseline, the red check, the already-done check, and every gate's
|
|
169
|
+
* test and slow test commands. Left out, no cap.
|
|
170
|
+
*/
|
|
171
|
+
test_concurrency: z.number().int().positive().optional(),
|
|
172
|
+
/**
|
|
173
|
+
* How long each test command may run, in milliseconds; left out, the
|
|
174
|
+
* engine's default (`DEFAULT_COMMAND_TIMEOUT_MS`, 5 minutes).
|
|
175
|
+
*/
|
|
176
|
+
test_timeout_ms: z.number().int().positive().optional(),
|
|
177
|
+
/**
|
|
178
|
+
* How long one fast test may take, in milliseconds (#580): passed to every
|
|
179
|
+
* `bun` command of `checks.test` as `--timeout`, and checked on a ticket's
|
|
180
|
+
* own test files by their measured time. Left out, no limit.
|
|
181
|
+
*/
|
|
182
|
+
test_case_timeout_ms: z.number().int().positive().optional(),
|
|
183
|
+
/**
|
|
184
|
+
* Glob patterns for more new markdown the leftover scan lets through,
|
|
185
|
+
* beyond doc folders, READMEs, and files the repo names (#520), such as
|
|
186
|
+
* `design/*.md`; left out, none.
|
|
187
|
+
*/
|
|
188
|
+
leftover_allow: z.array(z.string().min(1)).optional(),
|
|
189
|
+
/**
|
|
190
|
+
* The repo's generators (#618): each name a writer may ask the engine to
|
|
191
|
+
* run (`run_generators`), with its command, such as `bun run gen:parser`.
|
|
192
|
+
* The engine runs them in the worktree after the writer's turn, before
|
|
193
|
+
* the checks, and commits their output. Agents never run them. Left out,
|
|
194
|
+
* none.
|
|
195
|
+
*/
|
|
196
|
+
generators: z.record(z.string().min(1), z.string().min(1)).optional(),
|
|
197
|
+
/**
|
|
198
|
+
* A markdown file listing each feature and the test files that prove it
|
|
199
|
+
* (#583), relative to the repo root: a path inside the repo, so not an
|
|
200
|
+
* absolute path, not one that climbs out with `..`, and not ending in
|
|
201
|
+
* `/`. A path not ending in `.md` names a folder (#597): every `*.md`
|
|
202
|
+
* page under it together is the map, and `loadEngineConfig` refuses one
|
|
203
|
+
* that is a file. The red check refuses a new test file it doesn't list,
|
|
204
|
+
* and the test-writer may edit it. Left out, no map.
|
|
205
|
+
*/
|
|
206
|
+
feature_map: z
|
|
207
|
+
.string()
|
|
208
|
+
.min(1)
|
|
209
|
+
.refine(isFileInsideRepo, {
|
|
210
|
+
message:
|
|
211
|
+
'must be a file inside the repo, or a folder of pages in it, relative to its root: not an absolute path, not one that leaves the repo with "..", and not ending in "/"',
|
|
212
|
+
})
|
|
213
|
+
.optional(),
|
|
214
|
+
test_file_patterns: z.array(z.string()).default(['**/*.test.ts']),
|
|
215
|
+
/**
|
|
216
|
+
* Glob patterns for non-test files the test-writer may also write
|
|
217
|
+
* (#619), such as shared test helpers and fixtures. They aren't tests:
|
|
218
|
+
* the red check and the feature map leave them alone. Left out, none.
|
|
219
|
+
*/
|
|
220
|
+
test_helper_patterns: z.array(z.string().min(1)).optional(),
|
|
221
|
+
test_setup_files: z.array(z.string()).default([]),
|
|
222
|
+
rule_files: z.array(z.string()).default([]),
|
|
223
|
+
muninn: MuninnConfigSchema.optional(),
|
|
224
|
+
/** The decision model shadow mode asks; left out, Clef. */
|
|
225
|
+
decision_model: DecisionModelConfigSchema.optional(),
|
|
226
|
+
/** Each routed role's model floor and the confidence bar; left out, every role runs Opus. */
|
|
227
|
+
models: ModelsConfigSchema.optional(),
|
|
228
|
+
/**
|
|
229
|
+
* The repo's run budget in tokens; left out, the engine's default
|
|
230
|
+
* (`DEFAULT_RUN_BUDGET_TOKENS`).
|
|
231
|
+
*/
|
|
232
|
+
run_budget_tokens: z.number().int().positive().optional(),
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
export type EngineConfig = z.infer<typeof EngineConfigSchema>
|
|
@@ -1,149 +1,59 @@
|
|
|
1
|
-
import { join } from 'node:path'
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The engine config file, relative to the root of the repo a run works on.
|
|
7
|
-
* `muninn.vault` sits at the same path old Luca used, because memory tooling
|
|
8
|
-
* outside the engine reads it from there.
|
|
9
|
-
*/
|
|
10
|
-
export const ENGINE_CONFIG_FILE = '.luca/config.json'
|
|
11
|
-
|
|
12
1
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
2
|
+
* Loading the engine config (`loadEngineConfig`). The config's shape is in
|
|
3
|
+
* `engine-config-schema.ts`, the reads of its fields in `config-fields.ts`,
|
|
4
|
+
* and the test commands in `test-commands.ts`; this module re-exports all of
|
|
5
|
+
* them, so importers use `./engine-config` as ever.
|
|
16
6
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export type TestResults = z.infer<typeof TestResultsSchema>
|
|
20
|
-
|
|
21
|
-
/** One entry of a `checks.test` list: a command, or a command and its kind. */
|
|
22
|
-
const TestCommandEntrySchema = z.union([
|
|
23
|
-
z.string().min(1),
|
|
24
|
-
z.object({
|
|
25
|
-
run: z.string().min(1),
|
|
26
|
-
results: TestResultsSchema.optional(),
|
|
27
|
-
}),
|
|
28
|
-
])
|
|
29
|
-
|
|
30
|
-
/** The config's `muninn` block. */
|
|
31
|
-
export const MuninnConfigSchema = z.object({
|
|
32
|
-
/** The project's memory vault. */
|
|
33
|
-
vault: z.string().min(1),
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The per-repo engine config: the gate commands, where tests live, the rule
|
|
38
|
-
* files for the rules lens, the project's memory vault, and the run budget.
|
|
39
|
-
*
|
|
40
|
-
* Unknown keys, such as old Luca's, are dropped when the file is read.
|
|
41
|
-
*
|
|
42
|
-
* The test command is optional here on purpose. A config without it still
|
|
43
|
-
* loads, and intake refuses the run with a clear message instead.
|
|
44
|
-
* `checks.test` is one command or a list of them (see `testCommands`); it is
|
|
45
|
-
* kept as written.
|
|
46
|
-
*/
|
|
47
|
-
export const EngineConfigSchema = z.object({
|
|
48
|
-
checks: z
|
|
49
|
-
.object({
|
|
50
|
-
test: z
|
|
51
|
-
.union([z.string().min(1), z.array(TestCommandEntrySchema)])
|
|
52
|
-
.optional(),
|
|
53
|
-
types: z.string().min(1).optional(),
|
|
54
|
-
lint: z.string().min(1).optional(),
|
|
55
|
-
})
|
|
56
|
-
.default({}),
|
|
57
|
-
/**
|
|
58
|
-
* A command the engine runs in a checkout before every test run there
|
|
59
|
-
* (baseline, red check, gates), for repos whose tests need build outputs
|
|
60
|
-
* that are gitignored, such as `bun run build:rom`. Agents never run it.
|
|
61
|
-
*/
|
|
62
|
-
prepare: z.string().min(1).optional(),
|
|
63
|
-
/**
|
|
64
|
-
* How long the prepare command may run, in milliseconds; left out, the
|
|
65
|
-
* engine's default (`DEFAULT_PREPARE_TIMEOUT_MS`, 30 minutes).
|
|
66
|
-
*/
|
|
67
|
-
prepare_timeout_ms: z.number().int().positive().optional(),
|
|
68
|
-
test_file_patterns: z.array(z.string()).default(['**/*.test.ts']),
|
|
69
|
-
test_setup_files: z.array(z.string()).default([]),
|
|
70
|
-
rule_files: z.array(z.string()).default([]),
|
|
71
|
-
muninn: MuninnConfigSchema.optional(),
|
|
72
|
-
/**
|
|
73
|
-
* The repo's run budget in tokens; left out, the engine's default
|
|
74
|
-
* (`DEFAULT_RUN_BUDGET_TOKENS`).
|
|
75
|
-
*/
|
|
76
|
-
run_budget_tokens: z.number().int().positive().optional(),
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
export type EngineConfig = z.infer<typeof EngineConfigSchema>
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* How long the prepare command may run when the config doesn't say: 30
|
|
83
|
-
* minutes. A first build in a fresh worktree can take many minutes
|
|
84
|
-
* (HeartGold's takes about 8), far past the 5 minutes other commands get.
|
|
85
|
-
*/
|
|
86
|
-
export const DEFAULT_PREPARE_TIMEOUT_MS = 30 * 60 * 1000
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* The prepare command's time limit: the config's `prepare_timeout_ms`, else
|
|
90
|
-
* the default.
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* prepareTimeoutOf({ config: { ...config, prepare_timeout_ms: 3_600_000 } }) // 3_600_000
|
|
94
|
-
*/
|
|
95
|
-
export const prepareTimeoutOf = ({
|
|
96
|
-
config,
|
|
97
|
-
}: {
|
|
98
|
-
config: EngineConfig
|
|
99
|
-
}): number => config.prepare_timeout_ms ?? DEFAULT_PREPARE_TIMEOUT_MS
|
|
100
|
-
|
|
101
|
-
/** One test command the engine runs, and how it reads the results. */
|
|
102
|
-
export type TestCommand = { run: string; results: TestResults }
|
|
103
|
-
|
|
104
|
-
/** Whether `run` is a `bun test` command, which gives bun's per-test results. */
|
|
105
|
-
export const isBunTestCommand = (run: string): boolean =>
|
|
106
|
-
/^bun\s+test(\s|$)/.test(run.trim())
|
|
107
|
-
|
|
108
|
-
/** A command starting with `bun test` gives bun's per-test results. */
|
|
109
|
-
const defaultResults = (run: string): TestResults =>
|
|
110
|
-
isBunTestCommand(run) ? 'bun' : 'pass_fail'
|
|
7
|
+
import { stat } from 'node:fs/promises'
|
|
8
|
+
import { join } from 'node:path'
|
|
111
9
|
|
|
112
|
-
|
|
113
|
-
* Every test command in the config, in order, each with its results kind.
|
|
114
|
-
* A missing `results` is `bun` for a `bun test` command, else `pass_fail`.
|
|
115
|
-
* Empty when there is no test command.
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* testCommands({ config: { ...config, checks: { test: ['bun test', 'bun run test:workers'] } } })
|
|
119
|
-
* // [{ run: 'bun test', results: 'bun' }, { run: 'bun run test:workers', results: 'pass_fail' }]
|
|
120
|
-
*/
|
|
121
|
-
export const testCommands = ({
|
|
122
|
-
config,
|
|
123
|
-
}: {
|
|
124
|
-
config: EngineConfig
|
|
125
|
-
}): TestCommand[] => {
|
|
126
|
-
const { test } = config.checks
|
|
127
|
-
if (test === undefined) return []
|
|
128
|
-
return (typeof test === 'string' ? [test] : test).map((entry) =>
|
|
129
|
-
typeof entry === 'string'
|
|
130
|
-
? { run: entry, results: defaultResults(entry) }
|
|
131
|
-
: {
|
|
132
|
-
run: entry.run,
|
|
133
|
-
results: entry.results ?? defaultResults(entry.run),
|
|
134
|
-
}
|
|
135
|
-
)
|
|
136
|
-
}
|
|
10
|
+
import { z } from 'zod'
|
|
137
11
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
12
|
+
import { featureMapPath, isFeatureMapFolder } from './config-fields'
|
|
13
|
+
import {
|
|
14
|
+
ENGINE_CONFIG_FILE,
|
|
15
|
+
EngineConfigSchema,
|
|
16
|
+
type EngineConfig,
|
|
17
|
+
} from './engine-config-schema'
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
DEFAULT_PREPARE_CONCURRENCY,
|
|
21
|
+
DEFAULT_PREPARE_TIMEOUT_MS,
|
|
22
|
+
decisionModelOf,
|
|
23
|
+
featureMapPath,
|
|
24
|
+
isFeatureMapFolder,
|
|
25
|
+
modelsOf,
|
|
26
|
+
prepareConcurrencyOf,
|
|
27
|
+
prepareTimeoutOf,
|
|
28
|
+
testTimeoutOf,
|
|
29
|
+
} from './config-fields'
|
|
30
|
+
export {
|
|
31
|
+
DecisionModelConfigSchema,
|
|
32
|
+
DEFAULT_MODEL,
|
|
33
|
+
ENGINE_CONFIG_FILE,
|
|
34
|
+
EngineConfigSchema,
|
|
35
|
+
ModelNameSchema,
|
|
36
|
+
ModelsConfigSchema,
|
|
37
|
+
MuninnConfigSchema,
|
|
38
|
+
ROUTED_ROLES,
|
|
39
|
+
TestResultsSchema,
|
|
40
|
+
} from './engine-config-schema'
|
|
41
|
+
export type {
|
|
42
|
+
EngineConfig,
|
|
43
|
+
ModelName,
|
|
44
|
+
ModelsConfig,
|
|
45
|
+
TestResults,
|
|
46
|
+
} from './engine-config-schema'
|
|
47
|
+
export {
|
|
48
|
+
bunTestCommands,
|
|
49
|
+
caseLimitOf,
|
|
50
|
+
fastBunCommand,
|
|
51
|
+
hasSlowTests,
|
|
52
|
+
isBunTestCommand,
|
|
53
|
+
slowTestCommands,
|
|
54
|
+
testCommands,
|
|
55
|
+
type TestCommand,
|
|
56
|
+
} from './test-commands'
|
|
147
57
|
|
|
148
58
|
export type LoadEngineConfigResult =
|
|
149
59
|
| { ok: true; config: EngineConfig }
|
|
@@ -195,5 +105,25 @@ export const loadEngineConfig = async ({
|
|
|
195
105
|
error: `${path} is not a valid engine config:\n${z.prettifyError(parsed.error)}`,
|
|
196
106
|
}
|
|
197
107
|
}
|
|
108
|
+
const map = featureMapPath({ config: parsed.data })
|
|
109
|
+
if (
|
|
110
|
+
map !== null &&
|
|
111
|
+
isFeatureMapFolder({ path: map }) &&
|
|
112
|
+
(await isFile({ path: join(repo_root, map) }))
|
|
113
|
+
) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: `${path} is not a valid engine config: feature_map ${map} is a file, but a path not ending in ".md" names a folder of pages. Rename the map to end in ".md", or make it a folder of *.md pages.`,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
198
119
|
return { ok: true, config: parsed.data }
|
|
199
120
|
}
|
|
121
|
+
|
|
122
|
+
/** Whether a path is a file (or a link to one), without failing. */
|
|
123
|
+
const isFile = async ({ path }: { path: string }): Promise<boolean> => {
|
|
124
|
+
try {
|
|
125
|
+
return (await stat(path)).isFile()
|
|
126
|
+
} catch {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The config's test commands: every one the engine runs (`testCommands`,
|
|
3
|
+
* `slowTestCommands`) and how it reads their results, and the fast test speed
|
|
4
|
+
* limit (#580) added to a `bun` command (`bunTestCommands`).
|
|
5
|
+
*/
|
|
6
|
+
import type { EngineConfig, TestResults } from './engine-config-schema'
|
|
7
|
+
|
|
8
|
+
/** One test command the engine runs, and how it reads the results. */
|
|
9
|
+
export type TestCommand = { run: string; results: TestResults }
|
|
10
|
+
|
|
11
|
+
/** Whether `run` is a `bun test` command, which gives bun's per-test results. */
|
|
12
|
+
export const isBunTestCommand = (run: string): boolean =>
|
|
13
|
+
/^bun\s+test(\s|$)/.test(run.trim())
|
|
14
|
+
|
|
15
|
+
/** A command starting with `bun test` gives bun's per-test results. */
|
|
16
|
+
const defaultResults = (run: string): TestResults =>
|
|
17
|
+
isBunTestCommand(run) ? 'bun' : 'pass_fail'
|
|
18
|
+
|
|
19
|
+
/** Reads a test command setting (`checks.test` or `checks.slow_test`) into commands. */
|
|
20
|
+
const commandsOf = (value: EngineConfig['checks']['test']): TestCommand[] => {
|
|
21
|
+
if (value === undefined) return []
|
|
22
|
+
return (typeof value === 'string' ? [value] : value).map((entry) =>
|
|
23
|
+
typeof entry === 'string'
|
|
24
|
+
? { run: entry, results: defaultResults(entry) }
|
|
25
|
+
: {
|
|
26
|
+
run: entry.run,
|
|
27
|
+
results: entry.results ?? defaultResults(entry.run),
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Every test command in the config, in order, each with its results kind.
|
|
34
|
+
* A missing `results` is `bun` for a `bun test` command, else `pass_fail`.
|
|
35
|
+
* Empty when there is no test command.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* testCommands({ config: { ...config, checks: { test: ['bun test', 'bun run test:workers'] } } })
|
|
39
|
+
* // [{ run: 'bun test', results: 'bun' }, { run: 'bun run test:workers', results: 'pass_fail' }]
|
|
40
|
+
*/
|
|
41
|
+
export const testCommands = ({
|
|
42
|
+
config,
|
|
43
|
+
}: {
|
|
44
|
+
config: EngineConfig
|
|
45
|
+
}): TestCommand[] => commandsOf(config.checks.test)
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Every slow test command (#563), in order, each with its results kind, like
|
|
49
|
+
* `testCommands`. Empty when the config has none.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* slowTestCommands({ config: { ...config, checks: { test: 'bun test src', slow_test: 'bun test .e2e.test' } } })
|
|
53
|
+
* // [{ run: 'bun test .e2e.test', results: 'bun' }]
|
|
54
|
+
*/
|
|
55
|
+
export const slowTestCommands = ({
|
|
56
|
+
config,
|
|
57
|
+
}: {
|
|
58
|
+
config: EngineConfig
|
|
59
|
+
}): TestCommand[] => commandsOf(config.checks.slow_test)
|
|
60
|
+
|
|
61
|
+
/** Whether the config has a slow test command (#563). */
|
|
62
|
+
export const hasSlowTests = ({ config }: { config: EngineConfig }): boolean =>
|
|
63
|
+
slowTestCommands({ config }).length > 0
|
|
64
|
+
|
|
65
|
+
/** The fast test speed limit (#580), or `null` with none. */
|
|
66
|
+
export const caseLimitOf = ({
|
|
67
|
+
config,
|
|
68
|
+
}: {
|
|
69
|
+
config: EngineConfig
|
|
70
|
+
}): number | null => config.test_case_timeout_ms ?? null
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A fast `bun` test command with the speed limit (#580): `--timeout=<n>` at
|
|
74
|
+
* its end, or the command as written with no limit.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* fastBunCommand({ run: 'bun test src', config: { ...config, test_case_timeout_ms: 2000 } }) // 'bun test src --timeout=2000'
|
|
78
|
+
*/
|
|
79
|
+
export const fastBunCommand = ({
|
|
80
|
+
run,
|
|
81
|
+
config,
|
|
82
|
+
}: {
|
|
83
|
+
run: string
|
|
84
|
+
config: EngineConfig
|
|
85
|
+
}): string => {
|
|
86
|
+
const limit = caseLimitOf({ config })
|
|
87
|
+
return limit === null ? run : `${run} --timeout=${limit}`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The test commands whose per-test results the engine reads, each with the
|
|
92
|
+
* fast test speed limit (#580) when the config has one. Used by the baseline,
|
|
93
|
+
* the red check and the already-done check; `testCommands` stays as written.
|
|
94
|
+
*/
|
|
95
|
+
export const bunTestCommands = ({
|
|
96
|
+
config,
|
|
97
|
+
}: {
|
|
98
|
+
config: EngineConfig
|
|
99
|
+
}): string[] =>
|
|
100
|
+
testCommands({ config })
|
|
101
|
+
.filter(({ results }) => results === 'bun')
|
|
102
|
+
.map(({ run }) => fastBunCommand({ run, config }))
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { afterAll, beforeAll } from 'bun:test'
|
|
6
|
+
import omit from 'lodash/omit'
|
|
7
|
+
|
|
8
|
+
import type { ScriptedTurn } from '../agents/scripted-launcher'
|
|
9
|
+
import type { JournalRecord } from '../journal/journal-record'
|
|
10
|
+
import { createPracticeRepo } from '../testing/practice-repo'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Helpers shared by the guard tests (`agent-guards.e2e.test.ts` and the
|
|
14
|
+
* `agent-guards-*.e2e.test.ts` files beside it): reading what a practice run
|
|
15
|
+
* left in its journal and branch, and building one run to share between a
|
|
16
|
+
* describe's tests.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type Practice = Awaited<ReturnType<typeof createPracticeRepo>>
|
|
20
|
+
export type Ran = Awaited<ReturnType<Practice['run']>>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Each failed turn's content, without `session_gone` (#521) or `out_of_turns`
|
|
24
|
+
* (#543): no guard test follows up a gone session or reaches a turn limit.
|
|
25
|
+
*/
|
|
26
|
+
export const failures = (records: JournalRecord[]) =>
|
|
27
|
+
records.flatMap((record) =>
|
|
28
|
+
record.kind === 'agent_failed'
|
|
29
|
+
? [omit(record.content, 'session_gone', 'out_of_turns')]
|
|
30
|
+
: []
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
export const commits = (records: JournalRecord[]) =>
|
|
34
|
+
records.flatMap((record) =>
|
|
35
|
+
record.kind === 'commit_made'
|
|
36
|
+
? [{ stage: record.content.stage, files: record.content.files }]
|
|
37
|
+
: []
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
export const created = (records: JournalRecord[]) => {
|
|
41
|
+
const found = records.find(
|
|
42
|
+
(record) => record.kind === 'ticket_worktree_created'
|
|
43
|
+
)
|
|
44
|
+
if (found?.kind !== 'ticket_worktree_created') throw new Error('none')
|
|
45
|
+
return found.content
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The ticket's branch. A run that opens its PR removes the worktree, so its
|
|
50
|
+
* files are read from the branch, which stays.
|
|
51
|
+
*/
|
|
52
|
+
export const ticketBranch = (records: JournalRecord[]): string =>
|
|
53
|
+
created(records).branch
|
|
54
|
+
|
|
55
|
+
export const CLEAN_COMMITS: { stage: 'red' | 'green'; files: string[] }[] = [
|
|
56
|
+
{ stage: 'red', files: ['src/sum.test.ts'] },
|
|
57
|
+
{ stage: 'green', files: ['src/index.ts', 'src/sum.ts'] },
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds one practice run with these turns before the describe's tests, and
|
|
62
|
+
* removes its folder after them. The tests only read what it left.
|
|
63
|
+
*/
|
|
64
|
+
export const shareRun = (turns: (repo: string) => ScriptedTurn[]) => {
|
|
65
|
+
const shared: { root: string; practice?: Practice; ran?: Ran } = {
|
|
66
|
+
root: '',
|
|
67
|
+
}
|
|
68
|
+
beforeAll(async () => {
|
|
69
|
+
shared.root = await mkdtemp(join(tmpdir(), 'luca-engine-guards-'))
|
|
70
|
+
shared.practice = await createPracticeRepo({ root: shared.root })
|
|
71
|
+
shared.ran = await shared.practice.run({
|
|
72
|
+
turns: turns(shared.practice.repo),
|
|
73
|
+
})
|
|
74
|
+
}, 120_000)
|
|
75
|
+
afterAll(async () => {
|
|
76
|
+
await rm(shared.root, { recursive: true, force: true })
|
|
77
|
+
})
|
|
78
|
+
return (): Ran & { repo: string } => {
|
|
79
|
+
if (shared.practice === undefined || shared.ran === undefined) {
|
|
80
|
+
throw new Error('The shared run was not built.')
|
|
81
|
+
}
|
|
82
|
+
return { ...shared.ran, repo: shared.practice.repo }
|
|
83
|
+
}
|
|
84
|
+
}
|