autonomous-sdlc-harness 0.5.0 → 0.6.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/dist/commands/init.js +17 -8
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +27 -5
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +54 -11
- package/dist/config/model.js.map +1 -1
- package/dist/core/pluginIdentity.js +2 -0
- package/dist/core/pluginIdentity.js.map +1 -1
- package/dist/core/writer.js +1 -0
- package/dist/core/writer.js.map +1 -1
- package/dist/doctor/checks.js +317 -40
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +21 -11
- package/dist/generators/githubWorkflows.js.map +1 -1
- package/dist/remote/githubActions.js +63 -4
- package/dist/remote/githubActions.js.map +1 -1
- package/dist/retrieval/pythonBackend.js +114 -0
- package/dist/retrieval/pythonBackend.js.map +1 -0
- package/dist/retrieval/setup.js +8 -0
- package/dist/retrieval/setup.js.map +1 -1
- package/package.json +1 -1
- package/templates/README.md +1 -1
- package/templates/github/workflows/harness-control.yml +184 -0
- package/templates/github/workflows/harness-resume.yml +7 -0
- package/templates/github/workflows/harness-run.yml +100 -1
- package/templates/github/workflows/harness-trigger.yml +5 -4
- package/templates/scripts/README.md +1 -1
- package/templates/scripts/autonomous-watcher.sh +29 -2
- package/templates/scripts/docs-search-server.sh +88 -17
- package/templates/scripts/lib/harness-run-lib.sh +205 -26
- package/templates/scripts/remote-run.sh +2617 -150
- package/templates/scripts/scratch-run.sh +54 -73
- package/templates/state-dir/README-root.md +1 -1
- package/templates/state-dir/scratch/README.md +4 -2
- package/templates/state-dir/user_reviews/README.md +2 -2
package/dist/doctor/checks.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* remote-execution switch is `config/model.ts`'s {@link remoteExecutionApplies} and the
|
|
26
26
|
* issue-trigger switch its {@link forgeTriggerApplies}, the workflow paths and the binary run as
|
|
27
27
|
* `gh` are `remote/githubActions.ts`'s ({@link WORKFLOW_RUN_PATH}, {@link WORKFLOW_RESUME_PATH},
|
|
28
|
-
* {@link WORKFLOW_TRIGGER_PATH}, {@link GH_CLI_VARIABLE}, {@link ghCli}), whether a ref carries a
|
|
28
|
+
* {@link WORKFLOW_TRIGGER_PATH}, {@link WORKFLOW_CONTROL_PATH}, {@link GH_CLI_VARIABLE}, {@link ghCli}), whether a ref carries a
|
|
29
29
|
* file is `core/git.ts`'s {@link pathAtRef}, and
|
|
30
30
|
* the writability probe is the write engine's {@link probeWritable}. A
|
|
31
31
|
* check that wanted a slightly different answer would be a second definition of the thing being
|
|
@@ -70,13 +70,17 @@
|
|
|
70
70
|
* nothing. `retrieval-index` spawns a child of this CLI running `docs index --in-memory`: a child
|
|
71
71
|
* because {@link Check.run} is synchronous and the store is not, and because the child is the
|
|
72
72
|
* installation whose optional peers resolve beside it. It builds in memory and exits, so it starts
|
|
73
|
-
* no server and writes nothing.
|
|
73
|
+
* no server and writes nothing. The three `retrieval-python-*` checks share one child running the
|
|
74
|
+
* Python package's own `self-check` and grade its three lines; that child starts no server either.
|
|
74
75
|
*
|
|
75
76
|
* ## What this module deliberately does not do
|
|
76
77
|
*
|
|
77
78
|
* - **It writes nothing** beyond {@link probeWritable}'s temp file, which that function removes with
|
|
78
79
|
* an in-process `fs.rm` on a single file — never a shelled-out recursive removal, which a
|
|
79
|
-
* user-level `permissions.deny` can silently block (`cli.ts`'s header).
|
|
80
|
+
* user-level `permissions.deny` can silently block (`cli.ts`'s header). One exception:
|
|
81
|
+
* `retrieval-python-index` refreshes the Python backend's index **in its database**, because
|
|
82
|
+
* `self-check`'s `index` question builds it there — the same write the server's first query makes.
|
|
83
|
+
* It writes nothing in the repository or the machine cache.
|
|
80
84
|
* - **It repairs nothing.** Every failure names what to run — `init`, `init --force`, an edit to one
|
|
81
85
|
* config key — and `doctor` stays a command that is safe to run against a repository at any time.
|
|
82
86
|
*/
|
|
@@ -85,10 +89,11 @@ import { accessSync, constants as fsConstants, existsSync, readFileSync, statSyn
|
|
|
85
89
|
import { delimiter, join, posix, resolve as resolvePath } from 'node:path';
|
|
86
90
|
import { formatProblem } from '../config/check.js';
|
|
87
91
|
import { loadConfig } from '../config/io.js';
|
|
88
|
-
import { answersNone, browserWiringApplies, COMMAND_NONE_SENTINEL, CONFIG_FILENAME, DEFAULTS, FALLBACK_PRESET, FORGE_KINDS, forgeTriggerApplies, isPlaceholder, LAYER_CATCH_ALL_PATH, remoteExecutionApplies, retrievalApplies, STATE_DIR_DOT_PATTERN, STATE_DIR_PATTERN, } from '../config/model.js';
|
|
92
|
+
import { answersNone, browserWiringApplies, COMMAND_NONE_SENTINEL, CONFIG_FILENAME, DEFAULTS, FALLBACK_PRESET, FORGE_KINDS, forgeTriggerApplies, isPlaceholder, LAYER_CATCH_ALL_PATH, pythonRetrievalApplies, remoteExecutionApplies, retrievalApplies, STATE_DIR_DOT_PATTERN, STATE_DIR_PATTERN, } from '../config/model.js';
|
|
89
93
|
import { branchResolves, checkedOutBranch, commitsAhead, configuredRemotes, hasCommits, mainWorktreeRoot, pathAtRef, pathIsIgnored, remoteTrackingBranchResolves, resolveRepoRoot, worktreeList, } from '../core/git.js';
|
|
90
94
|
import { isJsonObject, readJsonFile } from '../core/json.js';
|
|
91
95
|
import { defaultBranchPushCommand, defaultBranchPushReason, WORKFLOW_SCOPE_COMMAND, WORKFLOW_SCOPE_REASON, } from '../core/defaultBranchPush.js';
|
|
96
|
+
import { internal } from '../core/errors.js';
|
|
92
97
|
import { layerCoverage } from '../core/layerCoverage.js';
|
|
93
98
|
import { layerGapRemedy, recordedVerdictClause } from '../core/layerGapRemedy.js';
|
|
94
99
|
import { nameList } from '../core/nameList.js';
|
|
@@ -113,9 +118,11 @@ import { selectedStateDirs } from '../generators/stateDir.js';
|
|
|
113
118
|
import { machineConfigDir } from '../machine/paths.js';
|
|
114
119
|
import { installedPluginsPath, knownMarketplacesPath, pluginInstallRoot, pluginRuntimeRoot, pluginScriptsDir, } from '../machine/plugins.js';
|
|
115
120
|
import { inspect, readRegistry, registryPath } from '../machine/registry.js';
|
|
116
|
-
import { API_KEY_SECRET, CLI_VERSION_VARIABLE, DEFAULT_GH_CLI, DEFAULT_TRIGGER_LABEL, GH_CLI_VARIABLE, ghCli, GIT_TOKEN_SECRET, OAUTH_TOKEN_SECRET, PUSH_URL_SECRET, REMOTE_STOP_VARIABLE, renderedCliVersions, runGh, RUNNER_VARIABLE, TRIGGER_ALLOWED_BOTS_VARIABLE, TRIGGER_LABEL_VARIABLE, WORKFLOW_RESUME_FILE, WORKFLOW_RESUME_PATH, WORKFLOW_RUN_FILE, WORKFLOW_RUN_PATH, WORKFLOW_TRIGGER_FILE, WORKFLOW_TRIGGER_PATH, } from '../remote/githubActions.js';
|
|
121
|
+
import { API_KEY_SECRET, CLI_VERSION_VARIABLE, DEFAULT_GH_CLI, COMMAND_HANDLE, COMMAND_VERBS, DEFAULT_TRIGGER_LABEL, GH_CLI_VARIABLE, ghCli, GIT_TOKEN_SECRET, LEGACY_TRIGGER_LABEL, OAUTH_TOKEN_SECRET, PR_CREATE_SETTING, PR_CREATE_SETTING_PATH, PUSH_URL_SECRET, REMOTE_STOP_VARIABLE, renderedCliVersions, runGh, RUNNER_VARIABLE, TRIGGER_ALLOWED_BOTS_VARIABLE, TRIGGER_LABEL_VARIABLE, triggerFallbackLabel, WORKFLOW_CONTROL_FILE, WORKFLOW_CONTROL_PATH, WORKFLOW_RESUME_FILE, WORKFLOW_RESUME_PATH, WORKFLOW_RUN_FILE, WORKFLOW_RUN_PATH, WORKFLOW_TRIGGER_FILE, WORKFLOW_TRIGGER_PATH, } from '../remote/githubActions.js';
|
|
117
122
|
import { modelFilesPresent } from '../retrieval/models.js';
|
|
123
|
+
import { launcherSearchPath, parseSelfCheck, PYTHON_DATABASE_URL_VARIABLE, PYTHON_FETCH_MODELS_SUB_COMMAND, PYTHON_RETRIEVAL_COMMAND, PYTHON_SELF_CHECK_SUB_COMMAND, pythonDatabaseUrl, SELF_CHECK_INDEX_NOT_ATTEMPTED, } from '../retrieval/pythonBackend.js';
|
|
118
124
|
import { retrievalCliEntry, retrievalModelCacheDir, retrievalRuntimeDir, retrievalRuntimeState, } from '../retrieval/runtime.js';
|
|
125
|
+
import { DOCS_SERVER_NAME } from '../retrieval/server.js';
|
|
119
126
|
/**
|
|
120
127
|
* How the CLI is typed, for every remedy that tells an operator what to run next. The `npx` prefix
|
|
121
128
|
* is not decoration: the rule for which occurrences carry it is stated once in `commands/init.ts`,
|
|
@@ -149,6 +156,13 @@ const REMOTE_RUN_SCRIPT = 'remote-run.sh';
|
|
|
149
156
|
* YAML file spells this endpoint.
|
|
150
157
|
*/
|
|
151
158
|
const ARTIFACT_RETENTION_ENDPOINT = 'repos/{owner}/{repo}/actions/permissions/artifact-and-log-retention';
|
|
159
|
+
/**
|
|
160
|
+
* The `gh api` path {@link REMOTE_GITHUB_CHECK} reads the *Allow GitHub Actions to create and approve
|
|
161
|
+
* pull requests* setting from — `can_approve_pull_request_reviews`. Local for
|
|
162
|
+
* {@link ARTIFACT_RETENTION_ENDPOINT}'s reason. A workflow's own token cannot read it; a person's `gh`
|
|
163
|
+
* usually can.
|
|
164
|
+
*/
|
|
165
|
+
const PR_SETTING_ENDPOINT = 'repos/{owner}/{repo}/actions/permissions/workflow';
|
|
152
166
|
/** Below this many days of artifact retention {@link REMOTE_GITHUB_CHECK} warns; argued there. */
|
|
153
167
|
const ARTIFACT_RETENTION_WARN_DAYS = 30;
|
|
154
168
|
/** The permission lists a generated profile carries, in the order the template writes them. */
|
|
@@ -2012,6 +2026,20 @@ function retentionDaysOf(stdout) {
|
|
|
2012
2026
|
const days = parsed.days;
|
|
2013
2027
|
return typeof days === 'number' && Number.isInteger(days) && days > 0 ? days : undefined;
|
|
2014
2028
|
}
|
|
2029
|
+
/** The boolean `can_approve_pull_request_reviews` of a workflow-permissions answer, or `undefined` for any other shape. */
|
|
2030
|
+
function prApprovalSettingOf(stdout) {
|
|
2031
|
+
let parsed;
|
|
2032
|
+
try {
|
|
2033
|
+
parsed = JSON.parse(stdout);
|
|
2034
|
+
}
|
|
2035
|
+
catch {
|
|
2036
|
+
return undefined;
|
|
2037
|
+
}
|
|
2038
|
+
if (!isJsonObject(parsed))
|
|
2039
|
+
return undefined;
|
|
2040
|
+
const allowed = parsed.can_approve_pull_request_reviews;
|
|
2041
|
+
return typeof allowed === 'boolean' ? allowed : undefined;
|
|
2042
|
+
}
|
|
2015
2043
|
/**
|
|
2016
2044
|
* What GitHub says about the remote setup — asked only under {@link CheckContext.probeGithub}.
|
|
2017
2045
|
*
|
|
@@ -2024,15 +2052,29 @@ function retentionDaysOf(stdout) {
|
|
|
2024
2052
|
* not know `harness-run.yml`; neither credential secret is set.
|
|
2025
2053
|
* - `warn` — `HARNESS_PUSH_URL` absent; `harness-resume.yml` unknown to GitHub; `HARNESS_REMOTE_STOP`
|
|
2026
2054
|
* set; artifact retention below {@link ARTIFACT_RETENTION_WARN_DAYS} days; when
|
|
2027
|
-
* {@link forgeTriggerApplies}, `harness-trigger.yml` unknown to GitHub,
|
|
2028
|
-
*
|
|
2029
|
-
* out, could not reach GitHub, or answered in a shape not
|
|
2030
|
-
* *missing*, so it never fails.
|
|
2055
|
+
* {@link forgeTriggerApplies}, `harness-trigger.yml` or `harness-control.yml` unknown to GitHub, no
|
|
2056
|
+
* label by the effective trigger name, or the pull-request setting off with no `HARNESS_GIT_TOKEN`
|
|
2057
|
+
* secret; and any call that timed out, could not reach GitHub, or answered in a shape not
|
|
2058
|
+
* understood — *cannot tell* is not *missing*, so it never fails.
|
|
2059
|
+
* - the pull-request setting off while the secret list was unreadable is a *cannot tell* warning,
|
|
2060
|
+
* never the missing-`HARNESS_GIT_TOKEN` one.
|
|
2061
|
+
* - the effective trigger label is `HARNESS_TRIGGER_LABEL`, else the fallback the committed
|
|
2062
|
+
* `harness-trigger.yml` carries ({@link triggerFallbackLabel}), else {@link DEFAULT_TRIGGER_LABEL};
|
|
2063
|
+
* a fallback of {@link LEGACY_TRIGGER_LABEL} is a note, since it still starts runs.
|
|
2031
2064
|
* - both credential secrets present is a note, not a finding: billing follows `ANTHROPIC_API_KEY`.
|
|
2032
2065
|
* - a non-empty `HARNESS_TRIGGER_ALLOWED_BOTS` is a note naming the bots, which start runs without a
|
|
2033
|
-
* permission check. When the trigger does not apply,
|
|
2066
|
+
* permission check. When the trigger does not apply, no trigger, control or pull-request-setting
|
|
2067
|
+
* read is made.
|
|
2034
2068
|
* - the retention read refused (typically HTTP 403: the endpoint needs admin access) is a note too —
|
|
2035
|
-
* the read is best-effort, and a collaborator without admin can still run remotely.
|
|
2069
|
+
* the read is best-effort, and a collaborator without admin can still run remotely. A refused
|
|
2070
|
+
* pull-request-setting read is a note on the same terms.
|
|
2071
|
+
* - the pull-request setting off with `HARNESS_GIT_TOKEN` set is a note: `deliver` opens the pull
|
|
2072
|
+
* request with that token instead.
|
|
2073
|
+
* - all three trigger answers positive — both workflows known and the label present — is confirmed on
|
|
2074
|
+
* every outcome that reaches the trigger reads, `fail` and `warn` included, so an unrelated finding
|
|
2075
|
+
* never hides it; any answer not positive is already among the warnings. An outcome returned before
|
|
2076
|
+
* those reads — `gh` not runnable, no usable login, or no readable answer to the login probe — asks
|
|
2077
|
+
* GitHub nothing about the trigger.
|
|
2036
2078
|
*
|
|
2037
2079
|
* **Why 30 days.** A parked run waits on a human answer and a usage-paused one on a reset, and the
|
|
2038
2080
|
* `harness-state` bundle is the only remote copy of either; once the repository's retention expires
|
|
@@ -2157,9 +2199,31 @@ const REMOTE_GITHUB_CHECK = {
|
|
|
2157
2199
|
if (trigger.answer.kind === 'refused') {
|
|
2158
2200
|
warnings.push(`GitHub does not know ${WORKFLOW_TRIGGER_FILE} (${trigger.call}: ${trigger.answer.why}), so labelling an issue starts nothing: push ${WORKFLOW_TRIGGER_PATH} to the repository's default branch`);
|
|
2159
2201
|
}
|
|
2202
|
+
const control = ask(['workflow', 'view', WORKFLOW_CONTROL_FILE]);
|
|
2203
|
+
if (control.answer === undefined)
|
|
2204
|
+
return fail(noSpawn);
|
|
2205
|
+
if (control.answer.kind === 'unknown')
|
|
2206
|
+
warnings.push(cannotTell(control.call, control.answer.why, `whether GitHub knows ${WORKFLOW_CONTROL_FILE}`));
|
|
2207
|
+
if (control.answer.kind === 'refused') {
|
|
2208
|
+
warnings.push(`GitHub does not know ${WORKFLOW_CONTROL_FILE} (${control.call}: ${control.answer.why}), so comments and reviews start nothing: push ${WORKFLOW_CONTROL_PATH} to the repository's default branch`);
|
|
2209
|
+
}
|
|
2160
2210
|
// The label's name is a repository variable, so an unread variable listing leaves nothing to compare.
|
|
2161
2211
|
const configured = variableValues?.get(TRIGGER_LABEL_VARIABLE)?.trim() ?? '';
|
|
2162
|
-
|
|
2212
|
+
let labelName = configured;
|
|
2213
|
+
if (configured === '') {
|
|
2214
|
+
// Unset, the committed workflow's own fallback starts a run; it is never re-rendered by an upgrade.
|
|
2215
|
+
let committed;
|
|
2216
|
+
try {
|
|
2217
|
+
committed = triggerFallbackLabel(readFileSync(join(root, ...WORKFLOW_TRIGGER_PATH.split('/')), 'utf8'));
|
|
2218
|
+
}
|
|
2219
|
+
catch {
|
|
2220
|
+
committed = undefined;
|
|
2221
|
+
}
|
|
2222
|
+
labelName = committed ?? DEFAULT_TRIGGER_LABEL;
|
|
2223
|
+
if (variableValues !== undefined && labelName === LEGACY_TRIGGER_LABEL) {
|
|
2224
|
+
notes.push(`${WORKFLOW_TRIGGER_PATH} was written by an earlier release and falls back to \`${LEGACY_TRIGGER_LABEL}\`, which keeps starting runs; \`${CLI} init --force\` re-renders it and the scripts to \`${DEFAULT_TRIGGER_LABEL}\`, and setting ${TRIGGER_LABEL_VARIABLE} keeps a name of your choosing under either`);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2163
2227
|
let labelFound = false;
|
|
2164
2228
|
if (variableValues === undefined) {
|
|
2165
2229
|
warnings.push(`cannot tell whether the trigger label exists: its name is the ${TRIGGER_LABEL_VARIABLE} variable, and ${variables.call} gave no readable answer`);
|
|
@@ -2186,14 +2250,39 @@ const REMOTE_GITHUB_CHECK = {
|
|
|
2186
2250
|
notes.push(`${TRIGGER_ALLOWED_BOTS_VARIABLE} admits ${nameList(bots)}, each of which can start a run without a permission check`);
|
|
2187
2251
|
}
|
|
2188
2252
|
}
|
|
2189
|
-
if (trigger.answer.kind === 'answered' && labelFound)
|
|
2190
|
-
triggerKnown = `; GitHub knows ${WORKFLOW_TRIGGER_FILE} and the label \`${labelName}\` exists`;
|
|
2253
|
+
if (trigger.answer.kind === 'answered' && control.answer.kind === 'answered' && labelFound) {
|
|
2254
|
+
triggerKnown = `; GitHub knows ${WORKFLOW_TRIGGER_FILE} and ${WORKFLOW_CONTROL_FILE}, and the label \`${labelName}\` exists`;
|
|
2255
|
+
}
|
|
2256
|
+
const prSetting = ask(['api', PR_SETTING_ENDPOINT]);
|
|
2257
|
+
if (prSetting.answer === undefined)
|
|
2258
|
+
return fail(noSpawn);
|
|
2259
|
+
if (prSetting.answer.kind === 'unknown') {
|
|
2260
|
+
warnings.push(cannotTell(prSetting.call, prSetting.answer.why, `whether a run's own token may open its pull request (${PR_CREATE_SETTING})`));
|
|
2261
|
+
}
|
|
2262
|
+
else if (prSetting.answer.kind === 'refused') {
|
|
2263
|
+
notes.push(`the pull-request setting was not checked: ${prSetting.call} may need more access than this login has (${prSetting.answer.why})`);
|
|
2264
|
+
}
|
|
2265
|
+
else {
|
|
2266
|
+
const allowed = prApprovalSettingOf(prSetting.answer.stdout);
|
|
2267
|
+
if (allowed === undefined) {
|
|
2268
|
+
warnings.push(`cannot tell whether a run's own token may open its pull request: ${prSetting.call} answered in a shape this check does not read`);
|
|
2269
|
+
}
|
|
2270
|
+
else if (!allowed && secretNames === undefined) {
|
|
2271
|
+
warnings.push(`${PR_CREATE_SETTING} is off, and whether ${GIT_TOKEN_SECRET} is set could not be read, so a completed run may not be able to open its draft pull request: turn the setting on under ${PR_CREATE_SETTING_PATH}, or confirm ${GIT_TOKEN_SECRET} is a repository secret`);
|
|
2272
|
+
}
|
|
2273
|
+
else if (!allowed && secretNames?.has(GIT_TOKEN_SECRET) === true) {
|
|
2274
|
+
notes.push(`${PR_CREATE_SETTING} is off, so a completed run opens its draft pull request with ${GIT_TOKEN_SECRET}`);
|
|
2275
|
+
}
|
|
2276
|
+
else if (!allowed) {
|
|
2277
|
+
warnings.push(`${PR_CREATE_SETTING} is off and ${GIT_TOKEN_SECRET} is not a repository secret, so a completed run cannot open its draft pull request with the job's token: turn it on under ${PR_CREATE_SETTING_PATH}, or set ${GIT_TOKEN_SECRET} with \`gh secret set ${GIT_TOKEN_SECRET}\``);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2191
2280
|
}
|
|
2192
2281
|
const noted = notes.length > 0 ? `; ${notes.join('; ')}` : '';
|
|
2193
2282
|
if (failures.length > 0)
|
|
2194
|
-
return fail(`${[...failures, ...warnings].join('; ')}${noted}`);
|
|
2283
|
+
return fail(`${[...failures, ...warnings].join('; ')}${triggerKnown}${noted}`);
|
|
2195
2284
|
if (warnings.length > 0)
|
|
2196
|
-
return warn(`${warnings.join('; ')}${noted}`);
|
|
2285
|
+
return warn(`${warnings.join('; ')}${triggerKnown}${noted}`);
|
|
2197
2286
|
const kept = retentionDays === undefined ? '' : `, and the repository keeps artifacts for ${retentionDays} days`;
|
|
2198
2287
|
return pass(`gh is authenticated, GitHub knows ${WORKFLOW_RUN_FILE} and ${WORKFLOW_RESUME_FILE}, a credential secret and ${PUSH_URL_SECRET} are set, and remote runs use ${runner}${kept}${triggerKnown}${noted}`);
|
|
2199
2288
|
},
|
|
@@ -2206,20 +2295,21 @@ const REMOTE_GITHUB_CHECK = {
|
|
|
2206
2295
|
* check speaks only about a value outside {@link FORGE_KINDS}. An absent key is a decision not yet
|
|
2207
2296
|
* made, and this line is where an operator learns the decision exists.
|
|
2208
2297
|
*
|
|
2209
|
-
* **Graded from local evidence only** (the module header's choice 3): the
|
|
2210
|
-
*
|
|
2211
|
-
*
|
|
2298
|
+
* **Graded from local evidence only** (the module header's choice 3): the two forge workflows —
|
|
2299
|
+
* {@link WORKFLOW_TRIGGER_FILE} and {@link WORKFLOW_CONTROL_FILE}, graded the same way — one git ref
|
|
2300
|
+
* and the configuration. What GitHub says — whether the label exists, which workflows it knows, and
|
|
2301
|
+
* the pull-request setting — is left to {@link REMOTE_GITHUB_CHECK}: the `github` pass names
|
|
2302
|
+
* `--check-github` when it is absent, and under it names that check, which {@link CHECKS} runs first.
|
|
2212
2303
|
*
|
|
2213
2304
|
* **Its worst grade is `warn`**: no `forge` state stops a run, because the inbox path works whatever
|
|
2214
2305
|
* the key says. The three `warn`s are all `github`: remote execution off, because a run started from
|
|
2215
|
-
* GitHub always executes through {@link WORKFLOW_RUN_FILE};
|
|
2216
|
-
*
|
|
2217
|
-
* only from its default branch — that last with
|
|
2218
|
-
* two *not graded* notes, on the same reasoning.
|
|
2219
|
-
*
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2222
|
-
* exists. A value outside {@link FORGE_KINDS} is the config check's `fail`, and is not graded here.
|
|
2306
|
+
* GitHub always executes through {@link WORKFLOW_RUN_FILE}; a forge workflow absent; and a forge
|
|
2307
|
+
* workflow not carried by `origin/<defaultBranch>`, because GitHub runs an `issues` or
|
|
2308
|
+
* `issue_comment` workflow only from its default branch — that last with
|
|
2309
|
+
* {@link REMOTE_EXECUTION_CHECK}'s push remedy and its two *not graded* notes, on the same reasoning.
|
|
2310
|
+
* Each warn names every file it is about, so two absent files are one line naming both.
|
|
2311
|
+
*
|
|
2312
|
+
* A value outside {@link FORGE_KINDS} is the config check's `fail`, and is not graded here.
|
|
2223
2313
|
*/
|
|
2224
2314
|
const FORGE_CHECK = {
|
|
2225
2315
|
id: 'forge',
|
|
@@ -2231,7 +2321,9 @@ const FORGE_CHECK = {
|
|
|
2231
2321
|
return unevaluated(`${CONFIG_FILENAME} could not be read (see the config check)`);
|
|
2232
2322
|
const root = ctx.repoRoot;
|
|
2233
2323
|
const forge = ctx.config.forge;
|
|
2234
|
-
const
|
|
2324
|
+
const forgeWorkflows = [WORKFLOW_TRIGGER_PATH, WORKFLOW_CONTROL_PATH];
|
|
2325
|
+
const presentWorkflows = forgeWorkflows.filter((path) => existsSync(join(root, ...path.split('/'))));
|
|
2326
|
+
const isAre = (paths) => (paths.length === 1 ? 'is' : 'are');
|
|
2235
2327
|
if (forge === undefined) {
|
|
2236
2328
|
return pass(`forge is not set, so the decision is not yet made and nothing starts a run from an issue. \`${CLI} config set forge github\`, with execution.target github-actions, turns the issue trigger on; \`${CLI} config set forge none\` records that this repository has no forge integration`);
|
|
2237
2329
|
}
|
|
@@ -2239,8 +2331,8 @@ const FORGE_CHECK = {
|
|
|
2239
2331
|
return pass('not graded, because forge holds a value this CLI does not know (see the config check)');
|
|
2240
2332
|
}
|
|
2241
2333
|
if (forge === 'none') {
|
|
2242
|
-
const left =
|
|
2243
|
-
? `. ${
|
|
2334
|
+
const left = presentWorkflows.length > 0
|
|
2335
|
+
? `. ${nameList(presentWorkflows)} ${isAre(presentWorkflows)} present and unused: the job each starts refuses every event while forge is not github`
|
|
2244
2336
|
: '';
|
|
2245
2337
|
return pass(`forge is none: this repository has no forge integration, and nothing starts a run from an issue${left}`);
|
|
2246
2338
|
}
|
|
@@ -2251,24 +2343,34 @@ const FORGE_CHECK = {
|
|
|
2251
2343
|
return warn(`forge is github, but remote execution is off (\`execution.target\`), so no trigger workflow is written: a run started from GitHub always executes through ${WORKFLOW_RUN_FILE}, because GitHub cannot reach this machine. Run \`${CLI} config set execution.target github-actions\`, then \`${CLI} init\`; to run such runs on your own hardware, register a self-hosted runner and name its label in the ${RUNNER_VARIABLE} repository variable (docs/remote-execution.md → ## 8. Choosing a runner)`);
|
|
2252
2344
|
}
|
|
2253
2345
|
const on = 'forge is github and remote execution is on';
|
|
2254
|
-
|
|
2255
|
-
|
|
2346
|
+
const startsNothing = {
|
|
2347
|
+
[WORKFLOW_TRIGGER_PATH]: 'labelling an issue starts nothing',
|
|
2348
|
+
[WORKFLOW_CONTROL_PATH]: 'comments and reviews start nothing',
|
|
2349
|
+
};
|
|
2350
|
+
const consequence = (paths) => paths.map((path) => startsNothing[path]).join(', and ');
|
|
2351
|
+
const absent = forgeWorkflows.filter((path) => !presentWorkflows.includes(path));
|
|
2352
|
+
if (absent.length > 0) {
|
|
2353
|
+
return warn(`${on}, but ${nameList(absent)} ${isAre(absent)} absent, so ${consequence(absent)}: re-run \`${CLI} init\`, which writes ${absent.length === 1 ? 'it' : 'them'} create-if-absent`);
|
|
2256
2354
|
}
|
|
2257
2355
|
const branch = ctx.config.defaultBranch;
|
|
2258
2356
|
let carried;
|
|
2259
2357
|
if (typeof branch !== 'string' || branch.trim() === '') {
|
|
2260
|
-
carried = `; whether GitHub's default branch carries
|
|
2358
|
+
carried = `; whether GitHub's default branch carries them is not graded, because defaultBranch is not a branch name (see the config check)`;
|
|
2261
2359
|
}
|
|
2262
2360
|
else if (!remoteTrackingBranchResolves(root, branch)) {
|
|
2263
|
-
carried = `; whether origin/${branch} carries
|
|
2264
|
-
}
|
|
2265
|
-
else if (!pathAtRef(root, `origin/${branch}`, WORKFLOW_TRIGGER_PATH)) {
|
|
2266
|
-
return warn(`${on}, but origin/${branch} does not carry ${WORKFLOW_TRIGGER_PATH}, as this checkout last fetched it, and GitHub runs an issues workflow only from its default branch, so labelling an issue starts nothing yet: commit it, then run \`${WORKFLOW_SCOPE_COMMAND}\`, then \`${defaultBranchPushCommand(branch)}\`. ${WORKFLOW_SCOPE_REASON} ${defaultBranchPushReason(branch)}`);
|
|
2361
|
+
carried = `; whether origin/${branch} carries them is not graded, because there is no origin/${branch} (see the remote check)`;
|
|
2267
2362
|
}
|
|
2268
2363
|
else {
|
|
2269
|
-
|
|
2364
|
+
const uncarried = forgeWorkflows.filter((path) => !pathAtRef(root, `origin/${branch}`, path));
|
|
2365
|
+
if (uncarried.length > 0) {
|
|
2366
|
+
return warn(`${on}, but origin/${branch} does not carry ${nameList(uncarried)}, as this checkout last fetched it, and GitHub runs an issues or issue_comment workflow only from its default branch, so ${consequence(uncarried)} yet: commit ${uncarried.length === 1 ? 'it' : 'them'}, then run \`${WORKFLOW_SCOPE_COMMAND}\`, then \`${defaultBranchPushCommand(branch)}\`. ${WORKFLOW_SCOPE_REASON} ${defaultBranchPushReason(branch)}`);
|
|
2367
|
+
}
|
|
2368
|
+
carried = ` and origin/${branch} carries them`;
|
|
2270
2369
|
}
|
|
2271
|
-
|
|
2370
|
+
const asked = ctx.probeGithub
|
|
2371
|
+
? `the ${REMOTE_GITHUB_CHECK.id} check above reports what GitHub says`
|
|
2372
|
+
: `\`${CLI} doctor --check-github\` asks GitHub`;
|
|
2373
|
+
return pass(`${on}: ${WORKFLOW_TRIGGER_PATH} and ${WORKFLOW_CONTROL_PATH} are present${carried}. Labelling an issue with the ${TRIGGER_LABEL_VARIABLE} label (default \`${DEFAULT_TRIGGER_LABEL}\`) starts a task run; a \`${COMMAND_HANDLE} <verb>\` comment (${nameList([...COMMAND_VERBS])}) steers it; a review requesting changes on the run's pull request starts a user-review round; and a completed run opens a draft pull request. What this cannot see lives on GitHub — the label, the workflows GitHub knows (${WORKFLOW_TRIGGER_FILE}, ${WORKFLOW_CONTROL_FILE}) and the pull-request setting; ${asked}`);
|
|
2272
2374
|
},
|
|
2273
2375
|
};
|
|
2274
2376
|
/**
|
|
@@ -3946,6 +4048,10 @@ const BROWSER_WIRING_CHECK = {
|
|
|
3946
4048
|
};
|
|
3947
4049
|
/** The pass every retrieval check gives when {@link retrievalApplies} is false. */
|
|
3948
4050
|
const RETRIEVAL_OFF = 'docs.retrieval is off (it needs phases.docs and docs.retrieval both true), so no RAG library is expected and none was resolved';
|
|
4051
|
+
/** The pass every `retrieval-python-*` check gives when retrieval is on under another backend. */
|
|
4052
|
+
const PYTHON_NOT_SELECTED = 'docs.retrieval is on with docs.retrievalBackend not python, so the launcher starts the TypeScript runtime and nothing of the Python backend is expected or checked';
|
|
4053
|
+
/** The pass the three TypeScript retrieval checks give when {@link pythonRetrievalApplies} is true. */
|
|
4054
|
+
const TYPESCRIPT_NOT_SELECTED = 'docs.retrievalBackend is python, so the launcher starts the Python backend rather than this runtime and it is not graded; init still installs it, so switching back costs nothing';
|
|
3949
4055
|
/** How many missing model files {@link RETRIEVAL_MODEL_CACHE_CHECK} names before it counts the rest. */
|
|
3950
4056
|
const MISSING_MODEL_FILES_NAMED = 5;
|
|
3951
4057
|
/**
|
|
@@ -3973,6 +4079,8 @@ const RETRIEVAL_DEPENDENCIES_CHECK = {
|
|
|
3973
4079
|
return unevaluated(`${CONFIG_FILENAME} could not be read (see the config check)`);
|
|
3974
4080
|
if (!retrievalApplies(ctx.config))
|
|
3975
4081
|
return pass(RETRIEVAL_OFF);
|
|
4082
|
+
if (pythonRetrievalApplies(ctx.config))
|
|
4083
|
+
return pass(TYPESCRIPT_NOT_SELECTED);
|
|
3976
4084
|
const runtime = retrievalRuntimeDir();
|
|
3977
4085
|
const state = retrievalRuntimeState();
|
|
3978
4086
|
if (state.installed) {
|
|
@@ -3992,6 +4100,8 @@ const RETRIEVAL_MODEL_CACHE_CHECK = {
|
|
|
3992
4100
|
return unevaluated(`${CONFIG_FILENAME} could not be read (see the config check)`);
|
|
3993
4101
|
if (!retrievalApplies(ctx.config))
|
|
3994
4102
|
return pass(RETRIEVAL_OFF);
|
|
4103
|
+
if (pythonRetrievalApplies(ctx.config))
|
|
4104
|
+
return pass(TYPESCRIPT_NOT_SELECTED);
|
|
3995
4105
|
const dir = retrievalModelCacheDir();
|
|
3996
4106
|
const { present, missing } = modelFilesPresent(dir);
|
|
3997
4107
|
if (present)
|
|
@@ -4035,6 +4145,8 @@ const RETRIEVAL_INDEX_CHECK = {
|
|
|
4035
4145
|
return unevaluated(`${CONFIG_FILENAME} could not be read (see the config check)`);
|
|
4036
4146
|
if (!retrievalApplies(ctx.config))
|
|
4037
4147
|
return pass(RETRIEVAL_OFF);
|
|
4148
|
+
if (pythonRetrievalApplies(ctx.config))
|
|
4149
|
+
return pass(TYPESCRIPT_NOT_SELECTED);
|
|
4038
4150
|
const resolved = retrievalCliEntry();
|
|
4039
4151
|
if (resolved === undefined)
|
|
4040
4152
|
return fail('cannot build without the RAG libraries (see retrieval-dependencies)');
|
|
@@ -4059,6 +4171,166 @@ const RETRIEVAL_INDEX_CHECK = {
|
|
|
4059
4171
|
}
|
|
4060
4172
|
},
|
|
4061
4173
|
};
|
|
4174
|
+
const pythonSelfCheckCache = new WeakMap();
|
|
4175
|
+
/** The `self-check` question each `retrieval-python-*` check id grades, for the index's cross-reference. */
|
|
4176
|
+
const PYTHON_CHECK_ID = Object.freeze({
|
|
4177
|
+
packages: 'retrieval-python-dependencies',
|
|
4178
|
+
weights: 'retrieval-python-model-cache',
|
|
4179
|
+
index: 'retrieval-python-index',
|
|
4180
|
+
});
|
|
4181
|
+
/**
|
|
4182
|
+
* Run the Python backend's own `self-check` once per {@link CheckContext}; the three
|
|
4183
|
+
* `retrieval-python-*` checks grade its lines and never re-derive them (choice 1).
|
|
4184
|
+
*
|
|
4185
|
+
* The command is resolved on {@link launcherSearchPath} — the `PATH` the launcher resolves it on —
|
|
4186
|
+
* not on this process's own, so a pass here is a pass on the server the launcher starts.
|
|
4187
|
+
*
|
|
4188
|
+
* **`spawnSync` rather than `execFileSync`:** `self-check` exits 1 whenever a line is `FAIL`, with its
|
|
4189
|
+
* whole answer on stdout, and `execFileSync` would turn that answer into a thrown error.
|
|
4190
|
+
*
|
|
4191
|
+
* **The env is the server's, not the shell's.** `process.env` is overlaid by the string entries of the
|
|
4192
|
+
* `.mcp.json` server's `env` object, which the agent runner passes to the server, and then
|
|
4193
|
+
* {@link PYTHON_DATABASE_URL_VARIABLE} is set to {@link pythonDatabaseUrl}'s answer from that object
|
|
4194
|
+
* alone: the runner never passes a shell export, so honouring one here would grade a database the
|
|
4195
|
+
* server does not use.
|
|
4196
|
+
*/
|
|
4197
|
+
function pythonSelfCheck(ctx, repoRoot) {
|
|
4198
|
+
const cached = pythonSelfCheckCache.get(ctx);
|
|
4199
|
+
if (cached !== undefined)
|
|
4200
|
+
return cached;
|
|
4201
|
+
const answer = runPythonSelfCheck(repoRoot);
|
|
4202
|
+
pythonSelfCheckCache.set(ctx, answer);
|
|
4203
|
+
return answer;
|
|
4204
|
+
}
|
|
4205
|
+
function runPythonSelfCheck(repoRoot) {
|
|
4206
|
+
const searched = launcherSearchPath(process.env['PATH'] ?? '', process.env['HOME']);
|
|
4207
|
+
const directory = locateOnPath(PYTHON_RETRIEVAL_COMMAND, searched);
|
|
4208
|
+
if (directory === undefined)
|
|
4209
|
+
return { kind: 'unresolved', searched };
|
|
4210
|
+
const mcp = readJsonFile(join(repoRoot, MCP_PATH));
|
|
4211
|
+
const servers = isJsonObject(mcp) ? mcp[SERVERS_KEY] : undefined;
|
|
4212
|
+
const server = isJsonObject(servers) ? servers[DOCS_SERVER_NAME] : undefined;
|
|
4213
|
+
const serverEnv = isJsonObject(server) ? server['env'] : undefined;
|
|
4214
|
+
const overlay = {};
|
|
4215
|
+
if (isJsonObject(serverEnv)) {
|
|
4216
|
+
for (const [key, value] of Object.entries(serverEnv)) {
|
|
4217
|
+
if (typeof value === 'string')
|
|
4218
|
+
overlay[key] = value;
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
const databaseUrl = pythonDatabaseUrl(serverEnv);
|
|
4222
|
+
const child = spawnSync(join(directory, PYTHON_RETRIEVAL_COMMAND), [PYTHON_SELF_CHECK_SUB_COMMAND, '--repo', repoRoot], {
|
|
4223
|
+
encoding: 'utf8',
|
|
4224
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4225
|
+
timeout: RETRIEVAL_INDEX_TIMEOUT_MS,
|
|
4226
|
+
env: { ...process.env, ...overlay, [PYTHON_DATABASE_URL_VARIABLE]: databaseUrl },
|
|
4227
|
+
});
|
|
4228
|
+
if (child.error !== undefined)
|
|
4229
|
+
return { kind: 'unreadable', text: messageOf(child.error) };
|
|
4230
|
+
const lines = child.status === 0 || child.status === 1 ? parseSelfCheck(child.stdout ?? '') : undefined;
|
|
4231
|
+
if (lines !== undefined)
|
|
4232
|
+
return { kind: 'answered', lines, databaseUrl };
|
|
4233
|
+
const how = child.status === null ? `stopped by ${child.signal}` : `exit status ${child.status}`;
|
|
4234
|
+
const said = firstLine(child.stderr ?? '') || firstLine(child.stdout ?? '');
|
|
4235
|
+
return { kind: 'unreadable', text: said === '' ? how : `${how}: ${said}` };
|
|
4236
|
+
}
|
|
4237
|
+
/** One line of an answered `self-check`; `parseSelfCheck` guarantees all three are present. */
|
|
4238
|
+
function selfCheckLine(answer, question) {
|
|
4239
|
+
const line = answer.lines.get(question);
|
|
4240
|
+
if (line === undefined)
|
|
4241
|
+
throw internal(`self-check answered without its ${question} line, which parseSelfCheck guarantees`);
|
|
4242
|
+
return line;
|
|
4243
|
+
}
|
|
4244
|
+
/**
|
|
4245
|
+
* Host, port and database of a connection string, with the credentials dropped — a check's text is
|
|
4246
|
+
* printed and may be pasted, so the password never reaches it.
|
|
4247
|
+
*/
|
|
4248
|
+
function databaseLocation(url) {
|
|
4249
|
+
try {
|
|
4250
|
+
const parsed = new URL(url);
|
|
4251
|
+
const port = parsed.port === '' ? '' : `:${parsed.port}`;
|
|
4252
|
+
return `${parsed.hostname}${port}${parsed.pathname}`;
|
|
4253
|
+
}
|
|
4254
|
+
catch {
|
|
4255
|
+
return 'a connection string this check cannot parse (not printed, as it may carry a password)';
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
/** The install remedy every `retrieval-python-dependencies` failure names. */
|
|
4259
|
+
const PYTHON_INSTALL_REMEDY = "install the package with its `models` extra (docs/retrieval.md → `## Turning on the Python backend`)";
|
|
4260
|
+
/** Do the Python backend's console script, interpreter and packages resolve? */
|
|
4261
|
+
const RETRIEVAL_PYTHON_DEPENDENCIES_CHECK = {
|
|
4262
|
+
id: 'retrieval-python-dependencies',
|
|
4263
|
+
title: "RAG's Python backend resolves",
|
|
4264
|
+
run: (ctx) => {
|
|
4265
|
+
if (ctx.repoRoot === undefined)
|
|
4266
|
+
return unevaluated('the repository root did not resolve (see the git check)');
|
|
4267
|
+
if (ctx.config === undefined)
|
|
4268
|
+
return unevaluated(`${CONFIG_FILENAME} could not be read (see the config check)`);
|
|
4269
|
+
if (!retrievalApplies(ctx.config))
|
|
4270
|
+
return pass(RETRIEVAL_OFF);
|
|
4271
|
+
if (!pythonRetrievalApplies(ctx.config))
|
|
4272
|
+
return pass(PYTHON_NOT_SELECTED);
|
|
4273
|
+
const answer = pythonSelfCheck(ctx, ctx.repoRoot);
|
|
4274
|
+
if (answer.kind === 'unresolved') {
|
|
4275
|
+
return fail(`${PYTHON_RETRIEVAL_COMMAND} does not resolve on the launcher's PATH (${answer.searched}), so the search server never starts — ${PYTHON_INSTALL_REMEDY}`);
|
|
4276
|
+
}
|
|
4277
|
+
if (answer.kind === 'unreadable') {
|
|
4278
|
+
return fail(`${PYTHON_RETRIEVAL_COMMAND} ${PYTHON_SELF_CHECK_SUB_COMMAND} answered in a shape this CLI cannot grade (${answer.text}) — ${PYTHON_INSTALL_REMEDY}, from a clone at the release tag matching this CLI's version, then run doctor again`);
|
|
4279
|
+
}
|
|
4280
|
+
const line = selfCheckLine(answer, 'packages');
|
|
4281
|
+
return line.ok ? pass(line.detail) : fail(`${line.detail} — ${PYTHON_INSTALL_REMEDY}`);
|
|
4282
|
+
},
|
|
4283
|
+
};
|
|
4284
|
+
/** Are the Python backend's model weights in its cache? */
|
|
4285
|
+
const RETRIEVAL_PYTHON_MODEL_CACHE_CHECK = {
|
|
4286
|
+
id: 'retrieval-python-model-cache',
|
|
4287
|
+
title: "RAG's Python backend weights are cached",
|
|
4288
|
+
run: (ctx) => {
|
|
4289
|
+
if (ctx.repoRoot === undefined)
|
|
4290
|
+
return unevaluated('the repository root did not resolve (see the git check)');
|
|
4291
|
+
if (ctx.config === undefined)
|
|
4292
|
+
return unevaluated(`${CONFIG_FILENAME} could not be read (see the config check)`);
|
|
4293
|
+
if (!retrievalApplies(ctx.config))
|
|
4294
|
+
return pass(RETRIEVAL_OFF);
|
|
4295
|
+
if (!pythonRetrievalApplies(ctx.config))
|
|
4296
|
+
return pass(PYTHON_NOT_SELECTED);
|
|
4297
|
+
const answer = pythonSelfCheck(ctx, ctx.repoRoot);
|
|
4298
|
+
if (answer.kind !== 'answered') {
|
|
4299
|
+
return fail(`cannot be checked without the Python backend (see ${PYTHON_CHECK_ID.packages})`);
|
|
4300
|
+
}
|
|
4301
|
+
const line = selfCheckLine(answer, 'weights');
|
|
4302
|
+
if (line.ok)
|
|
4303
|
+
return pass(line.detail);
|
|
4304
|
+
return fail(`${line.detail} — run \`${PYTHON_RETRIEVAL_COMMAND} ${PYTHON_FETCH_MODELS_SUB_COMMAND}\` where an operator is present. An unattended run has no web access, so a weight missing now is never fetched later`);
|
|
4305
|
+
},
|
|
4306
|
+
};
|
|
4307
|
+
/** Does the Python backend build its index in its database? */
|
|
4308
|
+
const RETRIEVAL_PYTHON_INDEX_CHECK = {
|
|
4309
|
+
id: 'retrieval-python-index',
|
|
4310
|
+
title: "the RAG Python backend's index builds",
|
|
4311
|
+
run: (ctx) => {
|
|
4312
|
+
if (ctx.repoRoot === undefined)
|
|
4313
|
+
return unevaluated('the repository root did not resolve (see the git check)');
|
|
4314
|
+
if (ctx.config === undefined)
|
|
4315
|
+
return unevaluated(`${CONFIG_FILENAME} could not be read (see the config check)`);
|
|
4316
|
+
if (!retrievalApplies(ctx.config))
|
|
4317
|
+
return pass(RETRIEVAL_OFF);
|
|
4318
|
+
if (!pythonRetrievalApplies(ctx.config))
|
|
4319
|
+
return pass(PYTHON_NOT_SELECTED);
|
|
4320
|
+
const answer = pythonSelfCheck(ctx, ctx.repoRoot);
|
|
4321
|
+
if (answer.kind !== 'answered') {
|
|
4322
|
+
return fail(`cannot be checked without the Python backend (see ${PYTHON_CHECK_ID.packages})`);
|
|
4323
|
+
}
|
|
4324
|
+
const line = selfCheckLine(answer, 'index');
|
|
4325
|
+
if (line.ok)
|
|
4326
|
+
return pass(line.detail);
|
|
4327
|
+
const stoppedBy = SELF_CHECK_INDEX_NOT_ATTEMPTED.exec(line.detail)?.[1];
|
|
4328
|
+
if (stoppedBy !== undefined) {
|
|
4329
|
+
return fail(`cannot build without the Python backend's ${stoppedBy} (see ${PYTHON_CHECK_ID[stoppedBy]})`);
|
|
4330
|
+
}
|
|
4331
|
+
return fail(`${line.detail} — the backend's database is ${databaseLocation(answer.databaseUrl)} (${MCP_PATH}'s ${DOCS_SERVER_NAME} \`env\` ${PYTHON_DATABASE_URL_VARIABLE}, else the compose default): start the bundled one by running \`docker compose up -d --wait postgres\` in docs-retrieval-service/ of a clone of this CLI's repository at its release tag (docs/retrieval.md → \`## Turning on the Python backend\`, step 2), or point ${PYTHON_DATABASE_URL_VARIABLE} in that \`env\` object at yours`);
|
|
4332
|
+
},
|
|
4333
|
+
};
|
|
4062
4334
|
/**
|
|
4063
4335
|
* The checks, in the order they are evaluated and reported: the host and the tools first, then the
|
|
4064
4336
|
* repository's own wiring, then the permission profile and the browser half that depends on it.
|
|
@@ -4149,7 +4421,9 @@ const RETRIEVAL_INDEX_CHECK = {
|
|
|
4149
4421
|
* profile lines all passed reads it as the last thing that can still be missing from that file.
|
|
4150
4422
|
*
|
|
4151
4423
|
* The three `retrieval-*` checks come last, under `browser-wiring`: the runtime, the models, then
|
|
4152
|
-
* whether an index builds, which needs libraries and models both and so reads after them.
|
|
4424
|
+
* whether an index builds, which needs libraries and models both and so reads after them. The three
|
|
4425
|
+
* `retrieval-python-*` checks follow them in the same runtime → models → index order; whichever
|
|
4426
|
+
* backend `docs.retrievalBackend` does not select passes its three as not applicable.
|
|
4153
4427
|
*/
|
|
4154
4428
|
export const CHECKS = Object.freeze([
|
|
4155
4429
|
GIT_CHECK,
|
|
@@ -4192,6 +4466,9 @@ export const CHECKS = Object.freeze([
|
|
|
4192
4466
|
RETRIEVAL_DEPENDENCIES_CHECK,
|
|
4193
4467
|
RETRIEVAL_MODEL_CACHE_CHECK,
|
|
4194
4468
|
RETRIEVAL_INDEX_CHECK,
|
|
4469
|
+
RETRIEVAL_PYTHON_DEPENDENCIES_CHECK,
|
|
4470
|
+
RETRIEVAL_PYTHON_MODEL_CACHE_CHECK,
|
|
4471
|
+
RETRIEVAL_PYTHON_INDEX_CHECK,
|
|
4195
4472
|
]);
|
|
4196
4473
|
/**
|
|
4197
4474
|
* Evaluate every check in order and return one result each.
|