@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,120 @@
|
|
|
1
|
+
import uniqBy from 'lodash/uniqBy'
|
|
2
|
+
|
|
3
|
+
import { endedText, type GateCheck, type TestRun } from './gate-schemas'
|
|
4
|
+
import { findCase } from './red-check'
|
|
5
|
+
|
|
6
|
+
import type { CriterionTests, TestRef } from '../agents/role-results'
|
|
7
|
+
import type { CommitPlace } from '../git/git-adapter'
|
|
8
|
+
|
|
9
|
+
/** What the engine's check of `already_done` evidence found (#495). */
|
|
10
|
+
export type AlreadyDoneCheckResult = { ok: boolean; problems: string[] }
|
|
11
|
+
|
|
12
|
+
/** What is wrong with one named test, or `null` if it passes on the base. */
|
|
13
|
+
const testProblem = ({
|
|
14
|
+
test,
|
|
15
|
+
missing,
|
|
16
|
+
test_files,
|
|
17
|
+
changed,
|
|
18
|
+
run,
|
|
19
|
+
}: {
|
|
20
|
+
test: TestRef
|
|
21
|
+
missing: Set<string>
|
|
22
|
+
test_files: Set<string>
|
|
23
|
+
changed: Set<string>
|
|
24
|
+
run: TestRun | null
|
|
25
|
+
}): string | null => {
|
|
26
|
+
const label = `"${test.name}" in ${test.file}`
|
|
27
|
+
if (missing.has(test.file)) return `${label}: the file does not exist`
|
|
28
|
+
if (!test_files.has(test.file)) {
|
|
29
|
+
return `${label}: the file is not a test file`
|
|
30
|
+
}
|
|
31
|
+
if (changed.has(test.file)) {
|
|
32
|
+
return `${label}: the file has changes that are not on the base`
|
|
33
|
+
}
|
|
34
|
+
// No run: the prepare command failed, and says so itself.
|
|
35
|
+
if (run === null) return null
|
|
36
|
+
const found = findCase({ cases: run.cases, test })
|
|
37
|
+
if (found?.status === 'passed') return null
|
|
38
|
+
if (found?.status === 'skipped') return `${label}: is skipped`
|
|
39
|
+
if (found !== undefined) return `${label}: fails on the base`
|
|
40
|
+
return run.files_without_results.includes(test.file)
|
|
41
|
+
? `${label}: its file did not load, so none of its tests ran`
|
|
42
|
+
: `${label}: not found in the test results`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The engine's check of a test-writer's `already_done` evidence (#495).
|
|
47
|
+
* Pure: every named commit is on the base (the commit the ticket's
|
|
48
|
+
* worktree started from), every criterion names a test, and every named
|
|
49
|
+
* test is in a test file that exists, is as it is on the base, and passes
|
|
50
|
+
* there.
|
|
51
|
+
*
|
|
52
|
+
* @param commits - Each named commit, and where git found it.
|
|
53
|
+
* @param missing - The named test files that do not exist in the worktree.
|
|
54
|
+
* @param test_files - The worktree's test files, per the engine config.
|
|
55
|
+
* @param changed - The worktree's uncommitted changes.
|
|
56
|
+
* @param prepare - The prepare command's run before the tests, if any.
|
|
57
|
+
* @param run - The run of the named test files; `null` if none ran.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const result = checkAlreadyDone({ base_sha, criteria_ids: ['AC1'], criteria, commits, missing: [], test_files, changed: [], prepare: null, run })
|
|
61
|
+
* if (!result.ok) console.error(result.problems)
|
|
62
|
+
*/
|
|
63
|
+
export const checkAlreadyDone = ({
|
|
64
|
+
base_sha,
|
|
65
|
+
criteria_ids,
|
|
66
|
+
criteria,
|
|
67
|
+
commits,
|
|
68
|
+
missing,
|
|
69
|
+
test_files,
|
|
70
|
+
changed,
|
|
71
|
+
prepare,
|
|
72
|
+
run,
|
|
73
|
+
}: {
|
|
74
|
+
base_sha: string
|
|
75
|
+
criteria_ids: string[]
|
|
76
|
+
criteria: CriterionTests[]
|
|
77
|
+
commits: { sha: string; place: CommitPlace }[]
|
|
78
|
+
missing: string[]
|
|
79
|
+
test_files: string[]
|
|
80
|
+
changed: string[]
|
|
81
|
+
prepare: GateCheck | null
|
|
82
|
+
run: TestRun | null
|
|
83
|
+
}): AlreadyDoneCheckResult => {
|
|
84
|
+
const problems: string[] = []
|
|
85
|
+
for (const { sha, place } of commits) {
|
|
86
|
+
if (place === 'unknown') {
|
|
87
|
+
problems.push(`commit ${sha}: not found in the repo`)
|
|
88
|
+
} else if (place === 'not_on_base') {
|
|
89
|
+
problems.push(
|
|
90
|
+
`commit ${sha}: not on the base (${base_sha}, where this ticket started)`
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const id of criteria_ids) {
|
|
95
|
+
const entry = criteria.find(({ criterion_id }) => criterion_id === id)
|
|
96
|
+
if (entry === undefined || entry.tests.length === 0) {
|
|
97
|
+
problems.push(`${id} names no test`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (prepare !== null && !prepare.ok) {
|
|
101
|
+
problems.push(
|
|
102
|
+
`The prepare command \`${prepare.command}\` failed (${endedText(prepare)}), so no test ran:\n${prepare.output}`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
const tests = uniqBy(
|
|
106
|
+
criteria.flatMap(({ tests }) => tests),
|
|
107
|
+
({ file, name }) => `${file}\0${name}`
|
|
108
|
+
)
|
|
109
|
+
for (const test of tests) {
|
|
110
|
+
const problem = testProblem({
|
|
111
|
+
test,
|
|
112
|
+
missing: new Set(missing),
|
|
113
|
+
test_files: new Set(test_files),
|
|
114
|
+
changed: new Set(changed),
|
|
115
|
+
run,
|
|
116
|
+
})
|
|
117
|
+
if (problem !== null) problems.push(problem)
|
|
118
|
+
}
|
|
119
|
+
return { ok: problems.length === 0, problems }
|
|
120
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises'
|
|
2
|
+
import { join, normalize, relative } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { testFilesAmong } from './test-runner'
|
|
5
|
+
|
|
6
|
+
import { isFeatureMapFolder } from '../config/engine-config'
|
|
7
|
+
import { pathText } from '../git/path-text'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The test files a feature map lists (#583): every backticked path in it that
|
|
11
|
+
* matches the test file patterns, in order, once each. A path written with a
|
|
12
|
+
* leading `./` counts as the path without it, as a worktree-relative path is
|
|
13
|
+
* everywhere else.
|
|
14
|
+
*
|
|
15
|
+
* @param text - The feature map's markdown.
|
|
16
|
+
* @param test_file_patterns - The engine config's test file patterns.
|
|
17
|
+
* @returns The listed test files, in the order the map first names them.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* featureMapFiles({ text: '- `src/sum.test.ts`', test_file_patterns: ['**\/*.test.ts'] }) // ['src/sum.test.ts']
|
|
21
|
+
* featureMapFiles({ text: '- `./src/sum.test.ts`', test_file_patterns: ['**\/*.test.ts'] }) // ['src/sum.test.ts']
|
|
22
|
+
*/
|
|
23
|
+
export const featureMapFiles = ({
|
|
24
|
+
text,
|
|
25
|
+
test_file_patterns,
|
|
26
|
+
}: {
|
|
27
|
+
text: string
|
|
28
|
+
test_file_patterns: string[]
|
|
29
|
+
}): string[] => {
|
|
30
|
+
const paths = [...text.matchAll(/`([^`\s]+)`/g)].map(([, path = '']) =>
|
|
31
|
+
path.replace(/^\.\//, '')
|
|
32
|
+
)
|
|
33
|
+
return [...new Set(testFilesAmong({ files: paths, test_file_patterns }))]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The text of every `*.md` page under a folder, nested ones too, in sorted
|
|
38
|
+
* order of their paths (#597). `null` when the folder is missing or a page
|
|
39
|
+
* can't be read.
|
|
40
|
+
*/
|
|
41
|
+
const folderText = async ({
|
|
42
|
+
folder,
|
|
43
|
+
}: {
|
|
44
|
+
folder: string
|
|
45
|
+
}): Promise<string | null> => {
|
|
46
|
+
try {
|
|
47
|
+
const entries = await readdir(folder, {
|
|
48
|
+
recursive: true,
|
|
49
|
+
withFileTypes: true,
|
|
50
|
+
})
|
|
51
|
+
const pages = entries
|
|
52
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
53
|
+
.map((entry) =>
|
|
54
|
+
relative(folder, join(entry.parentPath, entry.name))
|
|
55
|
+
)
|
|
56
|
+
.toSorted()
|
|
57
|
+
const texts = await Promise.all(
|
|
58
|
+
pages.map((page) => Bun.file(join(folder, page)).text())
|
|
59
|
+
)
|
|
60
|
+
return texts.join('\n')
|
|
61
|
+
} catch {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The test files a worktree's feature map lists (#583, #597). A path ending
|
|
68
|
+
* in `.md` is one file, read as it always was; any other path is a folder,
|
|
69
|
+
* and every `*.md` page under it, in sorted order, together is the map.
|
|
70
|
+
* (`loadEngineConfig` refuses a map file whose name doesn't end in `.md`.)
|
|
71
|
+
*
|
|
72
|
+
* @param cwd - The worktree.
|
|
73
|
+
* @param path - The map's worktree-relative path, as the config spells it.
|
|
74
|
+
* @param test_file_patterns - The engine config's test file patterns.
|
|
75
|
+
* @returns The listed test files (see `featureMapFiles`), or `null` when the
|
|
76
|
+
* map is missing or can't be read, so the red check can name it rather
|
|
77
|
+
* than crash.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* await readFeatureMap({ cwd, path: 'docs/feature-map', test_file_patterns }) // ['src/sum.test.ts']
|
|
81
|
+
*/
|
|
82
|
+
export const readFeatureMap = async ({
|
|
83
|
+
cwd,
|
|
84
|
+
path,
|
|
85
|
+
test_file_patterns,
|
|
86
|
+
}: {
|
|
87
|
+
cwd: string
|
|
88
|
+
path: string
|
|
89
|
+
test_file_patterns: string[]
|
|
90
|
+
}): Promise<string[] | null> => {
|
|
91
|
+
const clean = normalize(path)
|
|
92
|
+
const text = isFeatureMapFolder({ path: clean })
|
|
93
|
+
? await folderText({ folder: join(cwd, clean) })
|
|
94
|
+
: await pathText({ path: join(cwd, clean) })
|
|
95
|
+
return text === null ? null : featureMapFiles({ text, test_file_patterns })
|
|
96
|
+
}
|
|
@@ -1,100 +1,47 @@
|
|
|
1
|
-
import type { GateCheck, GateName } from './gate-schemas'
|
|
2
|
-
import {
|
|
1
|
+
import type { GateCheck, GateName, TestCase } from './gate-schemas'
|
|
2
|
+
import { ownFilesCheck } from './own-files-check'
|
|
3
|
+
import { prepareCheck } from './prepare-check'
|
|
4
|
+
import { shellCheck } from './shell-check'
|
|
5
|
+
import { testChecks } from './test-checks'
|
|
3
6
|
|
|
4
7
|
import {
|
|
5
|
-
|
|
8
|
+
caseLimitOf,
|
|
9
|
+
slowTestCommands,
|
|
6
10
|
testCommands,
|
|
11
|
+
testTimeoutOf,
|
|
7
12
|
type EngineConfig,
|
|
8
13
|
} from '../config/engine-config'
|
|
9
|
-
import {
|
|
10
|
-
clipOutput,
|
|
11
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
12
|
-
runShell,
|
|
13
|
-
} from '../shell/run-command'
|
|
14
|
-
|
|
15
|
-
/** A time limit in words: `30 minutes`, `1 minute`, `0.3 seconds`. */
|
|
16
|
-
const durationText = (ms: number): string => {
|
|
17
|
-
const [amount, unit] =
|
|
18
|
-
ms >= 60_000 ? [ms / 60_000, 'minute'] : [ms / 1000, 'second']
|
|
19
|
-
const rounded = Math.round(amount * 10) / 10
|
|
20
|
-
return `${rounded} ${unit}${rounded === 1 ? '' : 's'}`
|
|
21
|
-
}
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* `timeout_hint`, if any.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* const install = await shellCheck({ name: 'install', command: 'bun install', cwd })
|
|
31
|
-
*/
|
|
32
|
-
export const shellCheck = async ({
|
|
33
|
-
name,
|
|
34
|
-
command,
|
|
35
|
-
cwd,
|
|
36
|
-
timeout_ms,
|
|
37
|
-
timeout_hint,
|
|
38
|
-
}: {
|
|
39
|
-
name: GateName
|
|
40
|
-
command: string
|
|
41
|
-
cwd: string
|
|
42
|
-
/** Defaults to `DEFAULT_COMMAND_TIMEOUT_MS`. */
|
|
43
|
-
timeout_ms?: number
|
|
44
|
-
/** How to give the command longer, for the timed-out message. */
|
|
45
|
-
timeout_hint?: string
|
|
46
|
-
}): Promise<GateCheck> => {
|
|
47
|
-
const limit = timeout_ms ?? DEFAULT_COMMAND_TIMEOUT_MS
|
|
48
|
-
const result = await runShell({ command, cwd, timeout_ms: limit })
|
|
49
|
-
const ok = result.exit_code === 0
|
|
50
|
-
const timedOut = result.timed_out
|
|
51
|
-
? `Timed out after ${durationText(limit)}, so the engine stopped it and everything it started.${timeout_hint === undefined ? '' : ` ${timeout_hint}`}\n`
|
|
52
|
-
: ''
|
|
53
|
-
return {
|
|
54
|
-
name,
|
|
55
|
-
command,
|
|
56
|
-
ok,
|
|
57
|
-
exit_code: result.exit_code,
|
|
58
|
-
output: ok
|
|
59
|
-
? ''
|
|
60
|
-
: `${timedOut}${clipOutput({ text: `${result.stdout}\n${result.stderr}` })}`,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Runs the config's prepare command in `cwd`, as the `prepare` check, or
|
|
66
|
-
* `null` when the config has none. The engine runs it before every test run
|
|
67
|
-
* in a checkout, so tests that need build outputs find them up to date.
|
|
68
|
-
* It gets its own time limit (see `prepareTimeoutOf`), since a first build
|
|
69
|
-
* can take far longer than other commands.
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* const prepare = await prepareCheck({ config, cwd })
|
|
73
|
-
* if (prepare !== null && !prepare.ok) console.log(prepare.output)
|
|
74
|
-
*/
|
|
75
|
-
export const prepareCheck = async ({
|
|
76
|
-
config,
|
|
77
|
-
cwd,
|
|
78
|
-
}: {
|
|
79
|
-
config: EngineConfig
|
|
80
|
-
cwd: string
|
|
81
|
-
}): Promise<GateCheck | null> =>
|
|
82
|
-
config.prepare === undefined
|
|
83
|
-
? null
|
|
84
|
-
: shellCheck({
|
|
85
|
-
name: 'prepare',
|
|
86
|
-
command: config.prepare,
|
|
87
|
-
cwd,
|
|
88
|
-
timeout_ms: prepareTimeoutOf({ config }),
|
|
89
|
-
timeout_hint:
|
|
90
|
-
'To give it longer, raise `prepare_timeout_ms` in `.luca/config.json`.',
|
|
91
|
-
})
|
|
15
|
+
export { prepareCheck } from './prepare-check'
|
|
16
|
+
export { runSlowTests } from './slow-test-runner'
|
|
17
|
+
export { shellCheck } from './shell-check'
|
|
18
|
+
export { FAST_TEST_HINT } from './test-checks'
|
|
92
19
|
|
|
93
20
|
/**
|
|
94
21
|
* Runs every gate the engine config names, in order: each test command,
|
|
95
|
-
*
|
|
22
|
+
* then (the final review's gates only) each slow test command, types, lint.
|
|
23
|
+
* All must pass. Unset gates are left out; intake refuses a
|
|
96
24
|
* config with no test command. A `bun` test command runs with bun's JUnit
|
|
97
25
|
* reporter; a `pass_fail` one runs as written and is judged by its exit code.
|
|
26
|
+
* Each test command gets the config's time limit (see `testTimeoutOf`);
|
|
27
|
+
* types and lint get the engine's default. With `test_concurrency` (#544),
|
|
28
|
+
* each test command (the own-files run too) waits for a test slot first,
|
|
29
|
+
* and its check records the wait as `wait_ms`.
|
|
30
|
+
*
|
|
31
|
+
* With a fast test speed limit (`test_case_timeout_ms`, #580), every `bun`
|
|
32
|
+
* command of `checks.test` gets `--timeout=<limit>`, and the tests in
|
|
33
|
+
* `measured_files` are also held to it by measured time. Slow commands, the
|
|
34
|
+
* own-files run, and `pass_fail` commands never get it.
|
|
35
|
+
*
|
|
36
|
+
* With `own_test_files` (a ticket's gates, with a slow command, #564), the
|
|
37
|
+
* ones the fast runs gave no results for run next on their own with plain
|
|
38
|
+
* `bun test`, as one more `test` check, with the same time limit. A run that
|
|
39
|
+
* gives no results (its files don't load) adds no check after a failed fast
|
|
40
|
+
* run, which already fails the gate.
|
|
41
|
+
*
|
|
42
|
+
* Also returns `cases`, the test cases of the fast `bun` runs and the
|
|
43
|
+
* own-files run, each with its `expect()` count, so the engine can find a
|
|
44
|
+
* mapped test that checks nothing (#581). A `pass_fail` command has none.
|
|
98
45
|
*
|
|
99
46
|
* With an `install` command (see `installCommand`), the engine runs it first,
|
|
100
47
|
* as the `install` check. A failed install stops there: the other gates
|
|
@@ -103,8 +50,11 @@ export const prepareCheck = async ({
|
|
|
103
50
|
* `around_prepare`, if given, runs the prepare check (so the engine can see
|
|
104
51
|
* what it made).
|
|
105
52
|
*
|
|
53
|
+
* With `exclusive` (a lone rerun, #544), each test command waits until no
|
|
54
|
+
* other test run is going, and holds every test slot while it runs.
|
|
55
|
+
*
|
|
106
56
|
* @example
|
|
107
|
-
* const { ok, checks } = await runGates({ cwd, config, test_files, report_file, install: null })
|
|
57
|
+
* const { ok, checks, cases } = await runGates({ cwd, config, test_files, report_file, install: null })
|
|
108
58
|
*/
|
|
109
59
|
export const runGates = async ({
|
|
110
60
|
cwd,
|
|
@@ -113,6 +63,10 @@ export const runGates = async ({
|
|
|
113
63
|
report_file,
|
|
114
64
|
install,
|
|
115
65
|
around_prepare,
|
|
66
|
+
with_slow_tests,
|
|
67
|
+
own_test_files,
|
|
68
|
+
measured_files,
|
|
69
|
+
exclusive = false,
|
|
116
70
|
}: {
|
|
117
71
|
cwd: string
|
|
118
72
|
config: EngineConfig
|
|
@@ -123,7 +77,24 @@ export const runGates = async ({
|
|
|
123
77
|
around_prepare?: (
|
|
124
78
|
prepare: () => Promise<GateCheck | null>
|
|
125
79
|
) => Promise<GateCheck | null>
|
|
126
|
-
|
|
80
|
+
/**
|
|
81
|
+
* The final review's gates also run the config's slow tests (#563), after
|
|
82
|
+
* the test commands, as `slow_test` checks. Left out, they don't.
|
|
83
|
+
*/
|
|
84
|
+
with_slow_tests?: boolean
|
|
85
|
+
/**
|
|
86
|
+
* A ticket's changed test files (#564): those no fast run gave results
|
|
87
|
+
* for are run on their own, as one more `test` check. Left out, none.
|
|
88
|
+
*/
|
|
89
|
+
own_test_files?: string[]
|
|
90
|
+
/**
|
|
91
|
+
* A ticket's changed test files: their tests are held to
|
|
92
|
+
* `test_case_timeout_ms` by measured time (#580); left out, none.
|
|
93
|
+
*/
|
|
94
|
+
measured_files?: string[]
|
|
95
|
+
/** Run each test command alone (#544). Left out, they don't. */
|
|
96
|
+
exclusive?: boolean
|
|
97
|
+
}): Promise<{ ok: boolean; checks: GateCheck[]; cases: TestCase[] }> => {
|
|
127
98
|
const checks: GateCheck[] = []
|
|
128
99
|
if (install !== null) {
|
|
129
100
|
const installed = await shellCheck({
|
|
@@ -132,35 +103,64 @@ export const runGates = async ({
|
|
|
132
103
|
cwd,
|
|
133
104
|
})
|
|
134
105
|
checks.push(installed)
|
|
135
|
-
if (!installed.ok) return { ok: false, checks }
|
|
106
|
+
if (!installed.ok) return { ok: false, checks, cases: [] }
|
|
136
107
|
}
|
|
137
108
|
const prepare = () => prepareCheck({ config, cwd })
|
|
138
109
|
const prepared = await (around_prepare?.(prepare) ?? prepare())
|
|
139
110
|
if (prepared !== null) {
|
|
140
111
|
checks.push(prepared)
|
|
141
|
-
if (!prepared.ok) return { ok: false, checks }
|
|
112
|
+
if (!prepared.ok) return { ok: false, checks, cases: [] }
|
|
142
113
|
}
|
|
143
114
|
const { types, lint } = config.checks
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
115
|
+
const timeout_ms = testTimeoutOf({ config })
|
|
116
|
+
const { test_concurrency } = config
|
|
117
|
+
const limit_ms = caseLimitOf({ config })
|
|
118
|
+
const fast = testCommands({ config })
|
|
119
|
+
const fastChecks = await testChecks({
|
|
120
|
+
commands: fast,
|
|
121
|
+
name: 'test',
|
|
122
|
+
cwd,
|
|
123
|
+
test_files,
|
|
124
|
+
report_file,
|
|
125
|
+
first_index: 0,
|
|
126
|
+
timeout_ms,
|
|
127
|
+
limit:
|
|
128
|
+
limit_ms === null
|
|
129
|
+
? null
|
|
130
|
+
: { limit_ms, measured_files: measured_files ?? [] },
|
|
131
|
+
test_concurrency,
|
|
132
|
+
exclusive,
|
|
133
|
+
})
|
|
134
|
+
checks.push(...fastChecks.checks)
|
|
135
|
+
// The test cases the gates saw, for the empty-test check (#581).
|
|
136
|
+
const cases = fastChecks.runs.flatMap((run) => run.cases)
|
|
137
|
+
const own = await ownFilesCheck({
|
|
138
|
+
own_test_files: own_test_files ?? [],
|
|
139
|
+
fast_checks: fastChecks.checks,
|
|
140
|
+
fast_runs: fastChecks.runs,
|
|
141
|
+
cwd,
|
|
142
|
+
report_file,
|
|
143
|
+
report_index: fast.length + slowTestCommands({ config }).length,
|
|
144
|
+
timeout_ms,
|
|
145
|
+
test_concurrency,
|
|
146
|
+
exclusive,
|
|
147
|
+
})
|
|
148
|
+
cases.push(...own.cases)
|
|
149
|
+
if (own.check !== null) checks.push(own.check)
|
|
150
|
+
if (with_slow_tests === true) {
|
|
151
|
+
const slow = await testChecks({
|
|
152
|
+
commands: slowTestCommands({ config }),
|
|
153
|
+
name: 'slow_test',
|
|
152
154
|
cwd,
|
|
153
|
-
command,
|
|
154
155
|
test_files,
|
|
155
|
-
report_file
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
exit_code: run.exit_code,
|
|
162
|
-
output: run.ok ? '' : run.output,
|
|
156
|
+
report_file,
|
|
157
|
+
first_index: fast.length,
|
|
158
|
+
timeout_ms,
|
|
159
|
+
limit: null,
|
|
160
|
+
test_concurrency,
|
|
161
|
+
exclusive,
|
|
163
162
|
})
|
|
163
|
+
checks.push(...slow.checks)
|
|
164
164
|
}
|
|
165
165
|
const others: { name: GateName; command: string | undefined }[] = [
|
|
166
166
|
{ name: 'types', command: types },
|
|
@@ -170,5 +170,5 @@ export const runGates = async ({
|
|
|
170
170
|
if (command === undefined) continue
|
|
171
171
|
checks.push(await shellCheck({ name, command, cwd }))
|
|
172
172
|
}
|
|
173
|
-
return { ok: checks.every(({ ok }) => ok), checks }
|
|
173
|
+
return { ok: checks.every(({ ok }) => ok), checks, cases }
|
|
174
174
|
}
|
|
@@ -6,10 +6,22 @@ export const TestCaseSchema = z.object({
|
|
|
6
6
|
/** Describe names and the test name joined by " > ". */
|
|
7
7
|
full_name: z.string(),
|
|
8
8
|
status: z.enum(['passed', 'failed', 'skipped']),
|
|
9
|
+
/** How long it ran, from bun's JUnit `time` (#518). Missing in old journals. */
|
|
10
|
+
duration_ms: z.number().int().nonnegative().optional(),
|
|
11
|
+
/** How many `expect()` calls it made, from bun's JUnit `assertions` (#581). */
|
|
12
|
+
assertions: z.number().int().nonnegative().optional(),
|
|
9
13
|
})
|
|
10
14
|
|
|
11
15
|
export type TestCase = z.infer<typeof TestCaseSchema>
|
|
12
16
|
|
|
17
|
+
/** A test case by its file and full name, without its outcome. */
|
|
18
|
+
export const TestCaseRefSchema = TestCaseSchema.pick({
|
|
19
|
+
file: true,
|
|
20
|
+
full_name: true,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export type TestCaseRef = z.infer<typeof TestCaseRefSchema>
|
|
24
|
+
|
|
13
25
|
/** One run of the engine config's test command, with each case's outcome. */
|
|
14
26
|
export const TestRunSchema = z.object({
|
|
15
27
|
command: z.string(),
|
|
@@ -22,21 +34,31 @@ export const TestRunSchema = z.object({
|
|
|
22
34
|
files_without_results: z.array(z.string()),
|
|
23
35
|
/** The command's output, clipped. */
|
|
24
36
|
output: z.string(),
|
|
37
|
+
/**
|
|
38
|
+
* How long it waited for a test slot (#544), only with
|
|
39
|
+
* `test_concurrency` set.
|
|
40
|
+
*/
|
|
41
|
+
wait_ms: z.number().int().nonnegative().optional(),
|
|
25
42
|
})
|
|
26
43
|
|
|
27
44
|
export type TestRun = z.infer<typeof TestRunSchema>
|
|
28
45
|
|
|
29
46
|
/**
|
|
30
47
|
* The gates the engine config can name, plus `install`: the engine's own
|
|
31
|
-
* install, run first when a package manifest changed,
|
|
32
|
-
* config's prepare command, run before the tests
|
|
48
|
+
* install, run first when a package manifest changed, `prepare`: the
|
|
49
|
+
* config's prepare command, run before the tests, and `slow_test`: the
|
|
50
|
+
* config's slow tests, run only at the final review (#563). `generate` is
|
|
51
|
+
* one of the config's generators, run after a writer's turn when it asked
|
|
52
|
+
* (#618); it is journaled in `chores_done`, never in the gates.
|
|
33
53
|
*/
|
|
34
54
|
export const GateNameSchema = z.enum([
|
|
35
55
|
'install',
|
|
36
56
|
'prepare',
|
|
37
57
|
'test',
|
|
58
|
+
'slow_test',
|
|
38
59
|
'types',
|
|
39
60
|
'lint',
|
|
61
|
+
'generate',
|
|
40
62
|
])
|
|
41
63
|
|
|
42
64
|
export type GateName = z.infer<typeof GateNameSchema>
|
|
@@ -48,6 +70,14 @@ export const GateCheckSchema = z.object({
|
|
|
48
70
|
ok: z.boolean(),
|
|
49
71
|
exit_code: z.number().int().nullable(),
|
|
50
72
|
output: z.string(),
|
|
73
|
+
/** How long the command ran (#518). Missing in old journals. */
|
|
74
|
+
duration_ms: z.number().int().nonnegative().optional(),
|
|
75
|
+
/**
|
|
76
|
+
* How long it waited for its slot: a prepare slot for `prepare` (#518),
|
|
77
|
+
* a test slot for `test` and `slow_test` with `test_concurrency` set
|
|
78
|
+
* (#544).
|
|
79
|
+
*/
|
|
80
|
+
wait_ms: z.number().int().nonnegative().optional(),
|
|
51
81
|
})
|
|
52
82
|
|
|
53
83
|
export type GateCheck = z.infer<typeof GateCheckSchema>
|
|
@@ -82,3 +112,32 @@ export const LeftoverHitSchema = z.object({
|
|
|
82
112
|
})
|
|
83
113
|
|
|
84
114
|
export type LeftoverHit = z.infer<typeof LeftoverHitSchema>
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Why the leftover scan let a new markdown file through (#520): it sits in a
|
|
118
|
+
* doc place (a `docs` or `adr` folder, or a `README.md`), a tracked or newly
|
|
119
|
+
* added non-test file names it, or a `leftover_allow` pattern matches it.
|
|
120
|
+
*/
|
|
121
|
+
export const LeftoverPassReasonSchema = z.enum([
|
|
122
|
+
'doc_place',
|
|
123
|
+
'named_by_repo',
|
|
124
|
+
'config_pattern',
|
|
125
|
+
])
|
|
126
|
+
|
|
127
|
+
export type LeftoverPassReason = z.infer<typeof LeftoverPassReasonSchema>
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A new markdown file the leftover scan let through, and why, as the journal
|
|
131
|
+
* keeps it. The reason is a string there, so a later reason still reads.
|
|
132
|
+
*/
|
|
133
|
+
export const LeftoverPassSchema = z.object({
|
|
134
|
+
path: z.string(),
|
|
135
|
+
reason: z.string(),
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
/** A new markdown file the leftover scan let through, as the scan returns it. */
|
|
139
|
+
export const LeftoverPassResultSchema = LeftoverPassSchema.extend({
|
|
140
|
+
reason: LeftoverPassReasonSchema,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
export type LeftoverPass = z.infer<typeof LeftoverPassResultSchema>
|