autonomous-sdlc-harness 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/cli.js +0 -0
- package/dist/commands/docs.js +220 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/doctor.js +32 -11
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +240 -64
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/registry.js +2 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/config/check.js +36 -9
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +43 -1
- package/dist/config/model.js.map +1 -1
- package/dist/core/git.js +29 -0
- package/dist/core/git.js.map +1 -1
- package/dist/core/layerGapRemedy.js +3 -2
- package/dist/core/layerGapRemedy.js.map +1 -1
- package/dist/core/paths.js +22 -2
- package/dist/core/paths.js.map +1 -1
- package/dist/core/pluginIdentity.js +33 -0
- package/dist/core/pluginIdentity.js.map +1 -0
- package/dist/core/prompt.js +6 -2
- package/dist/core/prompt.js.map +1 -1
- package/dist/core/report.js +9 -0
- package/dist/core/report.js.map +1 -1
- package/dist/core/writer.js +24 -5
- package/dist/core/writer.js.map +1 -1
- package/dist/detect/presets.js +35 -26
- package/dist/detect/presets.js.map +1 -1
- package/dist/detect/signals.js +13 -9
- package/dist/detect/signals.js.map +1 -1
- package/dist/doctor/checks.js +483 -38
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/claudeContext.js +4 -5
- package/dist/generators/claudeContext.js.map +1 -1
- package/dist/generators/githubWorkflows.js +66 -0
- package/dist/generators/githubWorkflows.js.map +1 -0
- package/dist/generators/harnessConfig.js +13 -5
- package/dist/generators/harnessConfig.js.map +1 -1
- package/dist/generators/notifications.js +105 -19
- package/dist/generators/notifications.js.map +1 -1
- package/dist/generators/outerLoopScripts.js +30 -3
- package/dist/generators/outerLoopScripts.js.map +1 -1
- package/dist/generators/permissionProfile.js +152 -21
- package/dist/generators/permissionProfile.js.map +1 -1
- package/dist/generators/projectSettings.js +5 -15
- package/dist/generators/projectSettings.js.map +1 -1
- package/dist/generators/repoRoot.js +148 -25
- package/dist/generators/repoRoot.js.map +1 -1
- package/dist/generators/scripts.js +4 -1
- package/dist/generators/scripts.js.map +1 -1
- package/dist/generators/stateDir.js +9 -3
- package/dist/generators/stateDir.js.map +1 -1
- package/dist/machine/paths.js +19 -4
- package/dist/machine/paths.js.map +1 -1
- package/dist/machine/plugins.js +2 -1
- package/dist/machine/plugins.js.map +1 -1
- package/dist/remote/githubActions.js +86 -0
- package/dist/remote/githubActions.js.map +1 -0
- package/dist/retrieval/chunk.js +158 -0
- package/dist/retrieval/chunk.js.map +1 -0
- package/dist/retrieval/corpus.js +75 -0
- package/dist/retrieval/corpus.js.map +1 -0
- package/dist/retrieval/models.js +175 -0
- package/dist/retrieval/models.js.map +1 -0
- package/dist/retrieval/queryLog.js +70 -0
- package/dist/retrieval/queryLog.js.map +1 -0
- package/dist/retrieval/refresh.js +55 -0
- package/dist/retrieval/refresh.js.map +1 -0
- package/dist/retrieval/runtime.js +161 -0
- package/dist/retrieval/runtime.js.map +1 -0
- package/dist/retrieval/search.js +129 -0
- package/dist/retrieval/search.js.map +1 -0
- package/dist/retrieval/server.js +197 -0
- package/dist/retrieval/server.js.map +1 -0
- package/dist/retrieval/session.js +41 -0
- package/dist/retrieval/session.js.map +1 -0
- package/dist/retrieval/setup.js +121 -0
- package/dist/retrieval/setup.js.map +1 -0
- package/dist/retrieval/store.js +170 -0
- package/dist/retrieval/store.js.map +1 -0
- package/package.json +22 -3
- package/templates/README.md +3 -2
- package/templates/claude/CLAUDE.md +4 -4
- package/templates/claude/README.md +3 -1
- package/templates/claude/context/conventions.md +1 -1
- package/templates/claude/context/layer.md +1 -1
- package/templates/claude/push-notify.env.example +7 -2
- package/templates/claude/settings.autonomous.json +1 -1
- package/templates/claude/settings.autonomous.retrieval.json +9 -0
- package/templates/github/workflows/harness-resume.yml +124 -0
- package/templates/github/workflows/harness-run.yml +446 -0
- package/templates/repo/README.md +2 -0
- package/templates/repo/gitignore +6 -0
- package/templates/repo/gitignore.retrieval +2 -0
- package/templates/repo/mcp.retrieval.json +11 -0
- package/templates/scripts/README.md +1 -1
- package/templates/scripts/autonomous-notify.sh +10 -4
- package/templates/scripts/autonomous-watcher.sh +1445 -221
- package/templates/scripts/cleanup-merged-worktrees.sh +126 -8
- package/templates/scripts/docs-search-server.sh +64 -0
- package/templates/scripts/flow-walker.sh +629 -0
- package/templates/scripts/flows/task_plan_writing.graph.json +192 -0
- package/templates/scripts/lib/flow-walker-gates.sh +165 -0
- package/templates/scripts/lib/harness-run-lib.sh +452 -21
- package/templates/scripts/remote-run.sh +1785 -0
- package/templates/scripts/restart-watcher.sh +24 -3
- package/templates/scripts/run-test-suite.sh +182 -0
- package/templates/scripts/scratch-run.sh +2 -1
- package/templates/state-dir/README-root.md +1 -1
- package/templates/state-dir/autonomous_logs/README.md +1 -1
- package/templates/state-dir/business_parity_reviews/README.md +1 -1
- package/templates/state-dir/clarification_digests/README.md +1 -1
- package/templates/state-dir/clarifications/README.md +4 -4
- package/templates/state-dir/improvement_observations/README.md +2 -0
- package/templates/state-dir/scratch/README.md +1 -1
- package/templates/state-dir/test_fix_plan_reviews/README.md +9 -0
- package/templates/state-dir/test_fix_plans/README.md +9 -0
- package/templates/state-dir/test_fix_point_reviews/README.md +9 -0
- package/templates/state-dir/test_run_logs/README.md +11 -0
package/dist/commands/init.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* reproducible: a second `init` over the same repository produces the same result, and an adopter can
|
|
8
8
|
* re-derive the recorded preset from the detection table by hand. It is also why judgement-dependent
|
|
9
9
|
* work — reading real code to refine a layer profile, filling a conventions stub — belongs to
|
|
10
|
-
* `/harness-analyze` and is deliberately absent from this file. The offer to run it
|
|
10
|
+
* `/autonomous-sdlc-harness:harness-analyze` and is deliberately absent from this file. The offer to run it
|
|
11
11
|
* ({@link resolveAnalyzeOffer}) adds no model call and no external process here: the answer's whole
|
|
12
12
|
* material effect is which banner wording the generated always-loaded file carries and which closing
|
|
13
13
|
* pointer this run prints.
|
|
@@ -91,7 +91,7 @@ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
|
91
91
|
import { join, relative, resolve } from 'node:path';
|
|
92
92
|
import { formatProblem } from '../config/check.js';
|
|
93
93
|
import { configExists, loadConfig } from '../config/io.js';
|
|
94
|
-
import { answersNone, asQaDriver, browserWiringApplies, CONFIG_FILENAME, DEFAULTS, isPlaceholder, qaDriverChoices, STATE_DIR_DOT_PATTERN, } from '../config/model.js';
|
|
94
|
+
import { answersNone, asQaDriver, browserWiringApplies, CONFIG_FILENAME, DEFAULTS, isPlaceholder, qaDriverChoices, retrievalApplies, STATE_DIR_DOT_PATTERN, } from '../config/model.js';
|
|
95
95
|
import { EXIT, HarnessError } from '../core/errors.js';
|
|
96
96
|
import { commitAll, hasCommits, initRepository, probeRepoRoot } from '../core/git.js';
|
|
97
97
|
import { readJsonFile } from '../core/json.js';
|
|
@@ -99,7 +99,8 @@ import { layerCoverage } from '../core/layerCoverage.js';
|
|
|
99
99
|
import { layerGapRemedy, recordedVerdictClause } from '../core/layerGapRemedy.js';
|
|
100
100
|
import { nameList } from '../core/nameList.js';
|
|
101
101
|
import { insideRepo, packageRoot } from '../core/paths.js';
|
|
102
|
-
import {
|
|
102
|
+
import { ANALYZE_COMMAND } from '../core/pluginIdentity.js';
|
|
103
|
+
import { askLine, askYesNo, canPrompt, REPROMPT_LIMIT } from '../core/prompt.js';
|
|
103
104
|
import { normalizeRepoDir, normalizeRepoPathStrict } from '../core/repoPaths.js';
|
|
104
105
|
import { WritePlan } from '../core/writer.js';
|
|
105
106
|
import { findNestedApplicationDir } from '../detect/nestedApplication.js';
|
|
@@ -107,38 +108,21 @@ import { buildPreset, commandSourceClaim, searchedRoots, SHARED_CONVENTIONS_PATH
|
|
|
107
108
|
import { detectPreset, parsePresetName, FLAT_FALLBACK_SIGNAL_ID, FORCED_SIGNAL_ID, } from '../detect/signals.js';
|
|
108
109
|
import { writeClaudeContext, CLAUDE_MD_PATH, RESERVED_ANALYZE_TARGETS, SETUP_PENDING_OPEN, isUntouchedSkeletonText, } from '../generators/claudeContext.js';
|
|
109
110
|
import { pointHooksPath, writeGitHooks } from '../generators/githooks.js';
|
|
111
|
+
import { writeGithubWorkflows } from '../generators/githubWorkflows.js';
|
|
110
112
|
import { writeHarnessConfig } from '../generators/harnessConfig.js';
|
|
111
|
-
import { writeNotifications,
|
|
113
|
+
import { writeNotifications, PUSH_DESTINATION_FORMS, PUSH_DESTINATION_PLACEHOLDER, resolvePushDestination, UNRECOGNISED_DESTINATION_NOTE, } from '../generators/notifications.js';
|
|
112
114
|
import { writeOuterLoopScripts } from '../generators/outerLoopScripts.js';
|
|
113
|
-
import { writePermissionProfile } from '../generators/permissionProfile.js';
|
|
114
|
-
import { writeProjectSettings, MARKETPLACE_FLAG, MARKETPLACE_NAME, MARKETPLACES_KEY,
|
|
115
|
+
import { PLUGIN_ROOT_ENTRIES_FLAG, pluginRootEntriesNote, writePermissionProfile, } from '../generators/permissionProfile.js';
|
|
116
|
+
import { writeProjectSettings, MARKETPLACE_FLAG, MARKETPLACE_NAME, MARKETPLACES_KEY, SETTINGS_PATH, SLUG_SHAPE, } from '../generators/projectSettings.js';
|
|
115
117
|
import { writeRepoRootFiles, MCP_PATH } from '../generators/repoRoot.js';
|
|
116
118
|
import { configKeyPath, configuredCommand, wrapperCommandLine, wrapperPath, writeWrapperScripts, WRAPPER_SCRIPTS, } from '../generators/scripts.js';
|
|
117
119
|
import { writeStateDir } from '../generators/stateDir.js';
|
|
120
|
+
import { API_KEY_SECRET, GIT_TOKEN_SECRET, OAUTH_TOKEN_SECRET, PUSH_URL_SECRET, RUNNER_VARIABLE, } from '../remote/githubActions.js';
|
|
121
|
+
import { setUpRetrieval } from '../retrieval/setup.js';
|
|
118
122
|
/** The command's one-line summary, in the usage block and at the head of its own `--help`. */
|
|
119
123
|
const SUMMARY = 'Wire a repository: config, scripts, state tree, permission profile, project settings, conventions stubs';
|
|
120
124
|
/** The roadmap item this command belongs to, as the registry reports it. */
|
|
121
125
|
const ROADMAP_ITEM = 13;
|
|
122
|
-
/** The analyze command's own name, without the leading `/` and without the plugin prefix. */
|
|
123
|
-
const ANALYZE_COMMAND_NAME = 'harness-analyze';
|
|
124
|
-
/** The analyze command as a line addressing the adopter names it — step C of the install story. */
|
|
125
|
-
const ANALYZE_COMMAND = `/${ANALYZE_COMMAND_NAME}`;
|
|
126
|
-
/**
|
|
127
|
-
* The same command, spelled as something **executed** rather than read.
|
|
128
|
-
*
|
|
129
|
-
* A session's `/` picker lists every command of this plugin under the plugin's own prefix and
|
|
130
|
-
* fuzzy-matches a bare name onto it, so {@link ANALYZE_COMMAND} reaches the command wherever a
|
|
131
|
-
* person types it into a session. A command passed as a session's **first message** — which is
|
|
132
|
-
* what {@link ANALYZE_INVOCATION} does — meets no picker and is matched exactly, so the bare
|
|
133
|
-
* form fails there with `Unknown command`. That the **prefixed** form succeeds where the bare
|
|
134
|
-
* one fails is not established: the only measurement in this tree is headless and negative on
|
|
135
|
-
* both spellings (root `README.md`, `### Measured while building that evidence, and not fixed
|
|
136
|
-
* here`), and the interactive first-message form waits on the hand-run gate — if that comes back
|
|
137
|
-
* negative the printed line is dropped rather than respelled (`docs/analyze.md` §9). The prefix
|
|
138
|
-
* is taken from {@link PLUGIN_NAME}, which mirrors the plugin manifest, rather than written out
|
|
139
|
-
* here.
|
|
140
|
-
*/
|
|
141
|
-
const ANALYZE_COMMAND_QUALIFIED = `/${PLUGIN_NAME}:${ANALYZE_COMMAND_NAME}`;
|
|
142
126
|
/**
|
|
143
127
|
* How this CLI is typed, for every line that tells an adopter to run something — stated here once
|
|
144
128
|
* for the whole command surface.
|
|
@@ -181,6 +165,8 @@ const DAEMON_INSTALL_COMMAND = `${CLI} daemon install`;
|
|
|
181
165
|
* `browserWiringApplies` holds (`doctor/checks.ts`'s browser-wiring check, `commands/doctor.ts`).
|
|
182
166
|
*/
|
|
183
167
|
const DOCTOR_CHECK_REGISTRY_COMMAND = `${DOCTOR_COMMAND} --check-registry`;
|
|
168
|
+
/** The online check of GitHub-side setup, named last in the remote-execution block ({@link reportGithubSteps}). */
|
|
169
|
+
const DOCTOR_CHECK_GITHUB_COMMAND = `${DOCTOR_COMMAND} --check-github`;
|
|
184
170
|
/**
|
|
185
171
|
* How `.mcp.json` launches those servers, as the sentence names it.
|
|
186
172
|
*
|
|
@@ -199,7 +185,27 @@ const MCP_LAUNCHER = 'npx -y';
|
|
|
199
185
|
*/
|
|
200
186
|
const AGENT_CLI = 'claude';
|
|
201
187
|
const MARKETPLACE_ADD_COMMAND = `${AGENT_CLI} plugin marketplace add ${SLUG_SHAPE}`;
|
|
202
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Step C of the install story as a first message: {@link ANALYZE_COMMAND} is the one spelling both
|
|
190
|
+
* for the name a person types into an open session and for this. The interactive first-message form
|
|
191
|
+
* waits on the hand-run gate — if that comes back negative the printed line is dropped rather than
|
|
192
|
+
* respelled (`docs/analyze.md` §9).
|
|
193
|
+
*
|
|
194
|
+
* Headless first-message leg, re-measured: Claude Code 2.1.274, from this checkout with the plugin
|
|
195
|
+
* enabled; the `system`/`init` event of both runs listed `autonomous-sdlc-harness:harness-analyze`
|
|
196
|
+
* among its slash commands and did not list `harness-analyze`.
|
|
197
|
+
* - `claude -p "/autonomous-sdlc-harness:harness-analyze" --permission-mode plan --max-turns 2 --output-format stream-json --verbose`:
|
|
198
|
+
* exit 1; no `Skill` call, the first tool call was the command's own `Bash` read of the
|
|
199
|
+
* unfilled-stub markers; final `result` event `is_error: true`, `result: null`, verbatim message
|
|
200
|
+
* `Reached maximum number of turns (2)`.
|
|
201
|
+
* - `claude -p "/harness-analyze" --permission-mode plan --max-turns 2 --output-format stream-json --verbose`:
|
|
202
|
+
* exit 1; the first tool call was `Skill` naming `autonomous-sdlc-harness:harness-analyze`, which
|
|
203
|
+
* loaded the command; final `result` event `is_error: true`, `result: null`, verbatim message
|
|
204
|
+
* `Reached maximum number of turns (2)`.
|
|
205
|
+
* Both exits are the two-turn cap, not a resolution failure: the prefixed spelling ran the command
|
|
206
|
+
* directly, and the bare one reached it only through the model choosing the `Skill` tool.
|
|
207
|
+
*/
|
|
208
|
+
const ANALYZE_INVOCATION = `${AGENT_CLI} "${ANALYZE_COMMAND}"`;
|
|
203
209
|
/** The verb that applies a layer-profile revision — the one writer of `layers[]` (`docs/analyze.md` §3). */
|
|
204
210
|
const CONFIG_SET_LAYERS_COMMAND = `${CLI} config set layers`;
|
|
205
211
|
/** The one-key form of the same verb, named in step 3 for the two run settings (`commands/config.ts`). */
|
|
@@ -214,6 +220,22 @@ const CONFIG_SET_COMMAND = `${CLI} config set <key> <value>`;
|
|
|
214
220
|
*/
|
|
215
221
|
const MARKETPLACE_MANIFEST = join('.claude-plugin', 'marketplace.json');
|
|
216
222
|
const CLI_MANIFEST = join('cli', 'package.json');
|
|
223
|
+
/**
|
|
224
|
+
* The environment variable that lets a run wire the harness's own repository on purpose.
|
|
225
|
+
*
|
|
226
|
+
* **Self-adoption** — running the harness's own flows against the harness while developing it — is a
|
|
227
|
+
* contributor workflow, so the escape is an environment variable rather than a flag. A flag would
|
|
228
|
+
* appear in `--help` and owe a row in `docs/cli.md`'s table, where the only reader it could reach is
|
|
229
|
+
* an adopter, for whom it can never mean anything but a mistake. It is documented in
|
|
230
|
+
* `docs/development.md` instead, beside the gates that are the rest of that audience's contract.
|
|
231
|
+
*
|
|
232
|
+
* **A variable rather than a one-off source edit**, which is the other way to get one run through,
|
|
233
|
+
* because `init` is not one-shot: `--force` regenerates a generated file when `cli/templates/`
|
|
234
|
+
* changes and `--reset-config` rebuilds the configuration, so the run this exists for recurs every
|
|
235
|
+
* time a template moves. An edit-build-revert-build cycle per template change is a standing invitation
|
|
236
|
+
* to ship the CLI with its guard removed, and the test that would catch it only runs if it is run.
|
|
237
|
+
*/
|
|
238
|
+
const SELF_ADOPT_ENV = 'HARNESS_SELF_ADOPT';
|
|
217
239
|
/** The flag whose value the dot-directory refusal is raised against. */
|
|
218
240
|
const STATE_DIR_FLAG = '--state-dir';
|
|
219
241
|
/** The flag that answers the git gate — named in the prompt, in the refusal, and in the note. */
|
|
@@ -225,6 +247,18 @@ const GIT_INIT_FLAG = '--git-init';
|
|
|
225
247
|
const APP_DIR_FLAG = '--app-dir';
|
|
226
248
|
/** The flag that answers the QA-driver question — named in the prompt, the refusal and the note. */
|
|
227
249
|
const QA_DRIVER_FLAG = '--qa-driver';
|
|
250
|
+
/** The flag that answers the docs-retrieval question, read by its option row, its refusal and its prompt. */
|
|
251
|
+
const DOCS_RETRIEVAL_FLAG = '--docs-retrieval';
|
|
252
|
+
/**
|
|
253
|
+
* The second accepted spelling of that flag, in the word an adopter arrives with. It sets the same
|
|
254
|
+
* key; {@link DOCS_RETRIEVAL_FLAG} stays the canonical one the prompt and the documentation name.
|
|
255
|
+
*
|
|
256
|
+
* A constant rather than a literal for the reason every flag above is one: the `--help` row, the
|
|
257
|
+
* parser's lookup and the messages that name it to a reader are one spelling read from one owner,
|
|
258
|
+
* and it reaches all three through {@link InitOption.aliases} rather than through a second accepted-
|
|
259
|
+
* token list beside {@link INIT_OPTIONS}.
|
|
260
|
+
*/
|
|
261
|
+
const DOCS_RETRIEVAL_ALIAS_FLAG = '--rag';
|
|
228
262
|
/**
|
|
229
263
|
* The two spellings that answer the analyze offer — one accept, one decline, and neither given is
|
|
230
264
|
* the state {@link resolveAnalyzeOffer} answers with the documented default.
|
|
@@ -266,8 +300,9 @@ function initOptions(rows, ..._rowsMissingFor) {
|
|
|
266
300
|
* then the two that decide what is detected, then the values written into the config, then the three
|
|
267
301
|
* phase toggles with their own inputs beside them, then the onboarding slug, then the pair that
|
|
268
302
|
* answers the offer to analyze this repository — which decides the wording the generated
|
|
269
|
-
* always-loaded file carries —
|
|
270
|
-
* unattended run finishes, which is the one pair that writes nothing into the repository at all
|
|
303
|
+
* always-loaded file carries — then the pair that decides whether this account gets told when an
|
|
304
|
+
* unattended run finishes, which is the one pair that writes nothing into the repository at all, and
|
|
305
|
+
* last the switch that adds this machine's plugin-root entries to a freshly generated profile.
|
|
271
306
|
*
|
|
272
307
|
* The first two sit together, and ahead of everything else, because they are the rows whose subject
|
|
273
308
|
* is the **shape of the run** rather than a value in the generated file: one settles what `init` is
|
|
@@ -361,6 +396,14 @@ const INIT_OPTIONS = initOptions([
|
|
|
361
396
|
summary: 'Documentation root the docs phase keeps current (with --docs)',
|
|
362
397
|
configValue: 'docs.root',
|
|
363
398
|
},
|
|
399
|
+
{
|
|
400
|
+
key: 'docsRetrieval',
|
|
401
|
+
flag: DOCS_RETRIEVAL_FLAG,
|
|
402
|
+
aliases: [DOCS_RETRIEVAL_ALIAS_FLAG],
|
|
403
|
+
kind: 'switch',
|
|
404
|
+
summary: 'Turn RAG (docs retrieval) on: a local search tool over the docs and conventions (with --docs)',
|
|
405
|
+
configValue: 'docs.retrieval',
|
|
406
|
+
},
|
|
364
407
|
{
|
|
365
408
|
key: 'parity',
|
|
366
409
|
flag: '--parity',
|
|
@@ -414,10 +457,24 @@ const INIT_OPTIONS = initOptions([
|
|
|
414
457
|
key: 'pushUrl',
|
|
415
458
|
flag: PUSH_URL_FLAG,
|
|
416
459
|
kind: 'value',
|
|
417
|
-
placeholder:
|
|
418
|
-
summary: '
|
|
460
|
+
placeholder: PUSH_DESTINATION_PLACEHOLDER,
|
|
461
|
+
summary: 'Where notifications are posted: an ntfy topic name, or the full http:// or https:// URL of any endpoint that accepts a POST (with --notifications)',
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
key: 'pluginRootEntries',
|
|
465
|
+
flag: PLUGIN_ROOT_ENTRIES_FLAG,
|
|
466
|
+
kind: 'switch',
|
|
467
|
+
summary: "Include this machine's plugin-root permission entries when the profile is generated (for a remote job)",
|
|
419
468
|
},
|
|
420
469
|
]);
|
|
470
|
+
/**
|
|
471
|
+
* Every token a row accepts, canonical first: its flag and any {@link InitOption.aliases}. The one
|
|
472
|
+
* reader of that field, so the parser, the `--help` block and the discarded-flag warning cannot
|
|
473
|
+
* disagree about which spellings exist.
|
|
474
|
+
*/
|
|
475
|
+
function optionSpellings(option) {
|
|
476
|
+
return [option.flag, ...(option.aliases ?? [])];
|
|
477
|
+
}
|
|
421
478
|
/**
|
|
422
479
|
* A flag value as a reader can retype it: quoted only where a shell would otherwise split or expand
|
|
423
480
|
* it, so the remedy below stays copy-pasteable for an ordinary branch name and stays correct for a
|
|
@@ -457,16 +514,19 @@ function discardedConfigFlagsWarning(flags) {
|
|
|
457
514
|
if (configKey === undefined)
|
|
458
515
|
continue;
|
|
459
516
|
const steersDetection = option.steersDetection === true;
|
|
517
|
+
// The naming carries every accepted spelling, so a reader who typed an alias recognises the line
|
|
518
|
+
// as theirs; the re-invocation stays on the canonical one, because that half is pasted back.
|
|
519
|
+
const named = option.aliases === undefined ? option.flag : `${option.flag} (or ${option.aliases.join(', ')})`;
|
|
460
520
|
if (option.kind === 'switch') {
|
|
461
521
|
if (flags[option.key] === true) {
|
|
462
|
-
dropped.push({ flag:
|
|
522
|
+
dropped.push({ flag: named, configKey, invocation: option.flag, steersDetection });
|
|
463
523
|
}
|
|
464
524
|
continue;
|
|
465
525
|
}
|
|
466
526
|
const value = flags[option.key];
|
|
467
527
|
if (value === undefined)
|
|
468
528
|
continue;
|
|
469
|
-
dropped.push({ flag:
|
|
529
|
+
dropped.push({ flag: named, configKey, invocation: `${option.flag} ${retypable(value)}`, steersDetection });
|
|
470
530
|
}
|
|
471
531
|
if (dropped.length === 0)
|
|
472
532
|
return undefined;
|
|
@@ -478,9 +538,12 @@ function discardedConfigFlagsWarning(flags) {
|
|
|
478
538
|
: ` ${steering.join(', ')} also steer${steering.length === 1 ? 's' : ''} stack detection, and still did on this run: what \`--force\` then re-renders from that detection is the wrapper scripts, not this file.`;
|
|
479
539
|
return `${dropped.map((entry) => entry.flag).join(', ')} ${one ? 'was' : 'were'} given, and ${CONFIG_FILENAME} was read rather than written on this run: ${one ? 'that flag supplies a value' : 'each of those flags supplies a value'} for a key of that file, so no such value was written and the keys in the file are the ones every generator after it read.${steeringClause} Apply ${one ? 'it' : 'them'} by rebuilding the file from detection and this command line with \`${INIT_VERB} --reset-config ${dropped.map((entry) => entry.invocation).join(' ')}\`, which copies the file that is there to a .bak first and is the only route that also re-derives what a value implies — or change ${one ? 'the key' : 'the keys'} alone with ${keyRemedies}`;
|
|
480
540
|
}
|
|
481
|
-
/**
|
|
541
|
+
/**
|
|
542
|
+
* The command's own `Options:` rows, invocation-aligned, derived from {@link INIT_OPTIONS}. A row
|
|
543
|
+
* with aliases prints every spelling it accepts, so no accepted token is absent from `--help`.
|
|
544
|
+
*/
|
|
482
545
|
function initOptionLines() {
|
|
483
|
-
const invocations = INIT_OPTIONS.map((option) => `${option.
|
|
546
|
+
const invocations = INIT_OPTIONS.map((option) => `${optionSpellings(option).join(', ')}${option.kind === 'switch' ? '' : ` ${option.placeholder}`}`);
|
|
484
547
|
const width = Math.max(...invocations.map((invocation) => invocation.length));
|
|
485
548
|
return INIT_OPTIONS.map((option, index) => ` ${invocations[index].padEnd(width)} ${option.summary}`);
|
|
486
549
|
}
|
|
@@ -523,13 +586,15 @@ function parseQaDriver(value) {
|
|
|
523
586
|
}
|
|
524
587
|
function parseInitFlags(argv) {
|
|
525
588
|
const values = new Map();
|
|
526
|
-
|
|
589
|
+
// The spelling each switch was typed as, not merely that it was given: a row accepting an alias
|
|
590
|
+
// ({@link InitOption.aliases}) must be named back to the reader in the words they used.
|
|
591
|
+
const switches = new Map();
|
|
527
592
|
for (let index = 0; index < argv.length; index += 1) {
|
|
528
593
|
const token = argv[index];
|
|
529
594
|
const separator = token.startsWith('--') ? token.indexOf('=') : -1;
|
|
530
595
|
const name = separator > 0 ? token.slice(0, separator) : token;
|
|
531
596
|
const inlineValue = separator > 0 ? token.slice(separator + 1) : undefined;
|
|
532
|
-
const option = INIT_OPTIONS.find((candidate) => candidate.
|
|
597
|
+
const option = INIT_OPTIONS.find((candidate) => optionSpellings(candidate).includes(name));
|
|
533
598
|
if (option === undefined) {
|
|
534
599
|
throw new HarnessError(token.startsWith('-')
|
|
535
600
|
? `init: unknown option ${JSON.stringify(name)} — run \`${INIT_VERB} --help\` for the options it takes`
|
|
@@ -538,7 +603,7 @@ function parseInitFlags(argv) {
|
|
|
538
603
|
if (option.kind === 'switch') {
|
|
539
604
|
if (inlineValue !== undefined)
|
|
540
605
|
throw new HarnessError(`init: ${name} does not take a value`);
|
|
541
|
-
switches.
|
|
606
|
+
switches.set(option.key, name);
|
|
542
607
|
continue;
|
|
543
608
|
}
|
|
544
609
|
let value = inlineValue;
|
|
@@ -568,6 +633,22 @@ function parseInitFlags(argv) {
|
|
|
568
633
|
if (switches.has('analyze') && switches.has('noAnalyze')) {
|
|
569
634
|
throw new HarnessError(`init: ${ANALYZE_FLAG} and ${NO_ANALYZE_FLAG} answer the same question opposite ways and both were given: pass one, or neither — with neither, the documented default accepts the offer`);
|
|
570
635
|
}
|
|
636
|
+
// Refused rather than warned, unlike `--qa-driver` without `--qa`: `docs.retrieval: true` without
|
|
637
|
+
// `phases.docs` is a config-check error, so the generated config would fail the write guard.
|
|
638
|
+
if (switches.has('docsRetrieval') && !switches.has('docs')) {
|
|
639
|
+
// Named as it was typed: a run refused for passing the alias is not told about a flag it never
|
|
640
|
+
// used. The fallback is unreachable while the key is set only from a token, and is here so the
|
|
641
|
+
// message has a spelling rather than `undefined` if that ever stops holding.
|
|
642
|
+
const typed = switches.get('docsRetrieval') ?? DOCS_RETRIEVAL_FLAG;
|
|
643
|
+
throw new HarnessError(`init: ${typed} needs --docs: retrieval searches the documentation corpus the docs phase maintains, so it is legal only with that phase on`);
|
|
644
|
+
}
|
|
645
|
+
// Checked here for the reason `qaDriver` is, whether or not --notifications was given: a bad value
|
|
646
|
+
// must not cost an adopter a repository this run created. The value is withheld from the message,
|
|
647
|
+
// unlike {@link parseQaDriver}'s, because a push destination is a credential and stderr is logged.
|
|
648
|
+
const pushUrl = values.get('pushUrl');
|
|
649
|
+
if (pushUrl !== undefined && resolvePushDestination(pushUrl).kind === 'unrecognised') {
|
|
650
|
+
throw new HarnessError(`init: ${PUSH_URL_FLAG} takes ${PUSH_DESTINATION_FORMS}; the value given is neither, so nothing was written. It is not repeated here, because a push destination is a credential — check it and pass it again`);
|
|
651
|
+
}
|
|
571
652
|
return {
|
|
572
653
|
...Object.fromEntries([...values]),
|
|
573
654
|
...(qaDriver === undefined ? {} : { qaDriver: parseQaDriver(qaDriver) }),
|
|
@@ -578,7 +659,9 @@ function parseInitFlags(argv) {
|
|
|
578
659
|
notifications: switches.has('notifications'),
|
|
579
660
|
qa: switches.has('qa'),
|
|
580
661
|
docs: switches.has('docs'),
|
|
662
|
+
docsRetrieval: switches.has('docsRetrieval'),
|
|
581
663
|
parity: switches.has('parity'),
|
|
664
|
+
pluginRootEntries: switches.has('pluginRootEntries'),
|
|
582
665
|
};
|
|
583
666
|
}
|
|
584
667
|
/** The `name` of a JSON manifest, or `undefined` when there is no readable one with a name. */
|
|
@@ -614,13 +697,22 @@ function ownPackageName() {
|
|
|
614
697
|
* `docs/development.md` §5's gate 2 documents running the built CLI from this root, which was a
|
|
615
698
|
* refusal check for as long as `init` refused everything. Once it works, that invocation would
|
|
616
699
|
* generate a config, a state tree and a permission profile into the harness's own tree.
|
|
700
|
+
*
|
|
701
|
+
* It is a refusal and not a prohibition: {@link SELF_ADOPT_ENV} set to `1` is how a contributor asks
|
|
702
|
+
* for that wiring deliberately, and nothing else reaches it.
|
|
617
703
|
*/
|
|
618
704
|
function assertNotHarnessOwnRepository(repoRoot) {
|
|
619
705
|
if (!existsSync(join(repoRoot, MARKETPLACE_MANIFEST)))
|
|
620
706
|
return;
|
|
621
707
|
if (packageNameAt(join(repoRoot, CLI_MANIFEST)) !== ownPackageName())
|
|
622
708
|
return;
|
|
623
|
-
|
|
709
|
+
// Checked **after** both identity tests rather than before them, which is what keeps the variable
|
|
710
|
+
// inert everywhere it is not wanted: every root that is not this one has already returned above,
|
|
711
|
+
// so a value left in a shell's environment changes the behaviour of no adopter's run. Only the
|
|
712
|
+
// exact `1` opens it — a variable that is merely *set* is too easy to inherit by accident.
|
|
713
|
+
if (process.env[SELF_ADOPT_ENV] === '1')
|
|
714
|
+
return;
|
|
715
|
+
throw new HarnessError(`refusing to wire the harness's own repository; run \`${INIT_VERB}\` in the repository you want to adopt it. ${repoRoot} carries ${MARKETPLACE_MANIFEST} and a ${CLI_MANIFEST} naming this package, so it is the harness itself rather than an adopting project: wiring it would generate a ${CONFIG_FILENAME}, a run-artifact tree and a permission profile into the tree that ships them. Run ${INIT_VERB} from the adopting repository, or point it at one with --cwd <path>. To wire this repository on purpose — the harness's own self-adoption, a contributor workflow \`docs/development.md\` documents — set ${SELF_ADOPT_ENV}=1`);
|
|
624
716
|
}
|
|
625
717
|
/**
|
|
626
718
|
* Refuse a `--state-dir` that names, or reaches through, a dot-directory — **before** anything is
|
|
@@ -874,6 +966,21 @@ function askQaDriver(ctx) {
|
|
|
874
966
|
defaultValue: DEFAULTS.qa.driver,
|
|
875
967
|
}, promptCtx);
|
|
876
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Ask whether to turn docs retrieval on — or answer `undefined` on every run that cannot be asked, for
|
|
971
|
+
* {@link askQaDriver}'s reason: the generator notes an unasked run, and must not note a declined one.
|
|
972
|
+
*/
|
|
973
|
+
function askRetrieval(ctx) {
|
|
974
|
+
const promptCtx = { flags: ctx.flags, report: ctx.report };
|
|
975
|
+
if (!canPrompt(promptCtx))
|
|
976
|
+
return undefined;
|
|
977
|
+
return askYesNo({
|
|
978
|
+
question: 'Turn on RAG (docs retrieval)? It adds a local search tool over the docs and conventions for the plan writer and reviewers. Setup installs about 300 MB of local runtime and downloads two small models into a cache shared by every checkout on this machine. Off by default.',
|
|
979
|
+
defaultAnswer: false,
|
|
980
|
+
flag: DOCS_RETRIEVAL_FLAG,
|
|
981
|
+
flagHint: 'to turn it on without being asked',
|
|
982
|
+
}, promptCtx);
|
|
983
|
+
}
|
|
877
984
|
/**
|
|
878
985
|
* The conventions documents this repository has: every distinct `layers[].conventions` value, plus
|
|
879
986
|
* the shared cross-layer document whether or not a layer points at it.
|
|
@@ -1026,14 +1133,15 @@ function resolveAnalyzeOffer(ctx, flags, repoRoot, config) {
|
|
|
1026
1133
|
*
|
|
1027
1134
|
* The two questions are asked in order and the second only inside the first's yes, because an
|
|
1028
1135
|
* endpoint is meaningless without the opt-in and the opt-in writes nothing without an endpoint. The
|
|
1029
|
-
* endpoint question carries no default
|
|
1030
|
-
*
|
|
1031
|
-
* machine-local file would shadow a repository-side one that already has values
|
|
1032
|
-
* (`generators/notifications.ts`, choice 1)
|
|
1136
|
+
* endpoint question carries no default, and {@link askPushDestination} ends in one of three outcomes:
|
|
1137
|
+
* `undefined`, which routes the run to the generator's guided-setup note rather than to a file —
|
|
1138
|
+
* writing an empty machine-local file would shadow a repository-side one that already has values
|
|
1139
|
+
* (`generators/notifications.ts`, choice 1); a recognised destination, passed on as typed; or, after
|
|
1140
|
+
* the re-asks run out, the last unrecognised answer, on which the generator writes nothing and warns.
|
|
1033
1141
|
*
|
|
1034
1142
|
* A `--push-url` passed **without** the opt-in is left in place rather than dropped here: the
|
|
1035
1143
|
* generator owns what that means and warns about it, as it owns every other line about the artifact
|
|
1036
|
-
* it writes.
|
|
1144
|
+
* it writes. The flag always wins over asking.
|
|
1037
1145
|
*/
|
|
1038
1146
|
function resolveNotifications(ctx, flags) {
|
|
1039
1147
|
const promptCtx = { flags: ctx.flags, report: ctx.report };
|
|
@@ -1044,15 +1152,27 @@ function resolveNotifications(ctx, flags) {
|
|
|
1044
1152
|
flag: NOTIFICATIONS_FLAG,
|
|
1045
1153
|
flagHint: 'to set them up without being asked',
|
|
1046
1154
|
}, promptCtx);
|
|
1047
|
-
const pushUrl = flags.pushUrl ??
|
|
1048
|
-
(enabled
|
|
1049
|
-
? askLine({
|
|
1050
|
-
question: `Where should notifications be posted? (any endpoint that accepts a POST, e.g. ${GUIDED_ENDPOINT_EXAMPLE})`,
|
|
1051
|
-
flag: PUSH_URL_FLAG,
|
|
1052
|
-
}, promptCtx)
|
|
1053
|
-
: undefined);
|
|
1155
|
+
const pushUrl = flags.pushUrl ?? (enabled ? askPushDestination(promptCtx) : undefined);
|
|
1054
1156
|
return pushUrl === undefined ? { enabled } : { enabled, pushUrl };
|
|
1055
1157
|
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Ask for the push destination, re-asking an unrecognised answer up to `REPROMPT_LIMIT` times. Loops
|
|
1160
|
+
* over `askLine` rather than reading the terminal, so `core/prompt.ts` stays the one way to ask.
|
|
1161
|
+
* Returns `undefined` for no answer, a recognised answer as typed, or the last unrecognised one.
|
|
1162
|
+
*/
|
|
1163
|
+
function askPushDestination(promptCtx) {
|
|
1164
|
+
let answer;
|
|
1165
|
+
for (let attempt = 0; attempt <= REPROMPT_LIMIT; attempt += 1) {
|
|
1166
|
+
answer = askLine({
|
|
1167
|
+
question: `Where should notifications be posted? Type ${PUSH_DESTINATION_FORMS}.`,
|
|
1168
|
+
flag: PUSH_URL_FLAG,
|
|
1169
|
+
}, promptCtx);
|
|
1170
|
+
if (answer === undefined || resolvePushDestination(answer).kind !== 'unrecognised')
|
|
1171
|
+
return answer;
|
|
1172
|
+
promptCtx.report.info(UNRECOGNISED_DESTINATION_NOTE);
|
|
1173
|
+
}
|
|
1174
|
+
return answer;
|
|
1175
|
+
}
|
|
1056
1176
|
/** What detection concluded, as one line the summary can carry. */
|
|
1057
1177
|
function detectionLine(detection) {
|
|
1058
1178
|
if (detection.matchedSignal === FORCED_SIGNAL_ID) {
|
|
@@ -1568,6 +1688,8 @@ async function run(ctx) {
|
|
|
1568
1688
|
// back-references it ({@link askQaDriver}) — "the detected preset" names nothing an adopter who
|
|
1569
1689
|
// has not read that line can resolve.
|
|
1570
1690
|
askDriver: () => askQaDriver(ctx),
|
|
1691
|
+
// Lazy for the same reason: asked only while the docs phase is on and `--docs-retrieval` was absent.
|
|
1692
|
+
askRetrieval: () => askRetrieval(ctx),
|
|
1571
1693
|
});
|
|
1572
1694
|
// `kept` is known only now, so this is where the four held-back lists are published or dropped —
|
|
1573
1695
|
// ahead of `config.warnings`, which is the order they printed in before they were gated. A kept
|
|
@@ -1632,6 +1754,9 @@ async function run(ctx) {
|
|
|
1632
1754
|
// exist (`generators/outerLoopScripts.ts`).
|
|
1633
1755
|
const outerLoop = writeOuterLoopScripts({ repoRoot, config: effective, plan });
|
|
1634
1756
|
notes.push(...outerLoop.notes);
|
|
1757
|
+
// After the scripts, which the workflows run, and before the permission profile. Enqueues nothing
|
|
1758
|
+
// unless `execution.target` is `github-actions` (`generators/githubWorkflows.ts`).
|
|
1759
|
+
const workflows = writeGithubWorkflows({ repoRoot, config: effective, plan });
|
|
1635
1760
|
const state = writeStateDir({ repoRoot, config: effective, plan });
|
|
1636
1761
|
notes.push(...state.notes);
|
|
1637
1762
|
// The third of the four questions this run puts, and settled here because its answer is an
|
|
@@ -1676,6 +1801,7 @@ async function run(ctx) {
|
|
|
1676
1801
|
// The run's `--dry-run`, which changes that report's tense and nothing else — the same contract
|
|
1677
1802
|
// the project-file generator above keeps.
|
|
1678
1803
|
dryRun: ctx.flags.dryRun,
|
|
1804
|
+
pluginRootEntries: flags.pluginRootEntries === true,
|
|
1679
1805
|
});
|
|
1680
1806
|
warnings.push(...permissions.warnings);
|
|
1681
1807
|
notes.push(...permissions.notes);
|
|
@@ -1711,12 +1837,24 @@ async function run(ctx) {
|
|
|
1711
1837
|
warnings.push(...hooks.warnings);
|
|
1712
1838
|
notes.push(...hooks.notes);
|
|
1713
1839
|
ctx.report.step(ctx.flags.dryRun ? 'files (dry run — nothing is written)' : 'files');
|
|
1714
|
-
plan.apply({ repoRoot, report: ctx.report, dryRun: ctx.flags.dryRun, force: ctx.flags.force });
|
|
1840
|
+
const applied = plan.apply({ repoRoot, report: ctx.report, dryRun: ctx.flags.dryRun, force: ctx.flags.force });
|
|
1841
|
+
const profileWrite = applied.find((result) => result.path === permissions.path);
|
|
1842
|
+
const pluginRootNote = profileWrite === undefined ? undefined : pluginRootEntriesNote(permissions.pluginRootEntries, profileWrite.effect);
|
|
1843
|
+
if (pluginRootNote !== undefined)
|
|
1844
|
+
notes.push(pluginRootNote);
|
|
1715
1845
|
// After the plan, deliberately: this is the one git-configuration write, and pointing
|
|
1716
1846
|
// `core.hooksPath` at a directory whose hook has not landed yet would enable nothing.
|
|
1717
1847
|
const hooksPath = pointHooksPath({ repoRoot, githooksDir: hooks.githooksDir, dryRun: ctx.flags.dryRun });
|
|
1718
1848
|
warnings.push(...hooksPath.warnings);
|
|
1719
1849
|
notes.push(...hooksPath.notes);
|
|
1850
|
+
// After the plan: this step writes nothing into the repository, and a real install takes minutes, so
|
|
1851
|
+
// it must not delay the plan's own report. Before the commit, which a failure here must not stop.
|
|
1852
|
+
if (retrievalApplies(effective)) {
|
|
1853
|
+
ctx.report.step('docs retrieval setup');
|
|
1854
|
+
const retrieval = setUpRetrieval({ dryRun: ctx.flags.dryRun });
|
|
1855
|
+
warnings.push(...retrieval.warnings);
|
|
1856
|
+
notes.push(...retrieval.notes);
|
|
1857
|
+
}
|
|
1720
1858
|
// The second post-plan step, and after the plan for a reason of its own: the managed `.gitignore`
|
|
1721
1859
|
// block that decides what `git add -A` may stage arrived with the plan. Before the summary, so the
|
|
1722
1860
|
// commit is reported inside the run's action log rather than after its closing pointer.
|
|
@@ -1737,8 +1875,50 @@ async function run(ctx) {
|
|
|
1737
1875
|
// browser wiring at all. Taken from the one predicate the two generators that write that wiring
|
|
1738
1876
|
// read, never re-spelled here (`config/model.ts`).
|
|
1739
1877
|
browserWiringApplies(effective));
|
|
1878
|
+
// Only the workflows this run created or replaced: a kept one is the adopter's already, and telling
|
|
1879
|
+
// them to commit it again is false on every unforced re-run and in every remote job's `init`.
|
|
1880
|
+
const freshWorkflows = workflows.workflows
|
|
1881
|
+
.filter(({ absolute }) => {
|
|
1882
|
+
const result = applied.find((r) => r.path === absolute);
|
|
1883
|
+
return result !== undefined && result.effect !== 'kept';
|
|
1884
|
+
})
|
|
1885
|
+
.map(({ repoPath }) => repoPath);
|
|
1886
|
+
if (freshWorkflows.length > 0)
|
|
1887
|
+
reportGithubSteps(ctx, effective.defaultBranch, ctx.flags.dryRun, freshWorkflows);
|
|
1740
1888
|
return EXIT.OK;
|
|
1741
1889
|
}
|
|
1890
|
+
/**
|
|
1891
|
+
* The GitHub-side steps only the adopter can take, printed when this run created or replaced at least
|
|
1892
|
+
* one of the two workflows; `workflowPaths` names those, repo-relative.
|
|
1893
|
+
*
|
|
1894
|
+
* Commands stand on their own lines so each can be pasted. The push comes first because GitHub
|
|
1895
|
+
* dispatches a `workflow_dispatch` workflow only once it exists on the default branch.
|
|
1896
|
+
*/
|
|
1897
|
+
function reportGithubSteps(ctx, defaultBranch, dryRun, workflowPaths) {
|
|
1898
|
+
const wrote = dryRun ? 'would write' : 'wrote';
|
|
1899
|
+
const command = (line) => ctx.report.info(` ${line}`);
|
|
1900
|
+
ctx.report.step('remote execution');
|
|
1901
|
+
ctx.report.info(`1. This run ${wrote} ${workflowPaths.join(' and ')}. Commit and push ${workflowPaths.length === 1 ? 'it' : 'both'} to GitHub's default branch (assumed \`${defaultBranch}\` below) — a workflow_dispatch workflow can be dispatched only once it exists there:`);
|
|
1902
|
+
command(`git add ${workflowPaths.join(' ')}`);
|
|
1903
|
+
command('git commit -m "Add the harness workflows"');
|
|
1904
|
+
command(`git push origin ${defaultBranch}`);
|
|
1905
|
+
ctx.report.info('');
|
|
1906
|
+
ctx.report.info(`2. Set one credential secret: ${OAUTH_TOKEN_SECRET} for subscription billing, or ${API_KEY_SECRET} for API billing. When both are set, billing follows ${API_KEY_SECRET}:`);
|
|
1907
|
+
command(`gh secret set ${OAUTH_TOKEN_SECRET}`);
|
|
1908
|
+
command(`gh secret set ${API_KEY_SECRET}`);
|
|
1909
|
+
ctx.report.info('');
|
|
1910
|
+
ctx.report.info(`3. Optionally set ${PUSH_URL_SECRET} to receive push notifications from the job, and ${GIT_TOKEN_SECRET} — a personal or App token — so the job's pushes trigger your own CI, which pushes made with the job's built-in token never do:`);
|
|
1911
|
+
command(`gh secret set ${PUSH_URL_SECRET}`);
|
|
1912
|
+
command(`gh secret set ${GIT_TOKEN_SECRET}`);
|
|
1913
|
+
ctx.report.info('');
|
|
1914
|
+
ctx.report.info(`4. Optionally set the repository variable ${RUNNER_VARIABLE} to run on a self-hosted runner label instead of ubuntu-latest:`);
|
|
1915
|
+
command(`gh variable set ${RUNNER_VARIABLE} --body <runner-label>`);
|
|
1916
|
+
ctx.report.info('');
|
|
1917
|
+
ctx.report.info('5. Then verify the GitHub side:');
|
|
1918
|
+
command(DOCTOR_CHECK_GITHUB_COMMAND);
|
|
1919
|
+
ctx.report.info('');
|
|
1920
|
+
ctx.report.info('Runner choices, costs, billing and security: the harness documentation, docs/remote-execution.md — Remote execution on GitHub Actions.');
|
|
1921
|
+
}
|
|
1742
1922
|
/**
|
|
1743
1923
|
* The caveats, after the action log: the informational lines first, then the ones needing attention.
|
|
1744
1924
|
*
|
|
@@ -1898,12 +2078,9 @@ function analyzeRecordSentence(analyze, force, dryRun, backupWouldCarryContent,
|
|
|
1898
2078
|
* rather than a fifth thing to do. It names {@link AGENT_CLI} directly where the generated watcher
|
|
1899
2079
|
* reaches the same binary through `${HARNESS_AGENT_CLI:-claude}`
|
|
1900
2080
|
* (`templates/scripts/autonomous-watcher.sh`) — an override that belongs to a script's own
|
|
1901
|
-
* environment and would mean nothing on a line printed for a person to paste. It names
|
|
1902
|
-
* {@link
|
|
1903
|
-
*
|
|
1904
|
-
* whose picker resolves the bare form, while this line is a first message, which is matched exactly —
|
|
1905
|
-
* the introducing sentence says so, because a report that showed both spellings and explained neither
|
|
1906
|
-
* would read as a typo. It is withheld wherever
|
|
2081
|
+
* environment and would mean nothing on a line printed for a person to paste. It names the same
|
|
2082
|
+
* {@link ANALYZE_COMMAND} spelling step 2 above it names, so the introducing sentence gives no
|
|
2083
|
+
* spelling reason. It is withheld wherever
|
|
1907
2084
|
* it would contradict the report around it: on the declined arm, whose answer the same step just
|
|
1908
2085
|
* quoted; on the skip branch, where there is nothing to fill; and under `--dry-run`, which wired
|
|
1909
2086
|
* nothing to run it against. On the unresolved-slug arm it is **printed but qualified** rather than
|
|
@@ -1911,7 +2088,7 @@ function analyzeRecordSentence(analyze, force, dryRun, backupWouldCarryContent,
|
|
|
1911
2088
|
* published — because step 1 has just said the session resolves the plugin only once one of the two
|
|
1912
2089
|
* routes it names is taken, and handing the line over without that precondition would contradict it.
|
|
1913
2090
|
* **The outcome is stated as intended on both arms**: whether the prefixed first-message form
|
|
1914
|
-
* succeeds is not established ({@link
|
|
2091
|
+
* succeeds is not established ({@link ANALYZE_INVOCATION}), so the line an adopter acts on
|
|
1915
2092
|
* immediately names the typed route as the measured one instead of asserting its own.
|
|
1916
2093
|
*
|
|
1917
2094
|
* **One blank line between the numbered steps, and none before that paste line.** Each step is a
|
|
@@ -1974,13 +2151,12 @@ function reportNextSteps(ctx, layerNames, analyze, force, dryRun, backupWouldCar
|
|
|
1974
2151
|
// The sentence introducing the line carries the precondition step 1 just stated. On the unresolved
|
|
1975
2152
|
// arm, handing the line over without that precondition would contradict the step above it, which
|
|
1976
2153
|
// has said the session resolves the plugin only once one of the two routes it names is taken.
|
|
1977
|
-
|
|
1978
|
-
//
|
|
1979
|
-
// and the measured route is pointed at ({@link ANALYZE_COMMAND_QUALIFIED}, `docs/analyze.md` §9).
|
|
2154
|
+
// The name typed into an open session is the measured route; the interactive first message is not,
|
|
2155
|
+
// so the outcome is named as intended ({@link ANALYZE_INVOCATION}, `docs/analyze.md` §9).
|
|
1980
2156
|
const pasteUnconfirmed = ' That first-message form is not confirmed on the version measured here; the name above, typed into an open session, is the route that is.';
|
|
1981
2157
|
const pasteIntro = wiring.marketplaceSlug === undefined
|
|
1982
|
-
? ` Once the plugin resolves — step 1 names both routes — paste the line below at this repository's root: it is meant to start a session with that command already running.${
|
|
1983
|
-
: ` Paste the line below at this repository's root: it is meant to start a session with that command already running.${
|
|
2158
|
+
? ` Once the plugin resolves — step 1 names both routes — paste the line below at this repository's root: it is meant to start a session with that command already running.${pasteUnconfirmed}`
|
|
2159
|
+
: ` Paste the line below at this repository's root: it is meant to start a session with that command already running.${pasteUnconfirmed}`;
|
|
1984
2160
|
ctx.report.step('next');
|
|
1985
2161
|
ctx.report.info(`1. ${wired}`);
|
|
1986
2162
|
ctx.report.info('');
|