@webpresso/agent-kit 3.3.2 → 3.3.4
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/AGENTS.md.tpl +3 -5
- 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/catalog/agent/skills/verify/SKILL.md +12 -12
- package/catalog/compose/admin-blocks-registry.json +659 -0
- 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 +40 -16
- package/dist/esm/blueprint/core/validation/criteria.js +2 -1
- 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 +148 -40
- 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/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/cli-mcp-parity.js +3 -2
- package/dist/esm/build/package-manifest.js +7 -2
- package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
- package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
- package/dist/esm/cli/commands/audit-core.js +9 -1
- package/dist/esm/cli/commands/audit.js +16 -5
- 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 +29 -48
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +54 -4
- package/dist/esm/cli/commands/init/mcp-spec.js +126 -22
- package/dist/esm/cli/commands/init/merge.js +30 -10
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
- 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/package-manager.js +8 -2
- 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.js +60 -6
- 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 +46 -0
- package/dist/esm/compose/compose.js +250 -0
- package/dist/esm/compose/escape-html.d.ts +5 -0
- package/dist/esm/compose/escape-html.js +12 -0
- package/dist/esm/compose/ops-report-render.d.ts +5 -0
- package/dist/esm/compose/ops-report-render.js +71 -0
- package/dist/esm/compose/ops-report-schema.d.ts +42 -0
- package/dist/esm/compose/ops-report-schema.js +60 -0
- package/dist/esm/compose/preview.d.ts +14 -0
- package/dist/esm/compose/preview.js +32 -0
- package/dist/esm/compose/registry-resolve.d.ts +64 -0
- package/dist/esm/compose/registry-resolve.js +200 -0
- 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 +18 -21
- package/dist/esm/config/oxlint/path-roles.js +53 -0
- package/dist/esm/config/oxlint/query-patterns.js +2 -5
- 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/git/changed-files.d.ts +20 -0
- package/dist/esm/git/changed-files.js +63 -0
- 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 +1 -1
- 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/status/index.js +1 -1
- package/dist/esm/hooks/stop/qa-changed-files.js +6 -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/handlers/validate.js +6 -0
- package/dist/esm/mcp/blueprint/registration.js +1 -1
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +4 -0
- package/dist/esm/mcp/tools/_registry.js +8 -0
- 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/release-progress.d.ts +9 -0
- package/dist/esm/mcp/tools/release-progress.js +110 -0
- package/dist/esm/mcp/tools/worktree.js +65 -3
- package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
- package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-compose.js +96 -0
- package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-preview.js +59 -0
- package/dist/esm/package.json +2 -0
- 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/test/shard-durations.json +0 -1
- 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 +27 -21
- 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
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single owner of the Trust Dossier "Promotion Gates" table row grammar.
|
|
3
|
+
*
|
|
4
|
+
* Two callers must agree on this grammar byte-for-byte:
|
|
5
|
+
*
|
|
6
|
+
* - the lifecycle WRITER `updateGateLastResult` (`./promotion.ts`), which stamps
|
|
7
|
+
* the `Last result` cell during `wp blueprint promote`; and
|
|
8
|
+
* - the plan-subject digest NORMALIZER `neutralizePromotionGateResults`
|
|
9
|
+
* (`#review/subject.js`), which replaces that same cell with a placeholder so
|
|
10
|
+
* the stamp is never hashed into a plan approval's subject.
|
|
11
|
+
*
|
|
12
|
+
* They each used to implement the grammar independently, and diverged on four
|
|
13
|
+
* axes at once — cell count (writer 4 or 5, normalizer 4 only), section scope
|
|
14
|
+
* (writer any table row in the document, normalizer inside `### Promotion Gates`
|
|
15
|
+
* only), trailing pipe (writer `trim().startsWith("|")` alone, normalizer both
|
|
16
|
+
* pipes), and trailing whitespace after the closing pipe (writer sliced the
|
|
17
|
+
* UNTRIMMED line, so it mis-counted a 4-cell row as 5 and appended a spurious
|
|
18
|
+
* empty cell). Every divergence made `digest(doc) !== digest(writer(doc))`,
|
|
19
|
+
* which destroys the plan approval the promotion is gating on. One owner,
|
|
20
|
+
* consumed from both sides, is the only structural fix; anything else lets them
|
|
21
|
+
* drift again.
|
|
22
|
+
*
|
|
23
|
+
* The grammar deliberately matches what `parsePromotionGatesTable` in
|
|
24
|
+
* `./dossier.ts` already enforces on committed documents: a row is the trimmed
|
|
25
|
+
* line starting AND ending with a pipe, cells are split on `|` and trimmed, and
|
|
26
|
+
* the accepted widths are 4 (legacy) and 5 (opt-in `Defer` column). A row this
|
|
27
|
+
* module rejects is a row the dossier parser reports as `malformed table row`,
|
|
28
|
+
* so it can never be a live gate — skipping it is the fail-safe direction.
|
|
29
|
+
*/
|
|
30
|
+
/** Zero-based index of the lifecycle-owned `Last result` cell. */
|
|
31
|
+
export const LAST_RESULT_CELL_INDEX = 3;
|
|
32
|
+
/**
|
|
33
|
+
* Exact end-anchored heading match, mirroring `sectionBlock(markdown, 3, ...)`
|
|
34
|
+
* in `./dossier.ts` — `### Promotion Gates Notes` is NOT the owned section and
|
|
35
|
+
* its contents stay plan substance.
|
|
36
|
+
*/
|
|
37
|
+
const PROMOTION_GATES_HEADING = /^###\s+Promotion Gates\s*$/iu;
|
|
38
|
+
/**
|
|
39
|
+
* The section ends at the next heading of ANY level. `sectionBlock` itself ends
|
|
40
|
+
* at the next heading of level <= 3, so a table placed after an intervening
|
|
41
|
+
* `####` heading would be a gate to the parser but not to this walk. The
|
|
42
|
+
* narrower scope is deliberate: it is the scope every committed plan digest was
|
|
43
|
+
* computed against (measured: zero tracked blueprint documents put gate rows in
|
|
44
|
+
* that gap), and the consequence of the mismatch is fail-closed — the writer
|
|
45
|
+
* leaves such a row unstamped, so `validateBlueprintTrust` rejects the promotion
|
|
46
|
+
* with a non-passing gate instead of silently leaking the stamp into the digest.
|
|
47
|
+
*/
|
|
48
|
+
const ANY_HEADING = /^#{1,6}\s/u;
|
|
49
|
+
const ACCEPTED_ROW_WIDTHS = [4, 5];
|
|
50
|
+
/**
|
|
51
|
+
* Cells of one Promotion Gates table row, trimmed, or null when the line is not
|
|
52
|
+
* a row this grammar owns (no pipes, a single pipe, or an unsupported width).
|
|
53
|
+
*/
|
|
54
|
+
export function parsePromotionGateCells(line) {
|
|
55
|
+
const body = line.trim();
|
|
56
|
+
if (body.length < 2 || !body.startsWith("|") || !body.endsWith("|"))
|
|
57
|
+
return null;
|
|
58
|
+
const cells = body
|
|
59
|
+
.slice(1, -1)
|
|
60
|
+
.split("|")
|
|
61
|
+
.map((cell) => cell.trim());
|
|
62
|
+
if (!ACCEPTED_ROW_WIDTHS.includes(cells.length))
|
|
63
|
+
return null;
|
|
64
|
+
return cells;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Canonical row bytes. Whitespace around a row (leading indent, padding inside
|
|
68
|
+
* cells, whitespace after the closing pipe) is not plan substance, so both
|
|
69
|
+
* callers re-emit this single canonical form. That is what keeps a stamped row
|
|
70
|
+
* and its normalized counterpart byte-identical, and it is exactly the form the
|
|
71
|
+
* normalizer has always emitted for a 4-cell row, so committed plan digests are
|
|
72
|
+
* unaffected.
|
|
73
|
+
*/
|
|
74
|
+
export function formatPromotionGateRow(cells) {
|
|
75
|
+
return `| ${cells.join(" | ")} |`;
|
|
76
|
+
}
|
|
77
|
+
/** Copy `cells` with the lifecycle-owned `Last result` cell set to `value`. */
|
|
78
|
+
export function setLastResultCell(cells, value) {
|
|
79
|
+
const next = [...cells];
|
|
80
|
+
next[LAST_RESULT_CELL_INDEX] = value;
|
|
81
|
+
return next;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Rewrite every Promotion Gates table row (header, separator, and data rows
|
|
85
|
+
* alike) inside the `### Promotion Gates` subsection through `transform`.
|
|
86
|
+
* Returning the received `cells` reference unchanged leaves the line
|
|
87
|
+
* byte-identical; returning a new array re-emits the canonical row. Every line
|
|
88
|
+
* outside the section is always byte-identical.
|
|
89
|
+
*/
|
|
90
|
+
export function mapPromotionGateRows(text, transform) {
|
|
91
|
+
const lines = text.split("\n");
|
|
92
|
+
let inGatesSection = false;
|
|
93
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
94
|
+
const line = lines[index] ?? "";
|
|
95
|
+
if (PROMOTION_GATES_HEADING.test(line)) {
|
|
96
|
+
inGatesSection = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (inGatesSection && ANY_HEADING.test(line)) {
|
|
100
|
+
inGatesSection = false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!inGatesSection)
|
|
104
|
+
continue;
|
|
105
|
+
const cells = parsePromotionGateCells(line);
|
|
106
|
+
if (!cells)
|
|
107
|
+
continue;
|
|
108
|
+
const next = transform(cells);
|
|
109
|
+
if (next !== cells)
|
|
110
|
+
lines[index] = formatPromotionGateRow(next);
|
|
111
|
+
}
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
@@ -19,3 +19,20 @@ export declare function assertPromotionTrustProofCurrent(input: Pick<PromotionTr
|
|
|
19
19
|
export declare function runPromotionCommand(repoRoot: string, command: string, deps?: {
|
|
20
20
|
spawn?: typeof spawnSync;
|
|
21
21
|
}): void;
|
|
22
|
+
/**
|
|
23
|
+
* Exported for the plan-subject digest invariance suite
|
|
24
|
+
* (`src/review/lifecycle-writers.ts`, `src/review/subject-normalize.test.ts`).
|
|
25
|
+
* Those tests MUST call the production writer, never a hand-written mirror: a
|
|
26
|
+
* mirror can drift with the very bug under test and stay self-consistent, which
|
|
27
|
+
* is exactly how the 5-cell Promotion Gates leak survived the suite.
|
|
28
|
+
*/
|
|
29
|
+
export declare function upsertReadinessValue(markdown: string, key: string, value: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Stamp the `Last result` cell of the gate named `gate`.
|
|
32
|
+
*
|
|
33
|
+
* Exported for the same reason as `upsertReadinessValue` above. The row grammar
|
|
34
|
+
* and the section scope both come from `./gate-row.js`, which the plan-subject
|
|
35
|
+
* digest normalizer consumes too — hand-rolling either here is what let the two
|
|
36
|
+
* sides diverge on four axes and destroy plan approvals on promotion.
|
|
37
|
+
*/
|
|
38
|
+
export declare function updateGateLastResult(markdown: string, gate: string, result: string): string;
|
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { validateBlueprintTrust } from "./validator.js";
|
|
5
5
|
import { parseTrustDossier } from "./dossier.js";
|
|
6
|
+
import { mapPromotionGateRows, setLastResultCell } from "./gate-row.js";
|
|
6
7
|
import { parseAllowedWpCommand, wpTestFileTargets } from "./gates.js";
|
|
7
8
|
import { ALLOWED_WP_COMMAND_TIMEOUT_MS, executeAllowedWpCommand, tailBounded, } from "./command-runner.js";
|
|
8
9
|
export { parseAllowedWpCommand };
|
|
@@ -138,7 +139,14 @@ function formatPromotionGateFailure(result) {
|
|
|
138
139
|
details.push(`log=${result.logPath}`);
|
|
139
140
|
return `Promotion gate failed (${result.command}): ${details.join("; ")}`;
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Exported for the plan-subject digest invariance suite
|
|
144
|
+
* (`src/review/lifecycle-writers.ts`, `src/review/subject-normalize.test.ts`).
|
|
145
|
+
* Those tests MUST call the production writer, never a hand-written mirror: a
|
|
146
|
+
* mirror can drift with the very bug under test and stay self-consistent, which
|
|
147
|
+
* is exactly how the 5-cell Promotion Gates leak survived the suite.
|
|
148
|
+
*/
|
|
149
|
+
export function upsertReadinessValue(markdown, key, value) {
|
|
142
150
|
const re = new RegExp(`^- ${key}: .*$`, "mu");
|
|
143
151
|
if (re.test(markdown))
|
|
144
152
|
return markdown.replace(re, `- ${key}: ${value}`);
|
|
@@ -152,22 +160,14 @@ function upsertReadinessValue(markdown, key, value) {
|
|
|
152
160
|
lines.splice(insertIndex, 0, `- ${key}: ${value}`);
|
|
153
161
|
return lines.join("\n");
|
|
154
162
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (cells[0] === gate && cells.length === 4) {
|
|
166
|
-
lines[i] = `| ${cells[0]} | ${cells[1]} | ${cells[2]} | ${result} |`;
|
|
167
|
-
}
|
|
168
|
-
else if (cells[0] === gate && cells.length === 5) {
|
|
169
|
-
lines[i] = `| ${cells[0]} | ${cells[1]} | ${cells[2]} | ${result} | ${cells[4]} |`;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return lines.join("\n");
|
|
163
|
+
/**
|
|
164
|
+
* Stamp the `Last result` cell of the gate named `gate`.
|
|
165
|
+
*
|
|
166
|
+
* Exported for the same reason as `upsertReadinessValue` above. The row grammar
|
|
167
|
+
* and the section scope both come from `./gate-row.js`, which the plan-subject
|
|
168
|
+
* digest normalizer consumes too — hand-rolling either here is what let the two
|
|
169
|
+
* sides diverge on four axes and destroy plan approvals on promotion.
|
|
170
|
+
*/
|
|
171
|
+
export function updateGateLastResult(markdown, gate, result) {
|
|
172
|
+
return mapPromotionGateRows(markdown, (cells) => cells[0] === gate ? setLastResultCell(cells, result) : cells);
|
|
173
173
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy `source` to `destination` via copy-to-temp + atomic rename.
|
|
3
|
+
*
|
|
4
|
+
* A plain `copyFileSync(source, destination)` opens `destination` directly
|
|
5
|
+
* for writing. If another process currently has `destination` open for
|
|
6
|
+
* execution (a running compiled binary, a loaded native addon), POSIX
|
|
7
|
+
* returns `ETXTBSY` ("text file is busy"). This is not a flake: it is a
|
|
8
|
+
* deterministic race whenever a staging step runs concurrently with a
|
|
9
|
+
* process that execs the file being replaced (for example a vitest shard
|
|
10
|
+
* running the freshly staged `wp` runtime binary while another shard
|
|
11
|
+
* restages it).
|
|
12
|
+
*
|
|
13
|
+
* `rename(2)` is atomic within a filesystem and is legal even onto a file
|
|
14
|
+
* another process currently has open for execution: the running process
|
|
15
|
+
* keeps its already-open inode (and keeps running unaffected), while any
|
|
16
|
+
* new open/exec of `destination` resolves to the new file. Copying to a
|
|
17
|
+
* unique temp file beside `destination` (same directory) keeps the rename
|
|
18
|
+
* on the same filesystem so it stays atomic, then renaming over the
|
|
19
|
+
* destination replaces it without ever truncating a file that may be
|
|
20
|
+
* actively executing. `copyFileSync` preserves the source file's mode on
|
|
21
|
+
* the temp file, so there is no non-executable window between the rename
|
|
22
|
+
* and any later `chmodSync` a caller performs.
|
|
23
|
+
*
|
|
24
|
+
* On any failure (source read, temp write, or rename) the temp file is
|
|
25
|
+
* removed via `rmSync(..., { force: true })` (which tolerates the file not
|
|
26
|
+
* existing) before the error is rethrown.
|
|
27
|
+
*
|
|
28
|
+
* This guarantee is POSIX-only: on Windows, `renameSync` onto a file that
|
|
29
|
+
* is currently open for execution fails with `EPERM` for the same reason
|
|
30
|
+
* `copyFileSync` would, so this helper does not remove the race there.
|
|
31
|
+
*/
|
|
32
|
+
export declare function copyFileAtomic(source: string, destination: string): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { copyFileSync, renameSync, rmSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* Copy `source` to `destination` via copy-to-temp + atomic rename.
|
|
5
|
+
*
|
|
6
|
+
* A plain `copyFileSync(source, destination)` opens `destination` directly
|
|
7
|
+
* for writing. If another process currently has `destination` open for
|
|
8
|
+
* execution (a running compiled binary, a loaded native addon), POSIX
|
|
9
|
+
* returns `ETXTBSY` ("text file is busy"). This is not a flake: it is a
|
|
10
|
+
* deterministic race whenever a staging step runs concurrently with a
|
|
11
|
+
* process that execs the file being replaced (for example a vitest shard
|
|
12
|
+
* running the freshly staged `wp` runtime binary while another shard
|
|
13
|
+
* restages it).
|
|
14
|
+
*
|
|
15
|
+
* `rename(2)` is atomic within a filesystem and is legal even onto a file
|
|
16
|
+
* another process currently has open for execution: the running process
|
|
17
|
+
* keeps its already-open inode (and keeps running unaffected), while any
|
|
18
|
+
* new open/exec of `destination` resolves to the new file. Copying to a
|
|
19
|
+
* unique temp file beside `destination` (same directory) keeps the rename
|
|
20
|
+
* on the same filesystem so it stays atomic, then renaming over the
|
|
21
|
+
* destination replaces it without ever truncating a file that may be
|
|
22
|
+
* actively executing. `copyFileSync` preserves the source file's mode on
|
|
23
|
+
* the temp file, so there is no non-executable window between the rename
|
|
24
|
+
* and any later `chmodSync` a caller performs.
|
|
25
|
+
*
|
|
26
|
+
* On any failure (source read, temp write, or rename) the temp file is
|
|
27
|
+
* removed via `rmSync(..., { force: true })` (which tolerates the file not
|
|
28
|
+
* existing) before the error is rethrown.
|
|
29
|
+
*
|
|
30
|
+
* This guarantee is POSIX-only: on Windows, `renameSync` onto a file that
|
|
31
|
+
* is currently open for execution fails with `EPERM` for the same reason
|
|
32
|
+
* `copyFileSync` would, so this helper does not remove the race there.
|
|
33
|
+
*/
|
|
34
|
+
export function copyFileAtomic(source, destination) {
|
|
35
|
+
const tmpDestination = `${destination}.tmp-${process.pid}-${randomUUID()}`;
|
|
36
|
+
try {
|
|
37
|
+
copyFileSync(source, tmpDestination);
|
|
38
|
+
renameSync(tmpDestination, destination);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
rmSync(tmpDestination, { force: true });
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -52,8 +52,9 @@ export const CLI_MCP_PARITY_CLASSIFICATION = {
|
|
|
52
52
|
},
|
|
53
53
|
"release-progress": {
|
|
54
54
|
command: "release-progress",
|
|
55
|
-
classification: "
|
|
56
|
-
|
|
55
|
+
classification: "existing-mcp",
|
|
56
|
+
tools: ["wp_release_progress"],
|
|
57
|
+
reason: "Release workflow phase reporter (detect/native-matrix/publish) is available as wp_release_progress MCP and CLI",
|
|
57
58
|
},
|
|
58
59
|
cleanup: {
|
|
59
60
|
command: "cleanup",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
|
-
import { assertBuiltBlueprintMigrationSqlAssets } from "./blueprint-migration-assets.js";
|
|
5
|
-
import { assertBuiltSastRuleAssets } from "./sast-rule-assets.js";
|
|
4
|
+
import { assertBuiltBlueprintMigrationSqlAssets, syncBlueprintMigrationSqlAssets, } from "./blueprint-migration-assets.js";
|
|
5
|
+
import { assertBuiltSastRuleAssets, syncSastRuleAssets } from "./sast-rule-assets.js";
|
|
6
6
|
import { SESSION_MEMORY_NATIVE_TARGETS } from "#session-memory/native-targets.js";
|
|
7
7
|
import { RUNTIME_TARGETS } from "./runtime-targets.js";
|
|
8
8
|
const DEPENDENCY_SECTIONS = [
|
|
@@ -304,6 +304,11 @@ export function preparePackedManifest(rootDir, options = {}) {
|
|
|
304
304
|
// out-of-org consumers cannot resolve.
|
|
305
305
|
const packedManifest = createPackedManifest(manifest, readWorkspaceCatalogs(workspacePath), options);
|
|
306
306
|
if (options.assertBlueprintMigrationAssets ?? true) {
|
|
307
|
+
// Sync authoring sources into dist before assert so pack/prepack paths
|
|
308
|
+
// (version dry-run, npm pack) do not require a full `pnpm run build` first.
|
|
309
|
+
// Assert still fails closed on missing source or content drift.
|
|
310
|
+
syncBlueprintMigrationSqlAssets(rootDir);
|
|
311
|
+
syncSastRuleAssets(rootDir);
|
|
307
312
|
assertBuiltBlueprintMigrationSqlAssets(rootDir);
|
|
308
313
|
assertBuiltSastRuleAssets(rootDir);
|
|
309
314
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
export declare const AGENT_CONFIG_OXLINT_DIST: string;
|
|
3
|
+
export declare const AGENT_KIT_OXLINT_DIST: string;
|
|
4
|
+
/** Plugin basenames that ship as sibling .js next to oxlintrc.json (not path-roles). */
|
|
5
|
+
export declare const VENDOR_PLUGIN_BASENAMES: readonly ["code-safety", "foundation-purity", "graphql-conventions", "import-hygiene", "monorepo-paths", "query-patterns", "testing-quality", "tier-boundaries"];
|
|
6
|
+
export declare function vendorOxlintPlugins(sourceDir?: string, destDir?: string): {
|
|
7
|
+
copied: string[];
|
|
8
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Vendor compiled oxlint policy plugins from agent-config into agent-kit dist.
|
|
4
|
+
*
|
|
5
|
+
* Consumer SSOT remains @webpresso/agent-config/oxlint/*. agent-kit's inject
|
|
6
|
+
* oxlintrc uses sibling-relative jsPlugins; we copy the built plugin files so
|
|
7
|
+
* the published kit has zero runtime dependency on agent-config (no re-export
|
|
8
|
+
* stubs, no pack chicken-egg).
|
|
9
|
+
*/
|
|
10
|
+
import { cpSync, existsSync, mkdirSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
export const AGENT_CONFIG_OXLINT_DIST = join(process.cwd(), "packages/agent-config/dist/esm/oxlint");
|
|
13
|
+
export const AGENT_KIT_OXLINT_DIST = join(process.cwd(), "dist/esm/config/oxlint");
|
|
14
|
+
/** Plugin basenames that ship as sibling .js next to oxlintrc.json (not path-roles). */
|
|
15
|
+
export const VENDOR_PLUGIN_BASENAMES = [
|
|
16
|
+
"code-safety",
|
|
17
|
+
"foundation-purity",
|
|
18
|
+
"graphql-conventions",
|
|
19
|
+
"import-hygiene",
|
|
20
|
+
"monorepo-paths",
|
|
21
|
+
"query-patterns",
|
|
22
|
+
"testing-quality",
|
|
23
|
+
"tier-boundaries",
|
|
24
|
+
];
|
|
25
|
+
export function vendorOxlintPlugins(sourceDir = AGENT_CONFIG_OXLINT_DIST, destDir = AGENT_KIT_OXLINT_DIST) {
|
|
26
|
+
if (!existsSync(sourceDir)) {
|
|
27
|
+
throw new Error(`vendor-oxlint-plugins: missing agent-config oxlint dist at ${sourceDir}. Build @webpresso/agent-config first.`);
|
|
28
|
+
}
|
|
29
|
+
mkdirSync(destDir, { recursive: true });
|
|
30
|
+
const copied = [];
|
|
31
|
+
for (const name of VENDOR_PLUGIN_BASENAMES) {
|
|
32
|
+
const src = join(sourceDir, `${name}.js`);
|
|
33
|
+
if (!existsSync(src)) {
|
|
34
|
+
throw new Error(`vendor-oxlint-plugins: missing compiled plugin ${src}`);
|
|
35
|
+
}
|
|
36
|
+
const dest = join(destDir, `${name}.js`);
|
|
37
|
+
cpSync(src, dest);
|
|
38
|
+
copied.push(name);
|
|
39
|
+
// path-roles is imported by several plugins as a sibling — vendor it too.
|
|
40
|
+
}
|
|
41
|
+
// Always vendor path-roles (shared classifier used via relative imports inside plugins).
|
|
42
|
+
const pathRoles = join(sourceDir, "path-roles.js");
|
|
43
|
+
if (!existsSync(pathRoles)) {
|
|
44
|
+
throw new Error(`vendor-oxlint-plugins: missing ${pathRoles}`);
|
|
45
|
+
}
|
|
46
|
+
cpSync(pathRoles, join(destDir, "path-roles.js"));
|
|
47
|
+
copied.push("path-roles");
|
|
48
|
+
return { copied };
|
|
49
|
+
}
|
|
50
|
+
if (import.meta.main) {
|
|
51
|
+
const { copied } = vendorOxlintPlugins();
|
|
52
|
+
process.stdout.write(`vendored oxlint plugins: ${copied.join(", ")}\n`);
|
|
53
|
+
}
|
|
@@ -79,7 +79,15 @@ export async function runAuditDispatch(auditKind, targets, options, deps) {
|
|
|
79
79
|
case "blueprint-pr-coverage": {
|
|
80
80
|
const { auditBlueprintPrCoverage } = await import("#audit/blueprint-pr-coverage");
|
|
81
81
|
const root = options.root ?? target ?? deps.root;
|
|
82
|
-
const
|
|
82
|
+
const explicitBaseRef = options.baseRef ?? options.base;
|
|
83
|
+
// Same pinned-base hazard as the branch-scoped audits: CI's
|
|
84
|
+
// `github.event.pull_request.base.sha` stops being the fork point once the
|
|
85
|
+
// branch is rebased, and this audit diffs `<baseRef>...HEAD`. Narrow it to
|
|
86
|
+
// the real fork point so coverage is judged on this PR's own files.
|
|
87
|
+
const { resolveBranchDiffBase } = await import("#git/changed-files");
|
|
88
|
+
const baseRef = explicitBaseRef
|
|
89
|
+
? resolveBranchDiffBase(root, explicitBaseRef)
|
|
90
|
+
: explicitBaseRef;
|
|
83
91
|
const result = auditBlueprintPrCoverage(root, { baseRef, headRef: options.headRef });
|
|
84
92
|
return { kind: "repo-result", name: "blueprint-pr-coverage", result };
|
|
85
93
|
}
|
|
@@ -9,7 +9,7 @@ import { STRYKER_COMMAND } from "#audit/run-stryker.js";
|
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { addAffectedOptions, resolveAffectedTargets } from "#git/affected";
|
|
12
|
-
import { defaultBranchBaseRef, getBranchRemovedFiles, getStagedRemovedFiles, } from "#git/changed-files";
|
|
12
|
+
import { defaultBranchBaseRef, getBranchRemovedFiles, getStagedRemovedFiles, resolveBranchDiffBase, } from "#git/changed-files";
|
|
13
13
|
import { runAuditDispatch } from "#cli/commands/audit-core.js";
|
|
14
14
|
import { createCliLogSink } from "#cli/commands/quality-log-store.js";
|
|
15
15
|
import { emitCliCommandOutput, runLoggedChildCommand } from "#cli/commands/quality-runner.js";
|
|
@@ -284,6 +284,17 @@ export function registerAuditCommand(cli) {
|
|
|
284
284
|
let dispatchOptions = options;
|
|
285
285
|
let auditScope = "full";
|
|
286
286
|
let changeRemovesFile = false;
|
|
287
|
+
// A caller-supplied `--base` is a PINNED sha (CI passes
|
|
288
|
+
// `github.event.pull_request.base.sha`, which does not advance with the
|
|
289
|
+
// base branch). After a rebase it stops being the fork point, so scoping
|
|
290
|
+
// off it silently absorbs everything merged into the base branch since
|
|
291
|
+
// the PR opened. Narrow it to the real fork point once, and use that one
|
|
292
|
+
// value everywhere the branch scope is derived so the affected file set,
|
|
293
|
+
// the removal probe, and the audit's own base-vs-head range agree.
|
|
294
|
+
const explicitBaseRef = options.baseRef ?? options.base;
|
|
295
|
+
const branchBaseRef = branch && explicitBaseRef
|
|
296
|
+
? resolveBranchDiffBase(auditRoot, explicitBaseRef)
|
|
297
|
+
: explicitBaseRef;
|
|
287
298
|
if (affected || branch) {
|
|
288
299
|
if (kind &&
|
|
289
300
|
AUDIT_KINDS.includes(kind) &&
|
|
@@ -309,7 +320,7 @@ export function registerAuditCommand(cli) {
|
|
|
309
320
|
cwd: auditRoot,
|
|
310
321
|
affected,
|
|
311
322
|
branch,
|
|
312
|
-
baseRef:
|
|
323
|
+
baseRef: branchBaseRef,
|
|
313
324
|
explicitTargets: [],
|
|
314
325
|
policy: "fail-closed",
|
|
315
326
|
mapChangedFiles: (files) => files,
|
|
@@ -360,7 +371,7 @@ export function registerAuditCommand(cli) {
|
|
|
360
371
|
changeRemovesFile = affected
|
|
361
372
|
? affectedChangeRemovesFile(auditRoot, {
|
|
362
373
|
branch,
|
|
363
|
-
baseRef:
|
|
374
|
+
baseRef: branchBaseRef,
|
|
364
375
|
})
|
|
365
376
|
: false;
|
|
366
377
|
if (resolution.kind === "empty" && !changeRemovesFile) {
|
|
@@ -392,7 +403,7 @@ export function registerAuditCommand(cli) {
|
|
|
392
403
|
changedOnly: true,
|
|
393
404
|
affectedFiles: [],
|
|
394
405
|
root: options.root ?? resolution.cwd,
|
|
395
|
-
baseRef:
|
|
406
|
+
baseRef: branchBaseRef,
|
|
396
407
|
};
|
|
397
408
|
}
|
|
398
409
|
if (resolution.kind === "scoped") {
|
|
@@ -404,7 +415,7 @@ export function registerAuditCommand(cli) {
|
|
|
404
415
|
changedOnly: true,
|
|
405
416
|
affectedFiles: [...resolution.targets],
|
|
406
417
|
root: options.root ?? resolution.cwd,
|
|
407
|
-
baseRef:
|
|
418
|
+
baseRef: branchBaseRef,
|
|
408
419
|
};
|
|
409
420
|
}
|
|
410
421
|
}
|
|
@@ -85,6 +85,21 @@ export interface PromoteBlueprintResult {
|
|
|
85
85
|
readonly newPath: string;
|
|
86
86
|
readonly message: string;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Update `status:` in YAML frontmatter. Preserves everything else verbatim.
|
|
90
|
+
*
|
|
91
|
+
* Exported for the plan-subject digest invariance suite
|
|
92
|
+
* (`src/review/lifecycle-writers.ts`, `src/review/subject-normalize.test.ts`),
|
|
93
|
+
* which must exercise the production writer rather than a hand-written mirror.
|
|
94
|
+
*/
|
|
95
|
+
export declare function updateFrontmatterStatus(content: string, newStatus: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* Add or update `completed_at:` in YAML frontmatter.
|
|
98
|
+
* Inserts after the `status:` line if not already present.
|
|
99
|
+
*
|
|
100
|
+
* Exported for the same reason as `updateFrontmatterStatus` above.
|
|
101
|
+
*/
|
|
102
|
+
export declare function upsertCompletedAt(content: string, isoDate: string): string;
|
|
88
103
|
/**
|
|
89
104
|
* Advance a task's status in its blueprint markdown document, then re-ingest.
|
|
90
105
|
*
|
|
@@ -188,15 +188,21 @@ async function forceReIngestBlueprint(cwd, slug, documentPath) {
|
|
|
188
188
|
}
|
|
189
189
|
/**
|
|
190
190
|
* Update `status:` in YAML frontmatter. Preserves everything else verbatim.
|
|
191
|
+
*
|
|
192
|
+
* Exported for the plan-subject digest invariance suite
|
|
193
|
+
* (`src/review/lifecycle-writers.ts`, `src/review/subject-normalize.test.ts`),
|
|
194
|
+
* which must exercise the production writer rather than a hand-written mirror.
|
|
191
195
|
*/
|
|
192
|
-
function updateFrontmatterStatus(content, newStatus) {
|
|
196
|
+
export function updateFrontmatterStatus(content, newStatus) {
|
|
193
197
|
return content.replace(/^(status:\s*)(['"]?)[^'"\r\n]+?(['"]?)(\s*)$/m, `$1${newStatus}$4`);
|
|
194
198
|
}
|
|
195
199
|
/**
|
|
196
200
|
* Add or update `completed_at:` in YAML frontmatter.
|
|
197
201
|
* Inserts after the `status:` line if not already present.
|
|
202
|
+
*
|
|
203
|
+
* Exported for the same reason as `updateFrontmatterStatus` above.
|
|
198
204
|
*/
|
|
199
|
-
function upsertCompletedAt(content, isoDate) {
|
|
205
|
+
export function upsertCompletedAt(content, isoDate) {
|
|
200
206
|
// If already present, update it
|
|
201
207
|
if (/^completed_at:/m.test(content)) {
|
|
202
208
|
return content.replace(/^(completed_at:\s*).*$/m, `$1'${isoDate}'`);
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { CAC } from "cac";
|
|
2
|
+
import { type ModuleResolver } from "#shared-utils/package-root.js";
|
|
3
|
+
export declare function resolveRulesyncBinFromAgentKit(resolver?: ModuleResolver): string | null;
|
|
4
|
+
/**
|
|
5
|
+
* The rulesync version agent-kit itself resolves — the only honest baseline to
|
|
6
|
+
* compare a consumer's install against.
|
|
7
|
+
*
|
|
8
|
+
* This used to be a hand-maintained `PINNED_RULESYNC_VERSION` literal, which
|
|
9
|
+
* drifted five majors behind the declared dependency (9.4.0 vs 14.0.1). The
|
|
10
|
+
* mismatch warning then fired on every single run, including inside agent-kit
|
|
11
|
+
* itself, which trains readers to ignore the one warning meant to catch a real
|
|
12
|
+
* mismatch. Deriving it from the resolved package makes that drift impossible.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveBundledRulesyncVersion(resolver?: ModuleResolver): string | null;
|
|
2
15
|
export interface CompileResult {
|
|
3
16
|
readonly ok: boolean;
|
|
4
17
|
readonly targets: readonly string[];
|