@webpresso/agent-kit 3.2.0 → 3.3.1
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/bin/_run.js +4 -1
- package/catalog/agent/rules/pre-implementation.md +15 -0
- package/dist/esm/audit/blueprint-trust.js +14 -10
- package/dist/esm/audit/changeset-evidence.d.ts +23 -0
- package/dist/esm/audit/changeset-evidence.js +30 -0
- package/dist/esm/audit/changeset-required.d.ts +10 -0
- package/dist/esm/audit/changeset-required.js +73 -0
- package/dist/esm/audit/ci-guardrails-detection.d.ts +27 -0
- package/dist/esm/audit/ci-guardrails-detection.js +68 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +3 -13
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +6 -1
- package/dist/esm/blueprint/core/parser.js +61 -4
- package/dist/esm/blueprint/core/schema.d.ts +1 -0
- package/dist/esm/blueprint/core/schema.js +26 -0
- package/dist/esm/blueprint/lifecycle/audit.js +22 -10
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +39 -8
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +97 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +161 -0
- package/dist/esm/blueprint/sync/client.d.ts +2 -1
- package/dist/esm/blueprint/sync/client.js +1 -1
- package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
- package/dist/esm/blueprint/trust/dossier.js +40 -6
- package/dist/esm/blueprint/trust/promotion.js +12 -0
- package/dist/esm/build/cli-mcp-parity.js +5 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +9 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +2 -0
- package/dist/esm/cli/commands/agent-launch.js +31 -20
- package/dist/esm/cli/commands/audit.js +4 -0
- package/dist/esm/cli/commands/blueprint/abandon.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/abandon.js +178 -0
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +1 -0
- package/dist/esm/cli/commands/blueprint/router.d.ts +2 -0
- package/dist/esm/cli/commands/blueprint/router.js +5 -2
- package/dist/esm/cli/commands/ci-preflight.js +24 -3
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +87 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +189 -23
- package/dist/esm/cli/commands/init/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/index.js +2 -2
- package/dist/esm/cli/commands/init/package-root.d.ts +7 -0
- package/dist/esm/cli/commands/init/package-root.js +10 -2
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +12 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +31 -30
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +3 -1
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +17 -2
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +189 -17
- package/dist/esm/cli/commands/opencode-probe.d.ts +118 -0
- package/dist/esm/cli/commands/opencode-probe.js +323 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +23 -0
- package/dist/esm/cli/commands/opencode-rotate.js +23 -11
- package/dist/esm/cli/commands/quality-runner.js +8 -4
- package/dist/esm/cli/commands/review.js +27 -5
- package/dist/esm/cli/commands/rust-check.d.ts +24 -0
- package/dist/esm/cli/commands/rust-check.js +110 -0
- package/dist/esm/cli/commands/typecheck.d.ts +1 -0
- package/dist/esm/cli/commands/typecheck.js +30 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +14 -0
- package/dist/esm/cli/commands/worktree/router-dispatch.js +138 -2
- package/dist/esm/cli/commands/worktree/router.js +10 -0
- package/dist/esm/hooks/doctor.d.ts +2 -0
- package/dist/esm/hooks/doctor.js +37 -1
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
- package/dist/esm/hooks/shared/types.js +27 -1
- package/dist/esm/hooks/stop/qa-changed-files.js +18 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +31 -7
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/audit.d.ts +2 -1
- package/dist/esm/mcp/tools/audit.js +12 -0
- package/dist/esm/mcp/tools/audits.d.ts +1 -0
- package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
- package/dist/esm/package.json +2 -0
- package/dist/esm/platform/client.d.ts +7 -1
- package/dist/esm/platform/github-contract-source.d.ts +2 -1
- package/dist/esm/pr-description/contract.js +24 -4
- package/dist/esm/review/authority.js +14 -9
- package/dist/esm/review/availability.d.ts +11 -0
- package/dist/esm/review/availability.js +18 -3
- package/dist/esm/review/execution/adapters.d.ts +11 -1
- package/dist/esm/review/execution/adapters.js +10 -1
- package/dist/esm/review/execution/artifacts.d.ts +1 -1
- package/dist/esm/review/execution/review-checkout.d.ts +6 -0
- package/dist/esm/review/execution/review-checkout.js +61 -16
- package/dist/esm/review/execution/supervisor.js +31 -4
- package/dist/esm/review/execution/types.d.ts +1 -0
- package/dist/esm/review/subject.d.ts +13 -0
- package/dist/esm/review/subject.js +42 -4
- package/dist/esm/rust/affected-manifests.d.ts +38 -0
- package/dist/esm/rust/affected-manifests.js +69 -0
- package/dist/esm/session-memory/fetch-index.d.ts +1 -1
- package/dist/esm/status/snapshot.js +2 -6
- package/dist/esm/test-helpers/global-setup.d.ts +0 -1
- package/dist/esm/test-helpers/global-setup.js +18 -15
- package/dist/esm/typecheck/planner.d.ts +9 -1
- package/dist/esm/typecheck/planner.js +31 -0
- package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
- package/dist/esm/worktrees/orphan-scan.js +211 -0
- package/dist/esm/worktrees/owner-dirt.d.ts +40 -0
- package/dist/esm/worktrees/owner-dirt.js +104 -0
- package/package.json +14 -12
|
@@ -8,6 +8,10 @@ const HELP_TEXT = [
|
|
|
8
8
|
" list [--all] List repo or global managed worktrees",
|
|
9
9
|
" refresh [--all|--repo <dir>] Refresh managed registry from git",
|
|
10
10
|
" prune --all Prune stale managed registry entries",
|
|
11
|
+
" prune --orphans [--repo-id <id>] [--force|--yes]",
|
|
12
|
+
" Scan managed roots for on-disk dirs absent",
|
|
13
|
+
" from git worktree list; dry-run by default,",
|
|
14
|
+
" removes only proven-clean orphans with --force",
|
|
11
15
|
" migrate Move legacy sibling _worktrees",
|
|
12
16
|
" adopt <blueprint-slug> <path> Claim existing checkout as owner",
|
|
13
17
|
" rebind <blueprint-slug> [--path <path>] Repair owner metadata",
|
|
@@ -24,6 +28,9 @@ const HELP_TEXT = [
|
|
|
24
28
|
" --no-setup Skip configured worktree.setup scripts (new only)",
|
|
25
29
|
" --force Skip configured teardown scripts and force removal",
|
|
26
30
|
" --stash-primary Stash dirty primary state and switch to the expected primary branch before merge-cleanup",
|
|
31
|
+
" --orphans Filesystem-level orphan sweep for prune (see prune --orphans above)",
|
|
32
|
+
" --repo-id <id> Scope prune --orphans to one repo namespace under repos/<id>",
|
|
33
|
+
" --yes Alias for --force (prune --orphans only)",
|
|
27
34
|
" --cwd <dir> Repo root (default: process.cwd())",
|
|
28
35
|
].join("\n");
|
|
29
36
|
export function registerWorktreeRouter(cli) {
|
|
@@ -40,6 +47,9 @@ export function registerWorktreeRouter(cli) {
|
|
|
40
47
|
.option("--stash-primary", "Stash dirty primary state and switch to the expected primary branch before merge-cleanup")
|
|
41
48
|
.option("--all", "Use global managed inventory where supported")
|
|
42
49
|
.option("--repo <dir>", "Repo root for refresh/rebind (default: cwd)")
|
|
50
|
+
.option("--orphans", "Filesystem-level orphan sweep for prune (dry-run unless --force/--yes)")
|
|
51
|
+
.option("--repo-id <id>", "Scope prune --orphans to one repo namespace under repos/<id>")
|
|
52
|
+
.option("--yes", "Alias for --force (prune --orphans only)")
|
|
43
53
|
.option("--cwd <dir>", "Repo root to operate from (default: process.cwd())")
|
|
44
54
|
.action(async (subcommand, args, options) => {
|
|
45
55
|
if (!subcommand) {
|
|
@@ -118,6 +118,8 @@ export declare function checkCodexConfigKeys(configFilePath?: string, deps?: Hos
|
|
|
118
118
|
export declare function checkProjectMcpPins(cwd?: string, deps?: HostConfigDoctorDeps): DoctorCheck;
|
|
119
119
|
export interface ClaudeMcpDuplicateRegistrationOptions {
|
|
120
120
|
readonly home?: string;
|
|
121
|
+
/** Project root used to discover a project-local `.mcp.json` registration. */
|
|
122
|
+
readonly projectRoot?: string;
|
|
121
123
|
readonly exists?: (path: string) => boolean;
|
|
122
124
|
readonly readFile?: (path: string) => string;
|
|
123
125
|
}
|
package/dist/esm/hooks/doctor.js
CHANGED
|
@@ -1838,6 +1838,29 @@ export function checkClaudeMcpDuplicateRegistration(options = {}) {
|
|
|
1838
1838
|
manualSources.push(`project scope ${projectDir}`);
|
|
1839
1839
|
}
|
|
1840
1840
|
}
|
|
1841
|
+
// Also scan project-local `.mcp.json` (and ancestors). Claude Code honors
|
|
1842
|
+
// this file as a third registration surface; the home JSON projects map
|
|
1843
|
+
// does not always mirror it, so a plugin + project-file pair still doubles
|
|
1844
|
+
// the tool list without the home-JSON check ever firing.
|
|
1845
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
1846
|
+
for (const candidate of collectProjectMcpJsonCandidates(projectRoot)) {
|
|
1847
|
+
if (!exists(candidate))
|
|
1848
|
+
continue;
|
|
1849
|
+
try {
|
|
1850
|
+
const projectParsed = JSON.parse(readFile(candidate));
|
|
1851
|
+
if (projectParsed !== null &&
|
|
1852
|
+
typeof projectParsed === "object" &&
|
|
1853
|
+
"mcpServers" in projectParsed &&
|
|
1854
|
+
projectParsed.mcpServers &&
|
|
1855
|
+
"webpresso" in
|
|
1856
|
+
(projectParsed.mcpServers ?? {})) {
|
|
1857
|
+
manualSources.push(`project file ${candidate}`);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
catch {
|
|
1861
|
+
// Unreadable/malformed .mcp.json is not a duplicate signal — degrade.
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1841
1864
|
if (manualSources.length === 0) {
|
|
1842
1865
|
return { name, ok: true, detail: "plugin-scoped registration only" };
|
|
1843
1866
|
}
|
|
@@ -1851,6 +1874,19 @@ export function checkClaudeMcpDuplicateRegistration(options = {}) {
|
|
|
1851
1874
|
`claude mcp remove webpresso (run inside the project directory for project-scope entries).`,
|
|
1852
1875
|
};
|
|
1853
1876
|
}
|
|
1877
|
+
/** Walk projectRoot → filesystem root collecting `.mcp.json` paths that may register MCP servers. */
|
|
1878
|
+
function collectProjectMcpJsonCandidates(projectRoot) {
|
|
1879
|
+
const candidates = [];
|
|
1880
|
+
let current = projectRoot;
|
|
1881
|
+
for (;;) {
|
|
1882
|
+
candidates.push(join(current, ".mcp.json"));
|
|
1883
|
+
const parent = dirname(current);
|
|
1884
|
+
if (parent === current)
|
|
1885
|
+
break;
|
|
1886
|
+
current = parent;
|
|
1887
|
+
}
|
|
1888
|
+
return candidates;
|
|
1889
|
+
}
|
|
1854
1890
|
const QUOTED_TOML_STRING = /"((?:[^"\\]|\\.)*)"/gu;
|
|
1855
1891
|
const DANGLING_SYMLINK_SCAN_DEPTH = 3;
|
|
1856
1892
|
function collectDanglingWorktreePaths(raw, worktreesRoot, exists, alreadyReported) {
|
|
@@ -2055,7 +2091,7 @@ export async function runHooksDoctor(opts = {}) {
|
|
|
2055
2091
|
checks.push(checkCodexConfigKeys());
|
|
2056
2092
|
}
|
|
2057
2093
|
checks.push(checkProjectMcpPins(opts.cwd));
|
|
2058
|
-
checks.push(checkClaudeMcpDuplicateRegistration());
|
|
2094
|
+
checks.push(checkClaudeMcpDuplicateRegistration({ projectRoot: opts.cwd ?? process.cwd() }));
|
|
2059
2095
|
checks.push({ advisory: true, ...checkDanglingHostPaths() });
|
|
2060
2096
|
checks.push({ advisory: true, ...checkRootLauncherContract() });
|
|
2061
2097
|
checks.push({ advisory: true, ...checkNativePluginRuntime() });
|
|
@@ -95,7 +95,12 @@ export function validateWorktreeDiscipline(input) {
|
|
|
95
95
|
return { validator: VALIDATOR_NAME, passed: true };
|
|
96
96
|
}
|
|
97
97
|
function normalizedToolName(input) {
|
|
98
|
-
|
|
98
|
+
// tool_name is typed string but malformed host envelopes can deliver non-strings;
|
|
99
|
+
// never call string methods without a typeof guard (same class as isBashInput).
|
|
100
|
+
const raw = input.tool_name;
|
|
101
|
+
if (typeof raw !== "string")
|
|
102
|
+
return "";
|
|
103
|
+
return raw.toLowerCase().replaceAll(/[^a-z]/gu, "");
|
|
99
104
|
}
|
|
100
105
|
function applyPatchTargets(input) {
|
|
101
106
|
if (!normalizedToolName(input).endsWith("applypatch"))
|
|
@@ -78,7 +78,33 @@ export function resolveHookSessionId(input, env = process.env) {
|
|
|
78
78
|
return undefined;
|
|
79
79
|
}
|
|
80
80
|
export function isBashInput(input) {
|
|
81
|
-
|
|
81
|
+
// A bare `command` field is NOT sufficient: Codex delivers `apply_patch` patch
|
|
82
|
+
// text inside `tool_input.command`, the same field a shell command uses.
|
|
83
|
+
// Discriminate on the tool name instead. Absent / non-string tool name ⇒
|
|
84
|
+
// unknown/legacy host ⇒ classify as shell so command guards still fire (a
|
|
85
|
+
// guard must fail toward inspecting, never toward silently skipping). Malformed
|
|
86
|
+
// non-string tool_name must not throw (optional chaining only guards nullish).
|
|
87
|
+
if (!("command" in (input.tool_input || {})))
|
|
88
|
+
return false;
|
|
89
|
+
const rawToolName = input.tool_name;
|
|
90
|
+
const toolName = typeof rawToolName === "string" ? rawToolName.trim().toLowerCase() : undefined;
|
|
91
|
+
if (!toolName)
|
|
92
|
+
return true;
|
|
93
|
+
return (toolName === "bash" ||
|
|
94
|
+
toolName === "shell" ||
|
|
95
|
+
toolName === "exec" ||
|
|
96
|
+
toolName === "exec_command" ||
|
|
97
|
+
// Codex names its sandboxed shell tool `local_shell` (see
|
|
98
|
+
// session-memory/provider-readers `local_shell_call`); without it a real
|
|
99
|
+
// shell command bypasses the danger/forbidden/worktree guards.
|
|
100
|
+
toolName === "local_shell" ||
|
|
101
|
+
// Grok's shell tool: `run_terminal_command` (envelope name) and the internal
|
|
102
|
+
// `run_terminal_cmd` its Bash-mapping surfaces (docs.x.ai/build/features/hooks).
|
|
103
|
+
toolName === "run_terminal_command" ||
|
|
104
|
+
toolName === "run_terminal_cmd" ||
|
|
105
|
+
toolName.endsWith(".exec_command") ||
|
|
106
|
+
toolName.endsWith("_exec_command") ||
|
|
107
|
+
toolName.endsWith("__exec_command"));
|
|
82
108
|
}
|
|
83
109
|
export function isFileEditInput(input) {
|
|
84
110
|
const toolInput = input.tool_input || {};
|
|
@@ -98,6 +98,24 @@ const CROSS_CUTTING_TEST_DEPENDENTS = [
|
|
|
98
98
|
when: /^(README\.md|docs\/guides\/session-memory\.md)$/u,
|
|
99
99
|
tests: ["src/mcp/tools/session-docs.test.ts", "src/build/package-manifest.test.ts"],
|
|
100
100
|
},
|
|
101
|
+
{
|
|
102
|
+
// blueprint-server.ts owns the MCP architecture contract (tool naming,
|
|
103
|
+
// handler wiring, transport shape). Its architecture test is not colocated
|
|
104
|
+
// under the same basename, so --affected would otherwise skip it.
|
|
105
|
+
when: /^src\/mcp\/blueprint-server\./u,
|
|
106
|
+
tests: ["src/mcp/blueprint-server.test-architecture.test.ts"],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
// Package surface / publishability contracts live in the audit package,
|
|
110
|
+
// not next to package.json or the manifest builder.
|
|
111
|
+
when: /^(package\.json|src\/build\/package-manifest\.ts)$/u,
|
|
112
|
+
tests: ["src/audit/package-surface.test.ts"],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
// Marketplace and plugin.json validation is a build-side contract test.
|
|
116
|
+
when: /^\.claude-plugin\/(marketplace|plugin)\.json$/u,
|
|
117
|
+
tests: ["src/build/validate-marketplace.test.ts"],
|
|
118
|
+
},
|
|
101
119
|
];
|
|
102
120
|
/**
|
|
103
121
|
* Every test path the cross-cutting map can yield.
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import matter from "gray-matter";
|
|
5
5
|
import { parseBlueprint } from "#core/parser";
|
|
6
|
+
import { taskIdSchema } from "#core/schema";
|
|
6
7
|
import { evaluateApprovalGate, formatApprovalGateRequirement } from "#lifecycle/audit";
|
|
7
8
|
import { canonicalizeBlueprint } from "#lifecycle/canonicalize.js";
|
|
8
9
|
import { BlueprintCreationService } from "#service/BlueprintCreationService";
|
|
@@ -40,7 +41,7 @@ const createSchema = MutationTarget.extend({
|
|
|
40
41
|
head_at_ingest: z.string().nullable().optional(),
|
|
41
42
|
});
|
|
42
43
|
const putTaskSchema = z.object({
|
|
43
|
-
id:
|
|
44
|
+
id: taskIdSchema,
|
|
44
45
|
title: z.string().min(1),
|
|
45
46
|
status: z.enum(["todo", "in-progress", "blocked", "done", "dropped"]).default("todo"),
|
|
46
47
|
wave: z.string().optional(),
|
|
@@ -72,6 +73,12 @@ const putPromotionGateSchema = z.object({
|
|
|
72
73
|
command: z.string().min(1),
|
|
73
74
|
expected_outcome: z.string().min(1),
|
|
74
75
|
last_result: z.string().min(1),
|
|
76
|
+
// Explicit per-gate pre-implementation deferral. When set, `wp blueprint
|
|
77
|
+
// promote` skips executing/existence-checking this gate's command entirely
|
|
78
|
+
// and records a `deferred: ...` Last result instead. See
|
|
79
|
+
// src/blueprint/trust/promotion.ts and catalog/agent/rules
|
|
80
|
+
// /pre-implementation.md's Promotion gate section.
|
|
81
|
+
defer: z.literal("pre-implementation").optional(),
|
|
75
82
|
});
|
|
76
83
|
const putTrustDossierSchema = z.object({
|
|
77
84
|
readiness: putTrustReadinessSchema,
|
|
@@ -180,6 +187,11 @@ const putTrustDossierJsonSchema = {
|
|
|
180
187
|
command: { type: "string", description: "wp or ./bin/wp facade command" },
|
|
181
188
|
expected_outcome: { type: "string" },
|
|
182
189
|
last_result: { type: "string" },
|
|
190
|
+
defer: {
|
|
191
|
+
type: "string",
|
|
192
|
+
const: "pre-implementation",
|
|
193
|
+
description: "Explicit opt-in: skip executing/existence-checking this gate at promote time",
|
|
194
|
+
},
|
|
183
195
|
},
|
|
184
196
|
required: ["gate", "command", "expected_outcome", "last_result"],
|
|
185
197
|
additionalProperties: false,
|
|
@@ -323,12 +335,7 @@ function renderTrustDossierMarkdown(dossier) {
|
|
|
323
335
|
"",
|
|
324
336
|
"### Promotion Gates",
|
|
325
337
|
"",
|
|
326
|
-
...
|
|
327
|
-
gate.gate,
|
|
328
|
-
gate.command,
|
|
329
|
-
gate.expected_outcome,
|
|
330
|
-
gate.last_result,
|
|
331
|
-
])),
|
|
338
|
+
...renderPromotionGatesTable(dossier.promotion_gates),
|
|
332
339
|
"",
|
|
333
340
|
"### Residual Unknowns",
|
|
334
341
|
"",
|
|
@@ -336,6 +343,23 @@ function renderTrustDossierMarkdown(dossier) {
|
|
|
336
343
|
"",
|
|
337
344
|
];
|
|
338
345
|
}
|
|
346
|
+
// Only emits the (opt-in) Defer column when at least one gate uses it, so
|
|
347
|
+
// the common no-defer case keeps rendering the legacy 4-column table exactly
|
|
348
|
+
// as before -- matching src/blueprint/trust/dossier.ts's parser, which
|
|
349
|
+
// accepts either width.
|
|
350
|
+
function renderPromotionGatesTable(gates) {
|
|
351
|
+
const hasDefer = gates.some((gate) => gate.defer !== undefined);
|
|
352
|
+
if (!hasDefer) {
|
|
353
|
+
return renderMarkdownTable(["Gate", "Command", "Expected outcome", "Last result"], gates.map((gate) => [gate.gate, gate.command, gate.expected_outcome, gate.last_result]));
|
|
354
|
+
}
|
|
355
|
+
return renderMarkdownTable(["Gate", "Command", "Expected outcome", "Last result", "Defer"], gates.map((gate) => [
|
|
356
|
+
gate.gate,
|
|
357
|
+
gate.command,
|
|
358
|
+
gate.expected_outcome,
|
|
359
|
+
gate.last_result,
|
|
360
|
+
gate.defer ?? "",
|
|
361
|
+
]));
|
|
362
|
+
}
|
|
339
363
|
function normalizeResidualUnknowns(value) {
|
|
340
364
|
if (value === undefined)
|
|
341
365
|
return "None.";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const MCP_AUDIT_KINDS: readonly ["test-smells", "tph-e2e", "agents", "catalog-drift", "package-surface", "reference-parity-matrix", "docs-frontmatter", "blueprint-readme-drift", "blueprint-pr-coverage", "blueprint-lifecycle", "blueprint-trust", "architecture-drift", "cloudflare-deploy-contract", "absolute-path-policy", "no-first-party-mjs", "no-math-random", "roadmap-links", "bundle-budget", "commit-message", "tech-debt", "supported-agent-clis", "hook-surface", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "host-substitution-risk", "ai-contracts", "atomic-state-writes", "no-relative-package-scripts", "toolchain-isolation", "open-source-licenses", "secrets-policy", "no-dev-vars", "github-actions-secrets", "secret-provider-quarantine", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "test-scan-perf", "session-memory-hardcut", "worktree-main-ownership", "typescript-version", "ci-guardrails-wiring", "sast"];
|
|
1
|
+
export declare const MCP_AUDIT_KINDS: readonly ["test-smells", "tph-e2e", "agents", "catalog-drift", "package-surface", "reference-parity-matrix", "docs-frontmatter", "blueprint-readme-drift", "blueprint-pr-coverage", "blueprint-lifecycle", "blueprint-trust", "architecture-drift", "cloudflare-deploy-contract", "absolute-path-policy", "no-first-party-mjs", "no-math-random", "roadmap-links", "bundle-budget", "commit-message", "tech-debt", "supported-agent-clis", "hook-surface", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "host-substitution-risk", "ai-contracts", "atomic-state-writes", "no-relative-package-scripts", "toolchain-isolation", "open-source-licenses", "secrets-policy", "no-dev-vars", "github-actions-secrets", "secret-provider-quarantine", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "test-scan-perf", "session-memory-hardcut", "worktree-main-ownership", "typescript-version", "ci-guardrails-wiring", "sast", "changeset-required"];
|
|
2
2
|
export type MCPAuditKind = (typeof MCP_AUDIT_KINDS)[number];
|
|
3
3
|
export declare function isMCPAuditKind(value: string): value is MCPAuditKind;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import type { ToolDescriptor } from "#mcp/auto-discover";
|
|
18
|
-
export declare const KINDS: readonly ["test-smells", "tph-e2e", "agents", "catalog-drift", "package-surface", "reference-parity-matrix", "docs-frontmatter", "blueprint-readme-drift", "blueprint-pr-coverage", "blueprint-lifecycle", "blueprint-trust", "architecture-drift", "cloudflare-deploy-contract", "absolute-path-policy", "no-first-party-mjs", "no-math-random", "roadmap-links", "bundle-budget", "commit-message", "tech-debt", "supported-agent-clis", "hook-surface", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "host-substitution-risk", "ai-contracts", "atomic-state-writes", "no-relative-package-scripts", "toolchain-isolation", "open-source-licenses", "secrets-policy", "no-dev-vars", "github-actions-secrets", "secret-provider-quarantine", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "test-scan-perf", "session-memory-hardcut", "worktree-main-ownership", "typescript-version", "ci-guardrails-wiring", "sast"];
|
|
18
|
+
export declare const KINDS: readonly ["test-smells", "tph-e2e", "agents", "catalog-drift", "package-surface", "reference-parity-matrix", "docs-frontmatter", "blueprint-readme-drift", "blueprint-pr-coverage", "blueprint-lifecycle", "blueprint-trust", "architecture-drift", "cloudflare-deploy-contract", "absolute-path-policy", "no-first-party-mjs", "no-math-random", "roadmap-links", "bundle-budget", "commit-message", "tech-debt", "supported-agent-clis", "hook-surface", "harness-surfaces", "weakness-mining", "harness-overlay-evidence", "host-substitution-risk", "ai-contracts", "atomic-state-writes", "no-relative-package-scripts", "toolchain-isolation", "open-source-licenses", "secrets-policy", "no-dev-vars", "github-actions-secrets", "secret-provider-quarantine", "security-quality-regressions", "secrets-config", "consumer-agent-kit-dependency", "ci-test-perf", "test-scan-perf", "session-memory-hardcut", "worktree-main-ownership", "typescript-version", "ci-guardrails-wiring", "sast", "changeset-required"];
|
|
19
19
|
export declare const inputSchema: z.ZodObject<{
|
|
20
20
|
kind: z.ZodEnum<{
|
|
21
21
|
"absolute-path-policy": "absolute-path-policy";
|
|
@@ -29,6 +29,7 @@ export declare const inputSchema: z.ZodObject<{
|
|
|
29
29
|
"blueprint-trust": "blueprint-trust";
|
|
30
30
|
"bundle-budget": "bundle-budget";
|
|
31
31
|
"catalog-drift": "catalog-drift";
|
|
32
|
+
"changeset-required": "changeset-required";
|
|
32
33
|
"ci-guardrails-wiring": "ci-guardrails-wiring";
|
|
33
34
|
"ci-test-perf": "ci-test-perf";
|
|
34
35
|
"cloudflare-deploy-contract": "cloudflare-deploy-contract";
|
|
@@ -569,6 +569,18 @@ export async function dispatchAudit(input) {
|
|
|
569
569
|
details: auditResult,
|
|
570
570
|
};
|
|
571
571
|
}
|
|
572
|
+
case "changeset-required": {
|
|
573
|
+
const { auditChangesetRequired } = await import("#audit/changeset-required");
|
|
574
|
+
const auditResult = auditChangesetRequired(input.cwd ?? input.directory ?? process.cwd(), {
|
|
575
|
+
baseRef: input.baseRef,
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
passed: auditResult.ok,
|
|
579
|
+
summary: summarizeRepoAudit(kind, auditResult),
|
|
580
|
+
kind,
|
|
581
|
+
details: auditResult,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
572
584
|
default: {
|
|
573
585
|
// Exhaustiveness check — z.enum should make this unreachable.
|
|
574
586
|
const _exhaustive = kind;
|
|
@@ -25,6 +25,7 @@ declare const inputSchema: z.ZodObject<{
|
|
|
25
25
|
"blueprint-trust": "blueprint-trust";
|
|
26
26
|
"bundle-budget": "bundle-budget";
|
|
27
27
|
"catalog-drift": "catalog-drift";
|
|
28
|
+
"changeset-required": "changeset-required";
|
|
28
29
|
"ci-guardrails-wiring": "ci-guardrails-wiring";
|
|
29
30
|
"ci-test-perf": "ci-test-perf";
|
|
30
31
|
"cloudflare-deploy-contract": "cloudflare-deploy-contract";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolDescriptor, ToolHandlerExtra, ToolHandlerResult } from "#mcp/auto-discover";
|
|
2
2
|
export interface SessionFetchAndIndexDeps {
|
|
3
|
-
readonly fetchImpl?:
|
|
3
|
+
readonly fetchImpl?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
4
4
|
}
|
|
5
5
|
export declare function handleSessionFetchAndIndex(raw: unknown, extra?: ToolHandlerExtra, deps?: SessionFetchAndIndexDeps): Promise<ToolHandlerResult>;
|
|
6
6
|
declare const tool: ToolDescriptor;
|
package/dist/esm/package.json
CHANGED
|
@@ -89,6 +89,8 @@
|
|
|
89
89
|
"#compiler/*": "./compiler/*.js",
|
|
90
90
|
"#review/*.js": "./review/*.js",
|
|
91
91
|
"#review/*": "./review/*.js",
|
|
92
|
+
"#rust/*.js": "./rust/*.js",
|
|
93
|
+
"#rust/*": "./rust/*.js",
|
|
92
94
|
"#telemetry/*.js": "./telemetry/*.js",
|
|
93
95
|
"#telemetry/*": "./telemetry/*.js",
|
|
94
96
|
"#codex/app-server/client": "./codex/app-server/client.js",
|
|
@@ -44,8 +44,14 @@ export interface PlatformResponse<T = unknown> {
|
|
|
44
44
|
readonly status: number;
|
|
45
45
|
readonly data: T;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Injectable fetch call-signature used by platform transport and tests.
|
|
49
|
+
* Narrower than `typeof fetch` (which requires Bun/DOM statics like
|
|
50
|
+
* `preconnect`) so vitest mocks remain assignable without casts.
|
|
51
|
+
*/
|
|
52
|
+
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
47
53
|
export interface PlatformClientOptions {
|
|
48
|
-
readonly fetchFn?:
|
|
54
|
+
readonly fetchFn?: FetchLike;
|
|
49
55
|
readonly maxAttempts?: number;
|
|
50
56
|
readonly baseRetryDelayMs?: number;
|
|
51
57
|
readonly maxRetryDelayMs?: number;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type AuthenticatedPlatformContractSnapshot, type PlatformContractAuthority, type PlatformContractAuthorityProbeResult, type PlatformContractSourceAdapter } from "./contract-provenance.js";
|
|
2
|
+
import { type FetchLike } from "./client.js";
|
|
2
3
|
export interface GitHubContractSourceAdapterOptions {
|
|
3
4
|
readonly token: string;
|
|
4
5
|
readonly authority?: string;
|
|
5
6
|
readonly apiUrl?: string;
|
|
6
|
-
readonly fetchFn?:
|
|
7
|
+
readonly fetchFn?: FetchLike;
|
|
7
8
|
readonly sleep?: (delayMs: number) => Promise<void>;
|
|
8
9
|
}
|
|
9
10
|
export declare class GitHubContractSourceAdapter implements PlatformContractSourceAdapter {
|
|
@@ -31,13 +31,33 @@ const SESSION_SKIP_PREFIX = /^Session-skip:/im;
|
|
|
31
31
|
function trim(value) {
|
|
32
32
|
return value?.trim() ?? "";
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Finds the value of a "<label>: <value>" list item at the head of a line,
|
|
36
|
+
* tolerating a leading "- " bullet and markdown emphasis wrapping the label
|
|
37
|
+
* (`**bold**`, `__bold__`, `*italic*`, `_italic_`) on either side of the
|
|
38
|
+
* colon — e.g. both "- Execution model(s): x" and
|
|
39
|
+
* "- **Execution model(s):** x" match. Still anchored to the start of the
|
|
40
|
+
* line (after stripping the bullet/emphasis decoration): a mention of the
|
|
41
|
+
* label mid-sentence never matches. Mirrors the CI `require_field` awk
|
|
42
|
+
* matcher in `.github/workflows/ci.agent-kit.yml` — keep the two in lockstep.
|
|
43
|
+
*/
|
|
34
44
|
function fieldValue(body, label) {
|
|
35
45
|
const expected = label.toLowerCase();
|
|
36
46
|
for (const line of body.split(/\r?\n/u)) {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
const colonIndex = line.indexOf(":");
|
|
48
|
+
if (colonIndex === -1)
|
|
49
|
+
continue;
|
|
50
|
+
const before = line
|
|
51
|
+
.slice(0, colonIndex)
|
|
52
|
+
.replace(/^\s+/u, "")
|
|
53
|
+
.replace(/^-\s+/u, "")
|
|
54
|
+
.replace(/[*_]+/gu, "");
|
|
55
|
+
if (before.toLowerCase() !== expected)
|
|
56
|
+
continue;
|
|
57
|
+
return line
|
|
58
|
+
.slice(colonIndex + 1)
|
|
59
|
+
.replace(/^[*_]+/u, "")
|
|
60
|
+
.trim();
|
|
41
61
|
}
|
|
42
62
|
return undefined;
|
|
43
63
|
}
|
|
@@ -9,7 +9,7 @@ import { createHash } from "node:crypto";
|
|
|
9
9
|
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { evaluateReviewEvents, parseReviewEventLog } from "./events.js";
|
|
12
|
-
import { createDeliverySubjectAtRef, createLegacyPlanSubjectAtRef, createPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
|
|
12
|
+
import { createDeliverySubjectAtRef, createLegacyPlanSubjectAtRef, createPlanSubjectAtRef, createPreTagsStripPlanSubjectAtRef, resolveReviewCommit, } from "./subject.js";
|
|
13
13
|
const GIT_TIMEOUT_MS = 5_000;
|
|
14
14
|
const GIT_MAX_BUFFER_BYTES = 128 * 1024 * 1024;
|
|
15
15
|
const REGULAR_MODE_PATTERN = /^100(?:644|755)$/u;
|
|
@@ -138,18 +138,23 @@ export function readReviewAuthorityAtRef(cwd, ref, stableSlug, purpose) {
|
|
|
138
138
|
subjectDigest: subject.digest,
|
|
139
139
|
}, compromisedIds);
|
|
140
140
|
if (purpose === "plan" && evaluation.approvals.length === 0) {
|
|
141
|
-
// Digest-compatibility
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
// Digest-compatibility fallbacks (newest-first after current):
|
|
142
|
+
// 1) pre-tags-strip: lifecycle strip without advisory `tags` (approvals recorded
|
|
143
|
+
// after the stripped-key extension but before the tags strip)
|
|
144
|
+
// 2) legacy: status + completed_at only (pre-extension)
|
|
145
|
+
// New events always record the current digest (with tags strip).
|
|
146
|
+
for (const make of [createPreTagsStripPlanSubjectAtRef, createLegacyPlanSubjectAtRef]) {
|
|
147
|
+
const alt = make(cwd, commit, stableSlug, overview.path);
|
|
148
|
+
if (alt.digest === subject.digest)
|
|
149
|
+
continue;
|
|
147
150
|
evaluation = evaluateReviewEvents(events, {
|
|
148
151
|
purpose,
|
|
149
152
|
blueprintSlug: stableSlug,
|
|
150
|
-
subjectScheme:
|
|
151
|
-
subjectDigest:
|
|
153
|
+
subjectScheme: alt.scheme,
|
|
154
|
+
subjectDigest: alt.digest,
|
|
152
155
|
}, compromisedIds);
|
|
156
|
+
if (evaluation.approvals.length > 0)
|
|
157
|
+
break;
|
|
153
158
|
}
|
|
154
159
|
}
|
|
155
160
|
return {
|
|
@@ -45,6 +45,17 @@ export declare function recordReviewAvailability(projectRoot: string, event: Rev
|
|
|
45
45
|
export declare function recordReviewAvailabilityWithLock(projectRoot: string, event: ReviewAvailabilityEvent): Promise<ReviewAvailabilityState>;
|
|
46
46
|
export declare function isProviderSuppressed(state: ReviewAvailabilityState, provider: ReviewProvider): boolean;
|
|
47
47
|
export declare function suppressedReviewers(state: ReviewAvailabilityState): Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Backstop age for suppressions recorded without a parseable retry window.
|
|
50
|
+
*
|
|
51
|
+
* Without this, a provider/account failure that never set `retryAfterMs`
|
|
52
|
+
* (unparsable limit text, incomplete event payload) stays suppressed forever
|
|
53
|
+
* and permanently removes that surface from rotation. 24h is long enough that
|
|
54
|
+
* a real multi-hour/day window still wins when present, and short enough that
|
|
55
|
+
* a sticky unknown-limit entry self-heals on the next calendar day instead of
|
|
56
|
+
* requiring a manual availability reset.
|
|
57
|
+
*/
|
|
58
|
+
export declare const MAX_SUPPRESSION_AGE_MS: number;
|
|
48
59
|
export declare function pruneExpiredAvailabilitySuppressions(state: ReviewAvailabilityState, nowMs?: number): {
|
|
49
60
|
state: ReviewAvailabilityState;
|
|
50
61
|
pruned: string[];
|
|
@@ -247,21 +247,36 @@ export function suppressedReviewers(state) {
|
|
|
247
247
|
.filter((record) => record.suppressed)
|
|
248
248
|
.map((record) => record.canonicalId));
|
|
249
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Backstop age for suppressions recorded without a parseable retry window.
|
|
252
|
+
*
|
|
253
|
+
* Without this, a provider/account failure that never set `retryAfterMs`
|
|
254
|
+
* (unparsable limit text, incomplete event payload) stays suppressed forever
|
|
255
|
+
* and permanently removes that surface from rotation. 24h is long enough that
|
|
256
|
+
* a real multi-hour/day window still wins when present, and short enough that
|
|
257
|
+
* a sticky unknown-limit entry self-heals on the next calendar day instead of
|
|
258
|
+
* requiring a manual availability reset.
|
|
259
|
+
*/
|
|
260
|
+
export const MAX_SUPPRESSION_AGE_MS = 24 * 60 * 60 * 1000;
|
|
250
261
|
/**
|
|
251
262
|
* Shared expiry predicate for a suppressed availability record: applies to
|
|
252
263
|
* both the reviewer/provider store and the opencode-go account store below.
|
|
253
264
|
* A record clears when its terminal classification is no longer a failure
|
|
254
|
-
* classification, or when its retry window has
|
|
265
|
+
* classification, or when its retry window (or the max-age backstop) has
|
|
266
|
+
* elapsed.
|
|
255
267
|
*/
|
|
256
268
|
function suppressionExpired(record, nowMs) {
|
|
257
269
|
if (!record.suppressed)
|
|
258
270
|
return false;
|
|
259
271
|
if (!suppressedRecordStillApplies(record))
|
|
260
272
|
return true;
|
|
261
|
-
if (record.
|
|
273
|
+
if (record.lastFailureAt === undefined)
|
|
262
274
|
return false;
|
|
263
275
|
const failureAt = Date.parse(record.lastFailureAt);
|
|
264
|
-
|
|
276
|
+
if (!Number.isFinite(failureAt))
|
|
277
|
+
return false;
|
|
278
|
+
const windowMs = record.retryAfterMs === undefined ? MAX_SUPPRESSION_AGE_MS : record.retryAfterMs;
|
|
279
|
+
return failureAt + windowMs <= nowMs;
|
|
265
280
|
}
|
|
266
281
|
function pruneRecordMap(records, nowMs, pruned) {
|
|
267
282
|
return Object.fromEntries(Object.entries(records).map(([key, record]) => {
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import type { ReviewRunInput, ReviewStreamAdapter } from "./types.js";
|
|
1
|
+
import type { ReviewAdapterEvent, ReviewRunInput, ReviewStreamAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Classify an OpenCode Go usage-limit message and derive its real reset window.
|
|
4
|
+
*
|
|
5
|
+
* Exported because the `wp opencode` launch-time health probe
|
|
6
|
+
* (`#cli/commands/opencode-probe.js`) runs the same provider text through the
|
|
7
|
+
* same classifier. A second copy of this wording/duration math would drift the
|
|
8
|
+
* moment the provider changes a phrase, and the two consumers would then
|
|
9
|
+
* disagree about whether an account is capped.
|
|
10
|
+
*/
|
|
11
|
+
export declare function opencodeUsageLimit(message: string): ReviewAdapterEvent | null;
|
|
2
12
|
export declare const claudeReviewAdapter: ReviewStreamAdapter;
|
|
3
13
|
export declare const codexReviewAdapter: ReviewStreamAdapter;
|
|
4
14
|
export declare const opencodeReviewAdapter: ReviewStreamAdapter;
|
|
@@ -92,7 +92,16 @@ function parseResetDurationMs(message) {
|
|
|
92
92
|
}
|
|
93
93
|
return duration > 0 ? duration : undefined;
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Classify an OpenCode Go usage-limit message and derive its real reset window.
|
|
97
|
+
*
|
|
98
|
+
* Exported because the `wp opencode` launch-time health probe
|
|
99
|
+
* (`#cli/commands/opencode-probe.js`) runs the same provider text through the
|
|
100
|
+
* same classifier. A second copy of this wording/duration math would drift the
|
|
101
|
+
* moment the provider changes a phrase, and the two consumers would then
|
|
102
|
+
* disagree about whether an account is capped.
|
|
103
|
+
*/
|
|
104
|
+
export function opencodeUsageLimit(message) {
|
|
96
105
|
if (!/\busage limit reached\b/iu.test(message))
|
|
97
106
|
return null;
|
|
98
107
|
const limitWindow = /\bweekly usage limit reached\b/iu.test(message)
|
|
@@ -14,4 +14,4 @@ export interface ReviewArtifactWriter {
|
|
|
14
14
|
export declare function reviewRunStatePath(root: string, runId: string): string;
|
|
15
15
|
export type ReviewRunStateCleanupResult = "missing" | "not-running" | "removed" | "refused";
|
|
16
16
|
export declare function removeOwnedRunningReviewState(root: string, runId: string, reservationToken: string): ReviewRunStateCleanupResult;
|
|
17
|
-
export declare function createReviewArtifactWriter(root: string, runId: string, startedAt: string, reservationToken?:
|
|
17
|
+
export declare function createReviewArtifactWriter(root: string, runId: string, startedAt: string, reservationToken?: string): ReviewArtifactWriter;
|
|
@@ -4,4 +4,10 @@ export interface ReviewTargetCheckout {
|
|
|
4
4
|
readonly changes: () => readonly string[];
|
|
5
5
|
readonly cleanup: () => void;
|
|
6
6
|
}
|
|
7
|
+
declare function statusEntryPath(line: string): string;
|
|
7
8
|
export declare function createReviewTargetCheckout(repositoryRoot: string, targetSha: string): ReviewTargetCheckout;
|
|
9
|
+
/** Test-only surface for porcelain path parsing. */
|
|
10
|
+
export declare const reviewCheckoutInternals: {
|
|
11
|
+
statusEntryPath: typeof statusEntryPath;
|
|
12
|
+
};
|
|
13
|
+
export {};
|