@webpresso/agent-kit 3.3.3 → 3.3.5
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/THIRD-PARTY-NOTICES.md +12 -10
- package/catalog/agent/rules/changeset-release.md +15 -4
- package/catalog/agent/rules/managed-tool-binaries.md +67 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
- package/catalog/agent/rules/pre-implementation.md +36 -20
- package/catalog/agent/rules/rtk-routing.md +22 -16
- package/catalog/agent/skills/autopilot/SKILL.md +4 -3
- package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
- package/dist/esm/audit/agents.js +126 -26
- package/dist/esm/audit/hook-surface.js +5 -1
- package/dist/esm/audit/repo-guardrails.d.ts +8 -0
- package/dist/esm/audit/repo-guardrails.js +71 -1
- package/dist/esm/blueprint/core/parser.js +38 -12
- package/dist/esm/blueprint/execution/artifacts.js +8 -4
- package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
- package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
- package/dist/esm/blueprint/execution/metadata.js +8 -4
- package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
- package/dist/esm/blueprint/lifecycle/audit.js +89 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.js +63 -10
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
- package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
- package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
- package/dist/esm/blueprint/markdown/helpers.js +10 -4
- package/dist/esm/blueprint/tracked-document/parser.js +3 -3
- package/dist/esm/blueprint/trust/dossier.js +41 -1
- package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
- package/dist/esm/blueprint/trust/gate-row.js +113 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
- package/dist/esm/blueprint/trust/promotion.js +19 -19
- package/dist/esm/build/atomic-file-copy.d.ts +32 -0
- package/dist/esm/build/atomic-file-copy.js +44 -0
- package/dist/esm/build/vendor-oxlint-plugins.d.ts +11 -0
- package/dist/esm/build/vendor-oxlint-plugins.js +48 -0
- package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
- package/dist/esm/cli/commands/compile.d.ts +13 -0
- package/dist/esm/cli/commands/compile.js +182 -62
- package/dist/esm/cli/commands/format.js +17 -1
- package/dist/esm/cli/commands/gain/index.js +10 -3
- package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
- package/dist/esm/cli/commands/init/index.js +32 -52
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +48 -13
- package/dist/esm/cli/commands/init/mcp-spec.js +86 -30
- package/dist/esm/cli/commands/init/merge.js +30 -10
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
- package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
- package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
- package/dist/esm/cli/commands/pr-help.js +1 -1
- package/dist/esm/cli/commands/pr.js +18 -4
- package/dist/esm/cli/commands/review.d.ts +1 -0
- package/dist/esm/cli/commands/review.js +137 -7
- package/dist/esm/cli/commands/sync.d.ts +8 -6
- package/dist/esm/cli/commands/sync.js +11 -9
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
- package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
- package/dist/esm/cli/commands/worktree/router.js +5 -2
- package/dist/esm/cli/direct-provider-launch.js +12 -4
- package/dist/esm/compiler/flatten.d.ts +10 -1
- package/dist/esm/compiler/flatten.js +14 -4
- package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
- package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
- package/dist/esm/compose/compose.d.ts +8 -0
- package/dist/esm/compose/compose.js +115 -8
- package/dist/esm/compose/ops-report-schema.js +17 -3
- package/dist/esm/compose/registry-resolve.d.ts +17 -0
- package/dist/esm/compose/registry-resolve.js +78 -11
- package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
- package/dist/esm/config/oxlint/import-hygiene.js +21 -8
- package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
- package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
- package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
- package/dist/esm/config/oxlint/oxlintrc.js +19 -31
- package/dist/esm/config/oxlint/path-roles.js +53 -0
- package/dist/esm/config/oxlint/query-patterns.js +2 -5
- package/dist/esm/content/dispatch.js +4 -0
- package/dist/esm/content/loader.d.ts +1 -1
- package/dist/esm/content/loader.js +10 -6
- package/dist/esm/content/skill-policy.d.ts +11 -0
- package/dist/esm/content/skill-policy.js +33 -0
- package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
- package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
- package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
- package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
- package/dist/esm/daemon/domains/launch.d.ts +25 -0
- package/dist/esm/daemon/domains/launch.js +30 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
- package/dist/esm/daemon/handlers/product.d.ts +10 -0
- package/dist/esm/daemon/handlers/product.js +10 -1
- package/dist/esm/daemon/methods.d.ts +21 -1
- package/dist/esm/daemon/methods.js +20 -0
- package/dist/esm/daemon/protocol/contract.d.ts +58 -0
- package/dist/esm/daemon/protocol/contract.js +38 -0
- package/dist/esm/daemon/server.js +7 -0
- package/dist/esm/docs-linter/blueprint-plan.js +2 -2
- package/dist/esm/errors/wp-error.js +21 -2
- package/dist/esm/hooks/doctor.d.ts +11 -0
- package/dist/esm/hooks/doctor.js +114 -45
- package/dist/esm/hooks/pretool-guard/dev-routing.js +68 -5
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
- package/dist/esm/hooks/pretool-guard/runner.js +6 -0
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +62 -3
- package/dist/esm/hooks/status/index.js +1 -1
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
- package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
- package/dist/esm/mcp/blueprint/registration.js +1 -1
- package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
- package/dist/esm/mcp/tools/_shared/redact.js +114 -8
- package/dist/esm/mcp/tools/gain.js +28 -2
- package/dist/esm/mcp/tools/worktree.js +65 -3
- package/dist/esm/mcp/tools/wp-ui-compose.js +38 -4
- package/dist/esm/mcp/tools/wp-ui-preview.js +14 -7
- package/dist/esm/review/authority.js +56 -1
- package/dist/esm/review/delivery-verifier.d.ts +4 -0
- package/dist/esm/review/delivery-verifier.js +9 -1
- package/dist/esm/review/lifecycle-writers.d.ts +76 -0
- package/dist/esm/review/lifecycle-writers.js +330 -0
- package/dist/esm/review/subject.js +66 -32
- package/dist/esm/status/snapshot.d.ts +5 -0
- package/dist/esm/status/snapshot.js +27 -2
- package/dist/esm/symlinker/consumers.d.ts +3 -1
- package/dist/esm/symlinker/consumers.js +7 -1
- package/dist/esm/symlinker/unified-sync.d.ts +3 -3
- package/dist/esm/symlinker/unified-sync.js +88 -11
- package/dist/esm/test/shard-durations.json +0 -2
- package/dist/esm/test-helpers/global-setup.js +38 -3
- package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
- package/dist/esm/tool-runtime/managed-rtk.js +275 -0
- package/dist/esm/tool-runtime/resolve-runner.js +18 -194
- package/dist/esm/utils/package-root.d.ts +32 -0
- package/dist/esm/utils/package-root.js +82 -0
- package/dist/esm/worktrees/git-metadata.d.ts +41 -0
- package/dist/esm/worktrees/git-metadata.js +48 -0
- package/package.json +16 -21
- package/dist/esm/cli/commands/init/scaffolders/subagents/index.d.ts +0 -6
- package/dist/esm/cli/commands/init/scaffolders/subagents/index.js +0 -77
- package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
- package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
- package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
- package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
- package/dist/esm/config/oxlint/index.d.ts +0 -22
- package/dist/esm/config/oxlint/index.js +0 -28
- package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
- package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
- package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
- package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
- package/dist/esm/output-transforms/rulesync.d.ts +0 -2
- package/dist/esm/output-transforms/rulesync.js +0 -79
|
@@ -6,21 +6,18 @@
|
|
|
6
6
|
* the patch tiny and deterministic: per-server upserts, no TOML parser
|
|
7
7
|
* dependency, no edits to unrelated user config.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 2. Consumer-owned `.mcp.json` package declarations — compiled to setup-
|
|
18
|
-
* provisioned, exact-version binaries with environment-variable names
|
|
19
|
-
* forwarded through Codex's stdio `env_vars` allow-list.
|
|
9
|
+
* Webpresso's own MCP server is NOT one of them. Codex receives `webpresso`
|
|
10
|
+
* over exactly one delivery channel — the bundled Codex plugin's `.mcp.json`.
|
|
11
|
+
* Setup therefore *retires* the `[mcp_servers.webpresso]` block earlier
|
|
12
|
+
* versions wrote here (see {@link pruneLegacyCodexWebpressoMcp}) instead of
|
|
13
|
+
* refreshing it. What remains managed here is consumer-owned `.mcp.json`
|
|
14
|
+
* package declarations — compiled to setup-provisioned, exact-version binaries
|
|
15
|
+
* with environment-variable names forwarded through Codex's stdio `env_vars`
|
|
16
|
+
* allow-list.
|
|
20
17
|
*/
|
|
21
18
|
import { spawnSync } from "node:child_process";
|
|
22
|
-
import {
|
|
23
|
-
export {
|
|
19
|
+
import { WEBPRESSO_MCP_SERVER_NAME, type AgentKitOwnedCommandProbe, type WebpressoInstallProbe } from "#cli/commands/init/mcp-spec.js";
|
|
20
|
+
export { WEBPRESSO_MCP_SERVER_NAME };
|
|
24
21
|
export type { WebpressoInstallProbe };
|
|
25
22
|
import type { MergeOptions } from "#cli/commands/init/merge";
|
|
26
23
|
import { type PinnedCodexMcpDefinition } from "#hooks/host-config-contract.js";
|
|
@@ -143,35 +140,63 @@ export type EnsureClaudePlaywrightMcpResult = {
|
|
|
143
140
|
path: string;
|
|
144
141
|
};
|
|
145
142
|
export declare function ensureClaudePlaywrightMcp(input: EnsureClaudePlaywrightMcpInput): EnsureClaudePlaywrightMcpResult;
|
|
146
|
-
export
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
export type PruneOwnedMcpServerOutcome = {
|
|
144
|
+
readonly kind: "removed";
|
|
145
|
+
readonly raw: string;
|
|
146
|
+
readonly command: string;
|
|
147
|
+
} | {
|
|
148
|
+
readonly kind: "absent";
|
|
149
|
+
readonly raw: string;
|
|
150
|
+
} | {
|
|
151
|
+
readonly kind: "preserved";
|
|
152
|
+
readonly raw: string;
|
|
153
|
+
readonly command: string | null;
|
|
154
|
+
} | {
|
|
155
|
+
readonly kind: "ambiguous";
|
|
156
|
+
readonly raw: string;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Remove `[mcp_servers.<serverId>]` (plus its contiguous sub-tables) when
|
|
160
|
+
* `isOwned` claims the block's launch tuple. Fails closed — leaving `raw`
|
|
161
|
+
* byte-identical — for every TOML spelling this line scanner cannot rewrite
|
|
162
|
+
* safely (dotted keys, inline parent table, array tables, non-contiguous
|
|
163
|
+
* sub-tables).
|
|
164
|
+
*/
|
|
165
|
+
export declare function pruneOwnedMcpServerBlock(raw: string, serverId: string, isOwned: (command: string, args: readonly string[]) => boolean): PruneOwnedMcpServerOutcome;
|
|
166
|
+
export interface PruneLegacyCodexWebpressoMcpInput {
|
|
149
167
|
options: MergeOptions;
|
|
150
|
-
/** Test seam — override the resolved MCP entry path. */
|
|
151
|
-
entryPath?: string;
|
|
152
168
|
/** Test seam — override `$CODEX_HOME/config.toml`. */
|
|
153
169
|
configPath?: string;
|
|
154
|
-
/** Test seam — override
|
|
155
|
-
|
|
170
|
+
/** Test seam — override agent-kit install-root ownership resolution. */
|
|
171
|
+
ownership?: AgentKitOwnedCommandProbe;
|
|
156
172
|
}
|
|
157
|
-
export type
|
|
158
|
-
kind: "codex-webpresso-mcp-
|
|
173
|
+
export type PruneLegacyCodexWebpressoMcpResult = {
|
|
174
|
+
kind: "codex-legacy-webpresso-mcp-removed";
|
|
159
175
|
path: string;
|
|
160
|
-
|
|
176
|
+
command: string;
|
|
161
177
|
} | {
|
|
162
|
-
kind: "codex-webpresso-mcp-
|
|
178
|
+
kind: "codex-legacy-webpresso-mcp-absent";
|
|
163
179
|
path: string;
|
|
164
|
-
entryPath: string;
|
|
165
180
|
} | {
|
|
166
|
-
kind: "codex-webpresso-mcp-
|
|
181
|
+
kind: "codex-legacy-webpresso-mcp-preserved";
|
|
167
182
|
path: string;
|
|
168
|
-
|
|
183
|
+
command: string | null;
|
|
169
184
|
} | {
|
|
170
|
-
kind: "codex-webpresso-mcp-
|
|
185
|
+
kind: "codex-legacy-webpresso-mcp-ambiguous";
|
|
171
186
|
path: string;
|
|
172
187
|
} | {
|
|
173
|
-
kind: "codex-webpresso-mcp-
|
|
188
|
+
kind: "codex-legacy-webpresso-mcp-skipped-dry-run";
|
|
174
189
|
path: string;
|
|
175
|
-
checked: readonly string[];
|
|
176
190
|
};
|
|
177
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Retire the legacy user-global `[mcp_servers.webpresso]` block. Runs from
|
|
193
|
+
* setup AND from the `wp codex` launch preflight — the launch path is the only
|
|
194
|
+
* one that reaches machines that never re-run setup.
|
|
195
|
+
*/
|
|
196
|
+
export declare function pruneLegacyCodexWebpressoMcp(input: PruneLegacyCodexWebpressoMcpInput): PruneLegacyCodexWebpressoMcpResult;
|
|
197
|
+
/**
|
|
198
|
+
* Installed Codex plugin MCP manifests — the surviving delivery channel.
|
|
199
|
+
* Cheap directory probe only: no `codex plugin` subprocess belongs on the
|
|
200
|
+
* launch hot path.
|
|
201
|
+
*/
|
|
202
|
+
export declare function findInstalledCodexPluginMcpManifests(homeDir?: string): readonly string[];
|
|
@@ -6,26 +6,23 @@
|
|
|
6
6
|
* the patch tiny and deterministic: per-server upserts, no TOML parser
|
|
7
7
|
* dependency, no edits to unrelated user config.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 2. Consumer-owned `.mcp.json` package declarations — compiled to setup-
|
|
18
|
-
* provisioned, exact-version binaries with environment-variable names
|
|
19
|
-
* forwarded through Codex's stdio `env_vars` allow-list.
|
|
9
|
+
* Webpresso's own MCP server is NOT one of them. Codex receives `webpresso`
|
|
10
|
+
* over exactly one delivery channel — the bundled Codex plugin's `.mcp.json`.
|
|
11
|
+
* Setup therefore *retires* the `[mcp_servers.webpresso]` block earlier
|
|
12
|
+
* versions wrote here (see {@link pruneLegacyCodexWebpressoMcp}) instead of
|
|
13
|
+
* refreshing it. What remains managed here is consumer-owned `.mcp.json`
|
|
14
|
+
* package declarations — compiled to setup-provisioned, exact-version binaries
|
|
15
|
+
* with environment-variable names forwarded through Codex's stdio `env_vars`
|
|
16
|
+
* allow-list.
|
|
20
17
|
*/
|
|
21
18
|
import { spawnSync } from "node:child_process";
|
|
22
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
19
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
23
20
|
import { homedir } from "node:os";
|
|
24
21
|
import { dirname, join } from "node:path";
|
|
25
|
-
import {
|
|
26
|
-
// Re-exported so existing importers
|
|
27
|
-
// implementation
|
|
28
|
-
export {
|
|
22
|
+
import { isAgentKitOwnedWpCommand, resolveExecutableOnPath, WEBPRESSO_MCP_SERVER_NAME, } from "#cli/commands/init/mcp-spec.js";
|
|
23
|
+
// Re-exported so existing importers keep a stable path; the canonical
|
|
24
|
+
// implementation lives in `mcp-spec.ts`.
|
|
25
|
+
export { WEBPRESSO_MCP_SERVER_NAME };
|
|
29
26
|
import { updateScaffolderTextFile } from "#cli/commands/init/scaffolders/file-transaction.js";
|
|
30
27
|
import { isJsonRecord, isUnsafeRuntimeLauncher, parseJson, parseProjectMcpServers, parseTomlString, } from "#hooks/host-config-contract.js";
|
|
31
28
|
import { appendGlobalCapableVpArgs, resolveGlobalCapableVpCommand, } from "#cli/global-vp.js";
|
|
@@ -309,8 +306,10 @@ function hasDottedServerDefinition(lines, startsInsideMultiline, serverId) {
|
|
|
309
306
|
export function hasAmbiguousMcpServerDottedDefinition(raw, serverId) {
|
|
310
307
|
const lines = raw.trimEnd().split(/\r?\n/);
|
|
311
308
|
const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
|
|
312
|
-
|
|
313
|
-
|
|
309
|
+
// Scanned unconditionally, not only when no table header exists: a dotted key
|
|
310
|
+
// alongside the table would outlive the table's removal and re-declare the
|
|
311
|
+
// server with no launcher, which breaks the whole config load.
|
|
312
|
+
return hasDottedServerDefinition(lines, startsInsideMultiline, serverId);
|
|
314
313
|
}
|
|
315
314
|
export function hasManagedMcpServerTableConflict(raw, serverId) {
|
|
316
315
|
const lines = raw.trimEnd().split(/\r?\n/);
|
|
@@ -658,39 +657,177 @@ export function ensureClaudePlaywrightMcp(input) {
|
|
|
658
657
|
return { kind: "claude-playwright-mcp-written", path: configPath };
|
|
659
658
|
}
|
|
660
659
|
// ────────────────────────────────────────────────────────────────────────────
|
|
661
|
-
//
|
|
660
|
+
// Legacy `[mcp_servers.webpresso]` retirement
|
|
661
|
+
//
|
|
662
|
+
// Earlier versions delivered webpresso to Codex over a SECOND channel: a
|
|
663
|
+
// `[mcp_servers.webpresso]` block in the user-global `config.toml` pinned to
|
|
664
|
+
// whatever absolute install path existed at setup time. The bundled Codex
|
|
665
|
+
// plugin is now the single channel, so setup must remove what it used to write
|
|
666
|
+
// — otherwise every machine keeps a duplicate registration pinned to a path
|
|
667
|
+
// agent-kit itself deletes on the next update or worktree cleanup.
|
|
668
|
+
//
|
|
669
|
+
// The removal predicate is OWNERSHIP-based, never shape-based: a live `wp mcp`
|
|
670
|
+
// launcher outside every agent-kit-owned root is an operator pointing Codex at
|
|
671
|
+
// a custom build and is preserved (and reported by `wp doctor`).
|
|
662
672
|
// ────────────────────────────────────────────────────────────────────────────
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
673
|
+
const TOML_COMMAND_ASSIGNMENT = /^\s*command\s*=\s*(.+?)\s*$/u;
|
|
674
|
+
const TOML_ARGS_ASSIGNMENT = /^\s*args\s*=\s*(.+?)\s*$/u;
|
|
675
|
+
function firstTomlAssignmentValue(body, pattern) {
|
|
676
|
+
for (const line of body) {
|
|
677
|
+
const match = pattern.exec(line);
|
|
678
|
+
if (match?.[1] !== undefined)
|
|
679
|
+
return match[1];
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
function parseTomlStringArray(value) {
|
|
684
|
+
const trimmed = value.trim();
|
|
685
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]"))
|
|
686
|
+
return null;
|
|
687
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
688
|
+
if (inner.length === 0)
|
|
689
|
+
return [];
|
|
690
|
+
const parsed = inner
|
|
691
|
+
.split(",")
|
|
692
|
+
.map((element) => element.trim())
|
|
693
|
+
.filter((element) => element.length > 0)
|
|
694
|
+
.map(parseTomlString);
|
|
695
|
+
return parsed.every((element) => element !== null) ? parsed : null;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Read the parent block's `command`/`args`, stopping at the first sub-table
|
|
699
|
+
* header so a `[mcp_servers.<id>.env]` child never supplies the launch tuple.
|
|
700
|
+
*/
|
|
701
|
+
function readManagedBlockLaunch(lines, startsInsideMultiline, start, end) {
|
|
702
|
+
const body = [];
|
|
703
|
+
for (let index = start + 1; index < end; index += 1) {
|
|
704
|
+
// Multiline-string content is document text, not configuration: reading a
|
|
705
|
+
// decoy tuple from it — or letting a bracket line there end the scan —
|
|
706
|
+
// would delete the operator's real registration.
|
|
707
|
+
if (startsInsideMultiline[index])
|
|
708
|
+
continue;
|
|
709
|
+
if (lines[index].trim().startsWith("["))
|
|
710
|
+
break;
|
|
711
|
+
body.push(lines[index]);
|
|
712
|
+
}
|
|
713
|
+
const commandValue = firstTomlAssignmentValue(body, TOML_COMMAND_ASSIGNMENT);
|
|
714
|
+
const command = commandValue === null ? null : parseTomlString(commandValue);
|
|
715
|
+
if (command === null)
|
|
716
|
+
return null;
|
|
717
|
+
const argsValue = firstTomlAssignmentValue(body, TOML_ARGS_ASSIGNMENT);
|
|
718
|
+
const args = argsValue === null ? [] : parseTomlStringArray(argsValue);
|
|
719
|
+
return args === null ? null : { command, args };
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* The removal path is the only one that rewrites lines it did not classify, so
|
|
723
|
+
* it must reproduce the document's existing newline style rather than impose LF.
|
|
724
|
+
*/
|
|
725
|
+
function dominantEol(raw) {
|
|
726
|
+
const crlf = raw.split("\r\n").length - 1;
|
|
727
|
+
const lf = raw.split("\n").length - 1;
|
|
728
|
+
return crlf * 2 > lf ? "\r\n" : "\n";
|
|
729
|
+
}
|
|
730
|
+
function rebuildWithoutSection(lines, start, end, eol) {
|
|
731
|
+
const kept = lines.slice(0, start);
|
|
732
|
+
while (kept.at(-1)?.trim() === "")
|
|
733
|
+
kept.pop();
|
|
734
|
+
const next = kept
|
|
735
|
+
.concat(lines.slice(end))
|
|
736
|
+
.join(eol)
|
|
737
|
+
.replace(/^(?:[ \t]*(?:\r\n|\n))+/u, "")
|
|
738
|
+
.trimEnd();
|
|
739
|
+
return next.length > 0 ? `${next}${eol}` : "";
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Remove `[mcp_servers.<serverId>]` (plus its contiguous sub-tables) when
|
|
743
|
+
* `isOwned` claims the block's launch tuple. Fails closed — leaving `raw`
|
|
744
|
+
* byte-identical — for every TOML spelling this line scanner cannot rewrite
|
|
745
|
+
* safely (dotted keys, inline parent table, array tables, non-contiguous
|
|
746
|
+
* sub-tables).
|
|
747
|
+
*/
|
|
748
|
+
export function pruneOwnedMcpServerBlock(raw, serverId, isOwned) {
|
|
749
|
+
if (hasAmbiguousMcpServerDottedDefinition(raw, serverId) ||
|
|
750
|
+
hasManagedMcpServerTableConflict(raw, serverId)) {
|
|
751
|
+
return { kind: "ambiguous", raw };
|
|
752
|
+
}
|
|
753
|
+
const lines = raw.trimEnd().split(/\r?\n/);
|
|
754
|
+
const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
|
|
755
|
+
const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
|
|
756
|
+
if (start === -1)
|
|
757
|
+
return { kind: "absent", raw };
|
|
758
|
+
const end = managedSectionEnd(lines, startsInsideMultiline, start, serverId);
|
|
759
|
+
const launch = readManagedBlockLaunch(lines, startsInsideMultiline, start, end);
|
|
760
|
+
if (launch === null)
|
|
761
|
+
return { kind: "preserved", raw, command: null };
|
|
762
|
+
if (!isOwned(launch.command, launch.args)) {
|
|
763
|
+
return { kind: "preserved", raw, command: launch.command };
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
kind: "removed",
|
|
767
|
+
raw: rebuildWithoutSection(lines, start, end, dominantEol(raw)),
|
|
667
768
|
command: launch.command,
|
|
668
|
-
|
|
669
|
-
codexEnv: WEBPRESSO_MCP_SPEC.env,
|
|
670
|
-
});
|
|
769
|
+
};
|
|
671
770
|
}
|
|
672
|
-
|
|
673
|
-
|
|
771
|
+
function legacyPruneResult(outcome, path) {
|
|
772
|
+
switch (outcome.kind) {
|
|
773
|
+
case "removed":
|
|
774
|
+
return { kind: "codex-legacy-webpresso-mcp-removed", path, command: outcome.command };
|
|
775
|
+
case "preserved":
|
|
776
|
+
return { kind: "codex-legacy-webpresso-mcp-preserved", path, command: outcome.command };
|
|
777
|
+
case "ambiguous":
|
|
778
|
+
return { kind: "codex-legacy-webpresso-mcp-ambiguous", path };
|
|
779
|
+
case "absent":
|
|
780
|
+
return { kind: "codex-legacy-webpresso-mcp-absent", path };
|
|
781
|
+
}
|
|
674
782
|
}
|
|
675
|
-
|
|
783
|
+
/**
|
|
784
|
+
* Retire the legacy user-global `[mcp_servers.webpresso]` block. Runs from
|
|
785
|
+
* setup AND from the `wp codex` launch preflight — the launch path is the only
|
|
786
|
+
* one that reaches machines that never re-run setup.
|
|
787
|
+
*/
|
|
788
|
+
export function pruneLegacyCodexWebpressoMcp(input) {
|
|
676
789
|
const configPath = input.configPath ?? defaultConfigPath();
|
|
677
790
|
if (input.options.dryRun) {
|
|
678
|
-
return { kind: "codex-webpresso-mcp-skipped-dry-run", path: configPath };
|
|
791
|
+
return { kind: "codex-legacy-webpresso-mcp-skipped-dry-run", path: configPath };
|
|
679
792
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (!entryPath) {
|
|
683
|
-
const checked = resolution?.checked ?? [];
|
|
684
|
-
return { kind: "codex-webpresso-mcp-not-installed", path: configPath, checked };
|
|
793
|
+
if (!existsSync(configPath)) {
|
|
794
|
+
return { kind: "codex-legacy-webpresso-mcp-absent", path: configPath };
|
|
685
795
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
796
|
+
// Classify INSIDE the locked transaction so a concurrent writer's block is
|
|
797
|
+
// never judged from a pre-lock read (PR#767 lost-update guarantee).
|
|
798
|
+
let outcome = { kind: "absent", raw: "" };
|
|
799
|
+
updateCodexConfig(configPath, (raw) => {
|
|
800
|
+
outcome = pruneOwnedMcpServerBlock(raw, WEBPRESSO_MCP_SERVER_NAME, (command, args) => isAgentKitOwnedWpCommand(command, args, input.ownership));
|
|
801
|
+
return outcome.raw;
|
|
802
|
+
});
|
|
803
|
+
return legacyPruneResult(outcome, configPath);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Installed Codex plugin MCP manifests — the surviving delivery channel.
|
|
807
|
+
* Cheap directory probe only: no `codex plugin` subprocess belongs on the
|
|
808
|
+
* launch hot path.
|
|
809
|
+
*/
|
|
810
|
+
export function findInstalledCodexPluginMcpManifests(homeDir = process.env.HOME || homedir()) {
|
|
811
|
+
const base = join(homeDir, ".codex", "plugins", "cache");
|
|
812
|
+
const manifests = [];
|
|
813
|
+
for (const marketplace of safeReadDir(base)) {
|
|
814
|
+
const pluginDir = join(base, marketplace, "agent-kit");
|
|
815
|
+
for (const version of safeReadDir(pluginDir)) {
|
|
816
|
+
const manifest = join(pluginDir, version, ".mcp.json");
|
|
817
|
+
if (existsSync(manifest))
|
|
818
|
+
manifests.push(manifest);
|
|
819
|
+
}
|
|
690
820
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
821
|
+
return manifests;
|
|
822
|
+
}
|
|
823
|
+
function safeReadDir(dir) {
|
|
824
|
+
try {
|
|
825
|
+
const entries = readdirSync(dir);
|
|
826
|
+
if (!Array.isArray(entries))
|
|
827
|
+
return [];
|
|
828
|
+
return entries.filter((entry) => typeof entry === "string");
|
|
829
|
+
}
|
|
830
|
+
catch {
|
|
831
|
+
return [];
|
|
694
832
|
}
|
|
695
|
-
return { kind: "codex-webpresso-mcp-written", path: configPath, entryPath };
|
|
696
833
|
}
|
|
@@ -1,24 +1,36 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import type { MergeOptions } from "#cli/commands/init/merge";
|
|
3
2
|
import { type SpinnerFactory } from "#cli/commands/init/scaffolders/spinner";
|
|
3
|
+
import { type ManagedRtkDownloader } from "#tool-runtime/managed-rtk.js";
|
|
4
4
|
export interface EnsureRtkInput {
|
|
5
5
|
repoRoot: string;
|
|
6
6
|
options: MergeOptions;
|
|
7
|
-
|
|
7
|
+
/** @deprecated ignored — managed rtk no longer shells out to brew/PATH install */
|
|
8
|
+
spawn?: unknown;
|
|
8
9
|
pinFilePath?: string;
|
|
9
10
|
strict?: boolean;
|
|
10
11
|
spinnerFactory?: SpinnerFactory;
|
|
12
|
+
/** Test seam: inject archive downloader for ensureManagedRtk */
|
|
13
|
+
downloader?: ManagedRtkDownloader;
|
|
11
14
|
}
|
|
12
15
|
export type EnsureRtkResult = {
|
|
13
16
|
kind: "rtk-ok";
|
|
14
17
|
installed: boolean;
|
|
18
|
+
path: string;
|
|
15
19
|
} | {
|
|
16
20
|
kind: "rtk-skipped-dry-run";
|
|
17
21
|
} | {
|
|
18
22
|
kind: "rtk-not-found";
|
|
19
23
|
hint: string;
|
|
20
|
-
} | {
|
|
21
|
-
kind: "rtk-init-failed";
|
|
22
|
-
exitCode: number;
|
|
23
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Strip freestanding RTK PreToolUse surface from project Claude settings and
|
|
27
|
+
* delete the retired script. Also removes the historical stale legacy-runtime
|
|
28
|
+
* PreToolUse group when a direct wp-pretool-guard hook is already present.
|
|
29
|
+
* Idempotent; no-op when settings are absent or already clean.
|
|
30
|
+
*/
|
|
31
|
+
export declare function stripFreestandingRtkHooks(repoRoot: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Ensure managed (embedded-cache) rtk is available and freestanding hooks are gone.
|
|
34
|
+
* No brew, no rtk init --auto-patch, no PATH install requirement.
|
|
35
|
+
*/
|
|
24
36
|
export declare function ensureRtk(input: EnsureRtkInput): EnsureRtkResult;
|
|
@@ -1,32 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
import { makeNoopSpinnerFactory, } from "#cli/commands/init/scaffolders/spinner";
|
|
5
|
-
import {
|
|
6
|
-
const NOT_FOUND_HINT = "rtk
|
|
7
|
-
// `rtk --version` answers in ~10ms; 3s is generous headroom. Bounding the
|
|
8
|
-
// presence probe keeps a hung/wrong binary from stalling `wp setup`. The
|
|
9
|
-
// `brew install` / `rtk init` calls below are intentionally left unbounded —
|
|
10
|
-
// they are user-driven installs with inherited stdio where a fixed deadline
|
|
11
|
-
// would wrongly kill a legitimately slow run.
|
|
12
|
-
const RTK_PROBE_TIMEOUT_MS = 3000;
|
|
4
|
+
import { ensureManagedRtk } from "#tool-runtime/managed-rtk.js";
|
|
5
|
+
const NOT_FOUND_HINT = "managed rtk binary could not be installed (network/platform). Set WP_SKIP_RTK=1 to skip, or WP_RTK_BIN=/path/to/rtk.";
|
|
13
6
|
const RTK_HOOK_RELATIVE_PATH = join(".claude", "hooks", "rtk-rewrite.sh");
|
|
14
7
|
const CLAUDE_SETTINGS_RELATIVE_PATH = join(".claude", "settings.json");
|
|
15
8
|
const STALE_runtime_CLAUDE_COMMAND = "external-runtime/dist/scripts/codex-native-hook.js";
|
|
16
|
-
|
|
9
|
+
const FREESTANDING_RTK_HOOK_MARKERS = ["rtk-rewrite.sh", "rtk hook claude"];
|
|
10
|
+
/**
|
|
11
|
+
* Strip freestanding RTK PreToolUse surface from project Claude settings and
|
|
12
|
+
* delete the retired script. Also removes the historical stale legacy-runtime
|
|
13
|
+
* PreToolUse group when a direct wp-pretool-guard hook is already present.
|
|
14
|
+
* Idempotent; no-op when settings are absent or already clean.
|
|
15
|
+
*/
|
|
16
|
+
export function stripFreestandingRtkHooks(repoRoot) {
|
|
17
17
|
const hookPath = join(repoRoot, RTK_HOOK_RELATIVE_PATH);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
catch {
|
|
26
|
-
return false;
|
|
18
|
+
if (existsSync(hookPath)) {
|
|
19
|
+
try {
|
|
20
|
+
rmSync(hookPath, { force: true });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// best-effort; doctor will still flag if the file remains
|
|
24
|
+
}
|
|
27
25
|
}
|
|
28
|
-
}
|
|
29
|
-
function normalizeClaudeRtkSettings(repoRoot) {
|
|
30
26
|
const settingsPath = join(repoRoot, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31
27
|
if (!existsSync(settingsPath))
|
|
32
28
|
return;
|
|
@@ -41,73 +37,58 @@ function normalizeClaudeRtkSettings(repoRoot) {
|
|
|
41
37
|
if (!Array.isArray(groups) || groups.length === 0)
|
|
42
38
|
return;
|
|
43
39
|
const hasDirectWpGuard = groups.some((group) => (group.hooks ?? []).some((hook) => hook.command?.includes("wp-pretool-guard")));
|
|
44
|
-
if (!hasDirectWpGuard)
|
|
45
|
-
return;
|
|
46
40
|
let changed = false;
|
|
47
|
-
const nextGroups = groups
|
|
41
|
+
const nextGroups = groups
|
|
42
|
+
.map((group) => {
|
|
43
|
+
const hooks = group.hooks ?? [];
|
|
44
|
+
const nextHooks = hooks.filter((hook) => {
|
|
45
|
+
const command = hook.command ?? "";
|
|
46
|
+
if (FREESTANDING_RTK_HOOK_MARKERS.some((marker) => command.includes(marker))) {
|
|
47
|
+
changed = true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
if (nextHooks.length !== hooks.length) {
|
|
53
|
+
return { ...group, hooks: nextHooks };
|
|
54
|
+
}
|
|
55
|
+
return group;
|
|
56
|
+
})
|
|
57
|
+
.filter((group) => {
|
|
48
58
|
const commands = (group.hooks ?? []).map((hook) => hook.command ?? "");
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
if (commands.length === 0) {
|
|
60
|
+
changed = true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const isStaleOmcGroup = hasDirectWpGuard &&
|
|
64
|
+
commands.some((command) => command.includes(STALE_runtime_CLAUDE_COMMAND));
|
|
65
|
+
if (isStaleOmcGroup) {
|
|
51
66
|
changed = true;
|
|
52
|
-
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
53
70
|
});
|
|
54
71
|
if (!changed)
|
|
55
72
|
return;
|
|
56
73
|
parsed.hooks = { ...parsed.hooks, PreToolUse: nextGroups };
|
|
57
74
|
writeFileSync(settingsPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
|
|
58
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Ensure managed (embedded-cache) rtk is available and freestanding hooks are gone.
|
|
78
|
+
* No brew, no rtk init --auto-patch, no PATH install requirement.
|
|
79
|
+
*/
|
|
59
80
|
export function ensureRtk(input) {
|
|
60
81
|
if (input.options.dryRun)
|
|
61
82
|
return { kind: "rtk-skipped-dry-run" };
|
|
62
|
-
const spawn = input.spawn ?? spawnSync;
|
|
63
83
|
const spinner = (input.spinnerFactory ?? makeNoopSpinnerFactory())("rtk");
|
|
64
|
-
let installed = false;
|
|
65
84
|
spinner.start();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
const install = spawn("brew", ["install", "rtk"], { stdio: "inherit" });
|
|
73
|
-
if (install.status !== 0) {
|
|
74
|
-
spinner.fail("rtk install failed");
|
|
75
|
-
return { kind: "rtk-not-found", hint: NOT_FOUND_HINT };
|
|
76
|
-
}
|
|
77
|
-
installed = true;
|
|
78
|
-
probe = spawn("rtk", ["--version"], { encoding: "utf8", timeout: RTK_PROBE_TIMEOUT_MS });
|
|
79
|
-
if (probe.error || (probe.status !== null && probe.status !== 0)) {
|
|
80
|
-
spinner.fail("rtk not found after install");
|
|
81
|
-
return { kind: "rtk-not-found", hint: NOT_FOUND_HINT };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const installedVersion = String(probe.stdout ?? "").trim();
|
|
85
|
-
const pinCheck = checkVersionPin("rtk", installedVersion, input.pinFilePath ?? join(input.repoRoot, "compatible-versions.json"));
|
|
86
|
-
if (!pinCheck.ok) {
|
|
87
|
-
if (input.strict) {
|
|
88
|
-
spinner.fail("rtk version mismatch");
|
|
89
|
-
return { kind: "rtk-init-failed", exitCode: -1 };
|
|
90
|
-
}
|
|
91
|
-
console.warn(pinCheck.warning);
|
|
92
|
-
}
|
|
93
|
-
if (hasInstalledRtkHook(input.repoRoot)) {
|
|
94
|
-
normalizeClaudeRtkSettings(input.repoRoot);
|
|
95
|
-
spinner.succeed("rtk ready");
|
|
96
|
-
return { kind: "rtk-ok", installed };
|
|
97
|
-
}
|
|
98
|
-
const result = spawn("rtk", ["init", "-g", "--auto-patch"], {
|
|
99
|
-
cwd: input.repoRoot,
|
|
100
|
-
stdio: "inherit",
|
|
101
|
-
env: {
|
|
102
|
-
...process.env,
|
|
103
|
-
RTK_TELEMETRY_DISABLED: "1",
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
if (result.status !== 0) {
|
|
107
|
-
spinner.fail("rtk init failed");
|
|
108
|
-
return { kind: "rtk-init-failed", exitCode: result.status ?? -1 };
|
|
85
|
+
// Always strip freestanding surface first so setup never leaves CLAUDE_PROJECT_DIR hooks.
|
|
86
|
+
stripFreestandingRtkHooks(input.repoRoot);
|
|
87
|
+
const ensured = ensureManagedRtk({ downloader: input.downloader });
|
|
88
|
+
if (!ensured.ok) {
|
|
89
|
+
spinner.fail("rtk not available");
|
|
90
|
+
return { kind: "rtk-not-found", hint: `${NOT_FOUND_HINT} (${ensured.error})` };
|
|
109
91
|
}
|
|
110
|
-
normalizeClaudeRtkSettings(input.repoRoot);
|
|
111
92
|
spinner.succeed("rtk ready");
|
|
112
|
-
return { kind: "rtk-ok", installed };
|
|
93
|
+
return { kind: "rtk-ok", installed: ensured.installed, path: ensured.path };
|
|
113
94
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime detection for tools that webpresso
|
|
3
|
-
*
|
|
4
|
-
* or system package managers shouldn't be triggered silently).
|
|
2
|
+
* Runtime detection for host-install tools that webpresso does **not**
|
|
3
|
+
* managed-download (see catalog rule `managed-tool-binaries`).
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Contrasts with managed rtk (`ensureManagedRtk`): bun/vp/actionlint stay
|
|
6
|
+
* host package-manager installs. This probe only prints status + hints —
|
|
7
|
+
* it never curl-pipes or brew-installs silently.
|
|
8
|
+
*
|
|
9
|
+
* Auto-runs at the end of `wp setup`; one line per runtime.
|
|
9
10
|
*/
|
|
10
11
|
import { spawnSync } from "node:child_process";
|
|
11
12
|
function probeVersion(cmd, args = ["--version"]) {
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import type { ConvergeUnit } from "./convergence.js";
|
|
2
|
-
export type CollisionResolution = "none" | "tracked" | "removed-identical" | "removed-owned";
|
|
2
|
+
export type CollisionResolution = "none" | "tracked" | "removed-identical" | "removed-owned" | "ignored-patch-derived" | "ignored-backed-up";
|
|
3
3
|
export interface CollisionResult {
|
|
4
4
|
readonly ok: boolean;
|
|
5
5
|
readonly resolution?: CollisionResolution;
|
|
6
6
|
readonly error?: string;
|
|
7
|
+
/** Set when the collision proceeded but the caller should surface a notice. */
|
|
8
|
+
readonly warning?: string;
|
|
7
9
|
}
|
|
8
10
|
export interface ResolveCollisionOptions {
|
|
9
11
|
readonly unit: ConvergeUnit;
|
|
10
12
|
readonly desiredContent: string | Uint8Array;
|
|
11
13
|
/** Compatibility input only. Unknown content remains protected when true. */
|
|
12
14
|
readonly force?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* True when `desiredContent` was computed FROM the existing file's own
|
|
17
|
+
* content (e.g. a JSON key-preserving patch), so a divergent unmarked
|
|
18
|
+
* existing file is safe to write through: the caller already folded the
|
|
19
|
+
* user's data into `desiredContent`.
|
|
20
|
+
*/
|
|
21
|
+
readonly patchDerived?: boolean;
|
|
13
22
|
}
|
|
23
|
+
/** True when `gitPath` is git-ignored in `repoRoot` (machine-local, never committed). */
|
|
24
|
+
export declare function isGitIgnored(repoRoot: string, gitPath: string): boolean;
|
|
14
25
|
export declare function resolveUntrackedCollision(options: ResolveCollisionOptions): CollisionResult;
|