@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, lstatSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
const GIT_TIMEOUT_MS = 15_000;
|
|
4
4
|
const OWNERSHIP_MARKER = /^\uFEFF?[ \t]*(?:#|\/\/|\/\*|<!--|")?[ \t]*(?:>>> managed by webpresso\b|Generated by webpresso\b)/iu;
|
|
5
5
|
function git(repoRoot, args) {
|
|
@@ -12,6 +12,25 @@ function git(repoRoot, args) {
|
|
|
12
12
|
});
|
|
13
13
|
return { ok: result.status === 0 && !result.error };
|
|
14
14
|
}
|
|
15
|
+
/** True when `gitPath` is git-ignored in `repoRoot` (machine-local, never committed). */
|
|
16
|
+
export function isGitIgnored(repoRoot, gitPath) {
|
|
17
|
+
return git(repoRoot, ["check-ignore", "--no-index", "-q", "--", gitPath]).ok;
|
|
18
|
+
}
|
|
19
|
+
function resolveIgnoredDivergence(options) {
|
|
20
|
+
const { unit } = options;
|
|
21
|
+
if (options.patchDerived) {
|
|
22
|
+
return { ok: true, resolution: "ignored-patch-derived" };
|
|
23
|
+
}
|
|
24
|
+
const current = readFileSync(unit.targetPath);
|
|
25
|
+
const backupPath = `${unit.targetPath}.bak`;
|
|
26
|
+
writeFileSync(backupPath, current);
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
resolution: "ignored-backed-up",
|
|
30
|
+
warning: `Untracked, git-ignored ${unit.gitPath} had unmanaged content; ` +
|
|
31
|
+
`the original was backed up to ${unit.gitPath}.bak before it was overwritten.`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
15
34
|
export function resolveUntrackedCollision(options) {
|
|
16
35
|
const { unit } = options;
|
|
17
36
|
if (!existsSync(unit.targetPath))
|
|
@@ -34,6 +53,9 @@ export function resolveUntrackedCollision(options) {
|
|
|
34
53
|
rmSync(unit.targetPath);
|
|
35
54
|
return { ok: true, resolution: "removed-owned" };
|
|
36
55
|
}
|
|
56
|
+
if (unit.scope === "repo-ignored") {
|
|
57
|
+
return resolveIgnoredDivergence(options);
|
|
58
|
+
}
|
|
37
59
|
return {
|
|
38
60
|
ok: false,
|
|
39
61
|
error: `Unknown untracked content blocks ${unit.gitPath}; it was preserved${options.force ? " despite --force" : ""}. ` +
|
|
@@ -53,7 +53,7 @@ export function getPrWaitCommandHelpText() {
|
|
|
53
53
|
" --branch <name> Branch selector (defaults to current branch)",
|
|
54
54
|
" --pr <number-or-url> Explicit PR selector",
|
|
55
55
|
" --until <target> ready | checks-terminal | checks-green | merged (default: checks-terminal)",
|
|
56
|
-
" --timeout-ms <ms> Overall deadline; for ready, per-probe timeout only",
|
|
56
|
+
" --timeout-ms <ms> Overall deadline, max 300000 (5min); for ready, per-probe timeout only",
|
|
57
57
|
" --poll-interval-ms <ms> Poll interval (default: 15000)",
|
|
58
58
|
" --include-checks Include final check snapshot (default: true)",
|
|
59
59
|
" --no-include-checks Omit final check snapshot from output",
|
|
@@ -2,15 +2,29 @@ import { PR_WAIT_UNTIL_VALUES, runPrWait } from "#mcp/tools/pr-wait.js";
|
|
|
2
2
|
import { buildStatusSnapshot, formatStatusSnapshot } from "#status/snapshot.js";
|
|
3
3
|
// PR help text lives in ./pr-help.ts (dependency-free) so the `wp pr --help`
|
|
4
4
|
// fast path can render it without importing this heavy command module.
|
|
5
|
-
|
|
5
|
+
// Mirrors the hard cap `readonlyOpsBaseSchema` enforces downstream for `pr
|
|
6
|
+
// wait` (src/mcp/tools/_readonly-ops.ts). `pr status` shares the same
|
|
7
|
+
// `--timeout-ms` flag but reaches `buildStatusSnapshot` directly, which has
|
|
8
|
+
// no schema of its own, so an over-cap value was silently accepted with no
|
|
9
|
+
// error and no clamp. Validating here makes both subcommands fail the same
|
|
10
|
+
// way, with a clear message, before any network probe starts, instead of
|
|
11
|
+
// relying on a downstream schema that not every code path shares.
|
|
12
|
+
const MAX_TIMEOUT_MS = 300_000;
|
|
13
|
+
function parsePositiveInteger(value, name, max) {
|
|
6
14
|
if (value === undefined)
|
|
7
15
|
return undefined;
|
|
8
16
|
const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10);
|
|
9
17
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
10
18
|
throw new Error(`${name} must be a positive integer`);
|
|
11
19
|
}
|
|
20
|
+
if (max !== undefined && parsed > max) {
|
|
21
|
+
throw new Error(`${name} must not exceed ${max}`);
|
|
22
|
+
}
|
|
12
23
|
return parsed;
|
|
13
24
|
}
|
|
25
|
+
function parseTimeoutMs(value) {
|
|
26
|
+
return parsePositiveInteger(value, "--timeout-ms", MAX_TIMEOUT_MS);
|
|
27
|
+
}
|
|
14
28
|
function normalizeUntil(value) {
|
|
15
29
|
if (value === undefined)
|
|
16
30
|
return undefined;
|
|
@@ -26,7 +40,7 @@ export function registerPrCommand(cli) {
|
|
|
26
40
|
.option("--branch <name>", "Branch selector")
|
|
27
41
|
.option("--pr <number-or-url>", "Explicit PR selector")
|
|
28
42
|
.option("--until <target>", "ready | checks-terminal | checks-green | merged")
|
|
29
|
-
.option("--timeout-ms <ms>", "Overall deadline; for ready, per-probe timeout only")
|
|
43
|
+
.option("--timeout-ms <ms>", "Overall deadline (max 300000ms / 5min); for ready, per-probe timeout only")
|
|
30
44
|
.option("--poll-interval-ms <ms>", "Poll interval")
|
|
31
45
|
.option("--include-checks", "Include final check snapshot", { default: true })
|
|
32
46
|
.option("--max-output-bytes <n>", "Per-command output budget")
|
|
@@ -41,7 +55,7 @@ export function registerPrCommand(cli) {
|
|
|
41
55
|
includePr: true,
|
|
42
56
|
maxOutputBytes: parsePositiveInteger(options.maxOutputBytes, "--max-output-bytes"),
|
|
43
57
|
pr: options.pr === undefined ? undefined : String(options.pr),
|
|
44
|
-
timeoutMs:
|
|
58
|
+
timeoutMs: parseTimeoutMs(options.timeoutMs),
|
|
45
59
|
});
|
|
46
60
|
console.log(options.json ? JSON.stringify(snapshot, null, 2) : formatStatusSnapshot(snapshot));
|
|
47
61
|
return options.requireReady && snapshot.status !== "ready" ? 1 : 0;
|
|
@@ -57,7 +71,7 @@ export function registerPrCommand(cli) {
|
|
|
57
71
|
maxOutputBytes: parsePositiveInteger(options.maxOutputBytes, "--max-output-bytes"),
|
|
58
72
|
pollIntervalMs: parsePositiveInteger(options.pollIntervalMs, "--poll-interval-ms"),
|
|
59
73
|
pr: options.pr === undefined ? undefined : String(options.pr),
|
|
60
|
-
timeoutMs:
|
|
74
|
+
timeoutMs: parseTimeoutMs(options.timeoutMs),
|
|
61
75
|
until,
|
|
62
76
|
});
|
|
63
77
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -220,6 +220,7 @@ export interface ReviewGateDependencies {
|
|
|
220
220
|
*/
|
|
221
221
|
readonly prepareOpencodeCredential?: (account: OpencodeAccount) => string;
|
|
222
222
|
}
|
|
223
|
+
export declare function describeVerificationFailure(verification: ReviewGateVerification | undefined, gateId: string): string;
|
|
223
224
|
/**
|
|
224
225
|
* Sanctioned fallback when `wp review gate` does not reach "approved" — points
|
|
225
226
|
* at the tracked-provenance path (`wp review log` / `wp_blueprint_review_log`)
|
|
@@ -28,6 +28,7 @@ import { isPolicyApprovalVerdict } from "#review/verdict.js";
|
|
|
28
28
|
import { verificationArtifactSection, verificationPromptSummary, verifyDeliveryPromotionGates, } from "#review/delivery-verifier.js";
|
|
29
29
|
import { createDeliverySubjectAtRef, createPlanSubjectAtRef, findUnreviewedWorkingTreeChanges, } from "#review/subject.js";
|
|
30
30
|
import { runCommand } from "#mcp/tools/_shared/run-command.js";
|
|
31
|
+
import { ALLOWED_WP_COMMAND_TIMEOUT_MS } from "#trust/command-runner.js";
|
|
31
32
|
import { NotInGitRepoError, withLock } from "#paths/state-root.js";
|
|
32
33
|
import { writeFileAtomic, writeJsonFileAtomic } from "#shared-utils/write-json-file.js";
|
|
33
34
|
export const REVIEWER_IDS = [
|
|
@@ -1463,6 +1464,35 @@ function gateFailureFromSummary(summary) {
|
|
|
1463
1464
|
...(summary.retryAfterMs === undefined ? {} : { retryAfterMs: summary.retryAfterMs }),
|
|
1464
1465
|
};
|
|
1465
1466
|
}
|
|
1467
|
+
// `providerErrorDetail`'s doc comment promises "sanitized, length-bounded"
|
|
1468
|
+
// text; a checkout-creation error's message/cause chain is target/environment
|
|
1469
|
+
// text (pnpm/git stderr), not attacker-shaped, but is still capped here so
|
|
1470
|
+
// the invariant holds regardless.
|
|
1471
|
+
const MAX_PROVIDER_ERROR_DETAIL_LENGTH = 2000;
|
|
1472
|
+
// Walks an `Error.cause` chain into a single human-readable line. Used to
|
|
1473
|
+
// surface the real reason a review checkout could not be created instead of
|
|
1474
|
+
// discarding it behind a fixed string (see the bare `catch` this replaces in
|
|
1475
|
+
// `invokeProvider`). Bounded depth + a `seen` set guard against a pathological
|
|
1476
|
+
// or cyclic cause chain.
|
|
1477
|
+
function formatErrorWithCauseChain(error, maxDepth = 5) {
|
|
1478
|
+
const parts = [];
|
|
1479
|
+
const seen = new Set();
|
|
1480
|
+
let current = error;
|
|
1481
|
+
for (let depth = 0; depth < maxDepth; depth += 1) {
|
|
1482
|
+
if (current === undefined || current === null || seen.has(current))
|
|
1483
|
+
break;
|
|
1484
|
+
seen.add(current);
|
|
1485
|
+
if (current instanceof Error) {
|
|
1486
|
+
parts.push(current.message);
|
|
1487
|
+
current = current.cause;
|
|
1488
|
+
}
|
|
1489
|
+
else {
|
|
1490
|
+
parts.push(String(current));
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
return parts.join(" -> caused by: ");
|
|
1495
|
+
}
|
|
1466
1496
|
export async function invokeProvider(input, executeReview = runReviewExecution, runCatalogCommand = runCommand, createTargetCheckout = createReviewTargetCheckout,
|
|
1467
1497
|
// A thunk (not a checkout instance) so callers that never reach a real
|
|
1468
1498
|
// provider attempt — injected fakes included — never pay the checkout's
|
|
@@ -1475,19 +1505,34 @@ getSharedCheckout) {
|
|
|
1475
1505
|
checkout = existingCheckout ?? createTargetCheckout(input.projectRoot, input.targetSha);
|
|
1476
1506
|
checkout.beginAttempt();
|
|
1477
1507
|
}
|
|
1478
|
-
catch {
|
|
1508
|
+
catch (error) {
|
|
1509
|
+
// Bind + chain via Error.cause (ES2022) rather than discarding the real
|
|
1510
|
+
// failure — this exact swallow previously hid a genuine supply-chain
|
|
1511
|
+
// signal (`ERR_PNPM_TRUST_DOWNGRADE`) behind a fixed string for a full
|
|
1512
|
+
// day. See catalog/agent/rules/ts-coding-conventions.md.
|
|
1513
|
+
const isolationFailure = new Error("Unable to create the disposable review checkout; review was not started", { cause: error });
|
|
1514
|
+
let cleanupError;
|
|
1479
1515
|
if (ownsCheckout) {
|
|
1480
1516
|
try {
|
|
1481
1517
|
checkout?.cleanup();
|
|
1482
1518
|
}
|
|
1483
|
-
catch {
|
|
1484
|
-
//
|
|
1519
|
+
catch (cleanupFailure) {
|
|
1520
|
+
// Cleanup failing must not mask the primary isolation failure above;
|
|
1521
|
+
// capture it as secondary diagnostic context instead of discarding it.
|
|
1522
|
+
cleanupError = cleanupFailure;
|
|
1485
1523
|
}
|
|
1486
1524
|
}
|
|
1487
1525
|
const requestedModel = input.model ?? defaultReviewModel(input.provider) ?? "unresolved";
|
|
1488
1526
|
const model = resolveReviewModel(input.provider, requestedModel);
|
|
1527
|
+
// Chain from the original `error`, not `isolationFailure` — the latter's
|
|
1528
|
+
// own message is already the fixed prefix below, so chaining from it
|
|
1529
|
+
// would print that fixed sentence twice.
|
|
1530
|
+
const providerErrorDetail = (formatErrorWithCauseChain(error) +
|
|
1531
|
+
(cleanupError === undefined
|
|
1532
|
+
? ""
|
|
1533
|
+
: ` | cleanup after isolation failure also failed: ${formatErrorWithCauseChain(cleanupError)}`)).slice(0, MAX_PROVIDER_ERROR_DETAIL_LENGTH);
|
|
1489
1534
|
return {
|
|
1490
|
-
output:
|
|
1535
|
+
output: `${isolationFailure.message}: ${providerErrorDetail}`,
|
|
1491
1536
|
model,
|
|
1492
1537
|
reviewer: input.provider === "opencode" && model !== "unresolved"
|
|
1493
1538
|
? reviewerFamily(model)
|
|
@@ -1497,6 +1542,7 @@ getSharedCheckout) {
|
|
|
1497
1542
|
verdict: "no-verdict",
|
|
1498
1543
|
terminalClassification: "review-isolation-failure",
|
|
1499
1544
|
failureCode: "review-isolation-failure",
|
|
1545
|
+
providerErrorDetail,
|
|
1500
1546
|
};
|
|
1501
1547
|
}
|
|
1502
1548
|
try {
|
|
@@ -1799,6 +1845,64 @@ function selectGateProviders(input) {
|
|
|
1799
1845
|
const available = ordered.filter((provider) => !isProviderSuppressed(input.availability, provider));
|
|
1800
1846
|
return available.length > 0 ? available : [];
|
|
1801
1847
|
}
|
|
1848
|
+
// Builds the `message` for a "verification-failed" gate result: host-side
|
|
1849
|
+
// verification (delivery Promotion Gates + Trust Dossier parsing) died before
|
|
1850
|
+
// any review provider ran, so `attempts` is empty and the default
|
|
1851
|
+
// approval-gate-template message ("Approval gate not satisfied (...)") reads
|
|
1852
|
+
// like a reviewer rejected the work when no reviewer was ever invoked. Names
|
|
1853
|
+
// the actual cause and the corrective action so a failed gate does not cost
|
|
1854
|
+
// a repeat run just to discover what happened. Exported for tests.
|
|
1855
|
+
export function describeVerificationFailure(verification, gateId) {
|
|
1856
|
+
const resume = `Retry with --resume ${gateId} once fixed.`;
|
|
1857
|
+
const preamble = "Host verification failed before any review provider ran (0 review attempts) -- this is not a provider/quota failure.";
|
|
1858
|
+
if (!verification || verification.status !== "failed") {
|
|
1859
|
+
return `${preamble} ${resume}`;
|
|
1860
|
+
}
|
|
1861
|
+
if (verification.failureCode === "malformed-trust-dossier") {
|
|
1862
|
+
const fields = (verification.violations ?? [])
|
|
1863
|
+
.map((violation) => `${violation.section}: ${violation.message}`)
|
|
1864
|
+
.join("; ");
|
|
1865
|
+
return `${preamble} The Trust Dossier is malformed${fields ? ` (${fields})` : ""}. Fill in the named field(s) via wp_blueprint_put (or wp blueprint promote to see the same detail) before gating again. ${resume}`;
|
|
1866
|
+
}
|
|
1867
|
+
const failedCommand = verification.commands.find((command) => command.outcome !== "passed");
|
|
1868
|
+
if (verification.failureCode === "command-timed-out" && failedCommand) {
|
|
1869
|
+
const budgetSeconds = ALLOWED_WP_COMMAND_TIMEOUT_MS / 1000;
|
|
1870
|
+
return (`${preamble} The Promotion Gate command \`${failedCommand.command}\` exceeded the ` +
|
|
1871
|
+
`${budgetSeconds}s sandboxed gate budget and was killed -- this is a fixed per-command ` +
|
|
1872
|
+
"budget, not a live outage, and raising it is not the fix. Either replace this gate with " +
|
|
1873
|
+
"a faster command (e.g. `wp lint`, or a narrow `wp audit <kind>`), or mark this Promotion " +
|
|
1874
|
+
'Gates row `defer: "pre-implementation"` so it is recorded without being executed at gate ' +
|
|
1875
|
+
`time. ${resume}`);
|
|
1876
|
+
}
|
|
1877
|
+
if (failedCommand) {
|
|
1878
|
+
const exitDetail = failedCommand.exitCode === null ? "no exit code" : `exit ${failedCommand.exitCode}`;
|
|
1879
|
+
const logDetail = verification.logPath ? ` Log: ${verification.logPath}.` : "";
|
|
1880
|
+
return (`${preamble} Gate command \`${failedCommand.command}\` failed (${exitDetail}, ` +
|
|
1881
|
+
`outcome: ${failedCommand.outcome}).${logDetail} This is usually a missing dependency or ` +
|
|
1882
|
+
"stale install in the sandboxed checkout -- run the repo's install command " +
|
|
1883
|
+
`(e.g. \`wp install\`) and retry. ${resume}`);
|
|
1884
|
+
}
|
|
1885
|
+
const codeDetail = verification.failureCode ? ` (failureCode: ${verification.failureCode})` : "";
|
|
1886
|
+
return `${preamble} Verification failed${codeDetail} before any gate command ran. ${resume}`;
|
|
1887
|
+
}
|
|
1888
|
+
// Human-readable summary of why every candidate provider/reviewer is
|
|
1889
|
+
// currently suppressed in the project's availability.json cache, so a
|
|
1890
|
+
// zero-candidate gate result names the file and the reason instead of
|
|
1891
|
+
// looking like a silent no-op.
|
|
1892
|
+
function describeSuppressedCandidates(availability, candidates) {
|
|
1893
|
+
return candidates
|
|
1894
|
+
.map((provider) => {
|
|
1895
|
+
const record = availability.providers[provider];
|
|
1896
|
+
if (!record?.suppressed)
|
|
1897
|
+
return undefined;
|
|
1898
|
+
const reason = record.suppressionReason ?? record.terminalClassification ?? "unknown reason";
|
|
1899
|
+
const since = record.suppressedAt ? ` since ${record.suppressedAt}` : "";
|
|
1900
|
+
const retry = record.retryAfterMs === undefined ? "" : ` (retry window ${record.retryAfterMs}ms)`;
|
|
1901
|
+
return `${provider}: suppressed${since} (${reason}${retry})`;
|
|
1902
|
+
})
|
|
1903
|
+
.filter((value) => value !== undefined)
|
|
1904
|
+
.join("; ");
|
|
1905
|
+
}
|
|
1802
1906
|
function safeArtifactPath(blueprintPath, relativeArtifact) {
|
|
1803
1907
|
if (path.isAbsolute(relativeArtifact) || relativeArtifact.split(/[\\/]/u).includes("..")) {
|
|
1804
1908
|
throw new Error(`Unsafe review artifact path: ${relativeArtifact}`);
|
|
@@ -2127,21 +2231,30 @@ export async function runReviewGate(projectRoot, slug, input, dependencies = { i
|
|
|
2127
2231
|
// never need one (plan-purpose reuse of an existing approval, terminal
|
|
2128
2232
|
// rejections, exhausted scope). A setup failure is remembered so provider
|
|
2129
2233
|
// attempts still surface the same isolation-failure evidence.
|
|
2234
|
+
// Captures the real failure the first time checkoutFactory is
|
|
2235
|
+
// attempted this run, so the re-throw below (reached whenever a later
|
|
2236
|
+
// caller reuses the memoized failure) can chain to it via Error.cause
|
|
2237
|
+
// instead of discarding it — see ts-coding-conventions.md "never
|
|
2238
|
+
// swallow an error".
|
|
2239
|
+
let checkoutSetupError;
|
|
2130
2240
|
const ensureSharedCheckout = () => {
|
|
2131
2241
|
if (sharedCheckout !== undefined || checkoutSetupFailed)
|
|
2132
2242
|
return sharedCheckout;
|
|
2133
2243
|
try {
|
|
2134
2244
|
sharedCheckout = checkoutFactory(projectRoot, targetSha);
|
|
2135
2245
|
}
|
|
2136
|
-
catch {
|
|
2246
|
+
catch (error) {
|
|
2137
2247
|
checkoutSetupFailed = true;
|
|
2248
|
+
checkoutSetupError = error;
|
|
2138
2249
|
}
|
|
2139
2250
|
return sharedCheckout;
|
|
2140
2251
|
};
|
|
2141
2252
|
const createCheckoutForAttempt = () => {
|
|
2142
2253
|
// Only reachable when ensureSharedCheckout() already failed — a live
|
|
2143
2254
|
// shared checkout is always passed to the attempt as existingCheckout.
|
|
2144
|
-
throw new Error("review checkout setup failed"
|
|
2255
|
+
throw new Error("review checkout setup failed", {
|
|
2256
|
+
cause: checkoutSetupError,
|
|
2257
|
+
});
|
|
2145
2258
|
};
|
|
2146
2259
|
let sharedCheckoutCleaned = false;
|
|
2147
2260
|
const cleanupSharedCheckout = () => {
|
|
@@ -2171,7 +2284,7 @@ export async function runReviewGate(projectRoot, slug, input, dependencies = { i
|
|
|
2171
2284
|
prompt = renderPrompt(summary);
|
|
2172
2285
|
}
|
|
2173
2286
|
if (verification?.status === "failed") {
|
|
2174
|
-
const result = makeResult(currentApprovalGate(), "verification-failed");
|
|
2287
|
+
const result = makeResult(currentApprovalGate(), "verification-failed", describeVerificationFailure(verification, gateId));
|
|
2175
2288
|
assertReviewRefUnchanged(projectRoot, input.target ?? "HEAD", targetSha);
|
|
2176
2289
|
persistGateState(projectRoot, result);
|
|
2177
2290
|
cleanupSharedCheckout();
|
|
@@ -2243,6 +2356,23 @@ export async function runReviewGate(projectRoot, slug, input, dependencies = { i
|
|
|
2243
2356
|
availability,
|
|
2244
2357
|
...(explicitProvider ? { explicitProvider } : {}),
|
|
2245
2358
|
});
|
|
2359
|
+
if (providerQueue.length === 0) {
|
|
2360
|
+
const candidates = hostNeutralProviderOrder(explicitProvider);
|
|
2361
|
+
const detail = describeSuppressedCandidates(availability, candidates);
|
|
2362
|
+
const availabilityFile = path
|
|
2363
|
+
.relative(projectRoot, reviewAvailabilityPath(projectRoot))
|
|
2364
|
+
.replace(/\\/gu, "/");
|
|
2365
|
+
const result = makeResult(approvalGate, "needs-review", `No review provider was invoked: every candidate provider is suppressed by the ` +
|
|
2366
|
+
`cached availability record at ${availabilityFile}${detail ? ` (${detail})` : ""}. ` +
|
|
2367
|
+
"This is a stale-cache condition, not a live provider outage -- if the " +
|
|
2368
|
+
"account/provider has since recovered or been re-authenticated, delete the stale " +
|
|
2369
|
+
`entry (or the whole file) at ${availabilityFile}, or wait for its suppression ` +
|
|
2370
|
+
`window to elapse, then retry with --resume ${gateId}.`);
|
|
2371
|
+
assertReviewRefUnchanged(projectRoot, input.target ?? "HEAD", targetSha);
|
|
2372
|
+
persistGateState(projectRoot, result);
|
|
2373
|
+
cleanupSharedCheckout();
|
|
2374
|
+
return result;
|
|
2375
|
+
}
|
|
2246
2376
|
const usedReviewers = new Set([
|
|
2247
2377
|
...attempts
|
|
2248
2378
|
.filter((attempt) => !isRetryableGateAttempt(attempt))
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `wp sync` — projects the canonical webpresso rule/skill catalog into
|
|
3
|
-
* supported host surfaces.
|
|
2
|
+
* `wp sync` — projects the canonical webpresso rule/skill/agent catalog into
|
|
3
|
+
* the supported host surfaces.
|
|
4
4
|
*
|
|
5
|
-
* Projects unified rule + skill content (catalog ∪ consumer) into
|
|
6
|
-
* surfaces according to `DEFAULT_UNIFIED_CONSUMERS`.
|
|
5
|
+
* Projects unified rule + skill + agent content (catalog ∪ consumer) into
|
|
6
|
+
* per-IDE surfaces according to `DEFAULT_UNIFIED_CONSUMERS`.
|
|
7
7
|
*
|
|
8
8
|
* Flags:
|
|
9
|
-
* --kind rules|skills Filter to a single kind (default:
|
|
10
|
-
* --check
|
|
9
|
+
* --kind rules|skills|agents Filter to a single kind (default: all).
|
|
10
|
+
* --check Dry-run; exit 1 on first drift, no writes.
|
|
11
11
|
*/
|
|
12
12
|
import type { CAC } from "cac";
|
|
13
|
+
import type { ContentKind } from "#content/loader";
|
|
13
14
|
import { type ConsumerContext } from "./init/detect-consumer.js";
|
|
15
|
+
export declare function parseKind(input: string | undefined): readonly ContentKind[] | undefined;
|
|
14
16
|
export declare function computeSyncDriftCounts(input: {
|
|
15
17
|
readonly surfaceFixCount: number;
|
|
16
18
|
readonly agentsChanged: boolean;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `wp sync` — projects the canonical webpresso rule/skill catalog into
|
|
3
|
-
* supported host surfaces.
|
|
2
|
+
* `wp sync` — projects the canonical webpresso rule/skill/agent catalog into
|
|
3
|
+
* the supported host surfaces.
|
|
4
4
|
*
|
|
5
|
-
* Projects unified rule + skill content (catalog ∪ consumer) into
|
|
6
|
-
* surfaces according to `DEFAULT_UNIFIED_CONSUMERS`.
|
|
5
|
+
* Projects unified rule + skill + agent content (catalog ∪ consumer) into
|
|
6
|
+
* per-IDE surfaces according to `DEFAULT_UNIFIED_CONSUMERS`.
|
|
7
7
|
*
|
|
8
8
|
* Flags:
|
|
9
|
-
* --kind rules|skills Filter to a single kind (default:
|
|
10
|
-
* --check
|
|
9
|
+
* --kind rules|skills|agents Filter to a single kind (default: all).
|
|
10
|
+
* --check Dry-run; exit 1 on first drift, no writes.
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
@@ -25,14 +25,16 @@ function commandError(message, exitCode = 1) {
|
|
|
25
25
|
err.exitCode = exitCode;
|
|
26
26
|
return err;
|
|
27
27
|
}
|
|
28
|
-
function parseKind(input) {
|
|
28
|
+
export function parseKind(input) {
|
|
29
29
|
if (input === undefined)
|
|
30
30
|
return undefined;
|
|
31
31
|
if (input === "rules" || input === "rule")
|
|
32
32
|
return ["rule"];
|
|
33
33
|
if (input === "skills" || input === "skill")
|
|
34
34
|
return ["skill"];
|
|
35
|
-
|
|
35
|
+
if (input === "agents" || input === "agent")
|
|
36
|
+
return ["agent"];
|
|
37
|
+
throw commandError(`Invalid --kind: ${input}. Must be 'rules', 'skills', or 'agents'.`);
|
|
36
38
|
}
|
|
37
39
|
function formatMismatches(mismatches) {
|
|
38
40
|
return mismatches.map((m) => ` - [${m.consumerId}] ${m.targetPath}: ${m.reason}`).join("\n");
|
|
@@ -90,7 +92,7 @@ export function isUnmaterializedAgentKitSourceWorktree(consumer) {
|
|
|
90
92
|
export function registerSyncCommand(cli) {
|
|
91
93
|
cli
|
|
92
94
|
.command("sync", "Sync agent rules + skills across all supported host surfaces")
|
|
93
|
-
.option("--kind <kind>", "Filter: rules | skills (default:
|
|
95
|
+
.option("--kind <kind>", "Filter: rules | skills | agents (default: all)")
|
|
94
96
|
.option("--check", "Exit 1 on drift; no writes")
|
|
95
97
|
.action(async (options = {}) => {
|
|
96
98
|
const kinds = parseKind(options.kind);
|
|
@@ -8,6 +8,18 @@ export interface WorktreeCommandOptions {
|
|
|
8
8
|
prefix?: string;
|
|
9
9
|
dryRun?: boolean;
|
|
10
10
|
force?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Bypass the "primary must be on the expected branch" merge-cleanup
|
|
13
|
+
* refusal by switching a CLEAN primary checkout to that branch. Never
|
|
14
|
+
* bypasses the "primary is dirty" refusal — a dirty primary always
|
|
15
|
+
* refuses, regardless of this flag (see `decideMergeCleanup`).
|
|
16
|
+
*/
|
|
17
|
+
switchPrimary?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Renamed to `switchPrimary`. Kept as an accepted alias only
|
|
20
|
+
* (mapped from the CLI's legacy `--stash-primary` flag) — it no longer
|
|
21
|
+
* stashes anything; a dirty primary always refuses regardless of this flag.
|
|
22
|
+
*/
|
|
11
23
|
stashPrimary?: boolean;
|
|
12
24
|
noSetup?: boolean;
|
|
13
25
|
/** Dependency hydration for `new` only: install-locked (default) or reconcile. */
|
|
@@ -41,7 +53,6 @@ export interface MergeCleanupPlan {
|
|
|
41
53
|
readonly removeArgs: readonly string[];
|
|
42
54
|
readonly fetchArgs: readonly string[];
|
|
43
55
|
readonly mergeArgs: readonly string[];
|
|
44
|
-
readonly stashArgs?: readonly string[];
|
|
45
56
|
readonly switchArgs?: readonly string[];
|
|
46
57
|
readonly preservedPrimaryBranch?: string;
|
|
47
58
|
}
|
|
@@ -54,7 +65,18 @@ export interface MergeCleanupDecisionInput {
|
|
|
54
65
|
readonly currentBranch: string;
|
|
55
66
|
readonly repoDirty: boolean;
|
|
56
67
|
readonly targetDirty: boolean;
|
|
57
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Bypasses the wrong-branch refusal for a CLEAN primary only. A dirty
|
|
70
|
+
* primary always refuses — see `decideMergeCleanup` — this never bypasses
|
|
71
|
+
* that refusal, and merge-cleanup never stashes primary state.
|
|
72
|
+
*/
|
|
73
|
+
readonly switchPrimary?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* When the primary is dirty, optional porcelain -z lists the offending
|
|
76
|
+
* paths in the refusal message. Omitted messages stay generic (e.g. for
|
|
77
|
+
* unit tests that don't care about message content).
|
|
78
|
+
*/
|
|
79
|
+
readonly repoPorcelainZ?: string;
|
|
58
80
|
/**
|
|
59
81
|
* When target is dirty, optional porcelain -z + slug enrich the refusal message
|
|
60
82
|
* (scaffold-only vs other dirt). Omitted messages stay generic for unit tests.
|