@webpresso/agent-kit 0.21.3 → 0.21.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +105 -41
- package/catalog/AGENTS.md.tpl +3 -1
- package/catalog/agent/rules/changeset-release.md +13 -16
- package/catalog/agent/skills/plan-refine/SKILL.md +5 -4
- package/catalog/base-kit/commitlint.config.ts.tmpl +1 -3
- package/catalog/base-kit/e2e/fixtures/smoke.html.tmpl +13 -0
- package/catalog/base-kit/e2e/smoke.spec.ts.tmpl +13 -0
- package/catalog/base-kit/oxlint.config.ts.tmpl +26 -0
- package/catalog/base-kit/playwright.config.ts.tmpl +10 -0
- package/catalog/base-kit/src/quality-sample.test.ts.tmpl +19 -0
- package/catalog/base-kit/src/quality-sample.ts.tmpl +11 -0
- package/catalog/base-kit/stryker.config.ts.tmpl +14 -0
- package/catalog/base-kit/tsconfig.json.tmpl +9 -0
- package/catalog/base-kit/vitest.config.ts.tmpl +10 -0
- package/catalog/docs/templates/adr.md +1 -1
- package/catalog/docs/templates/blueprint.md +1 -0
- package/catalog/docs/templates/blueprint.yaml +6 -3
- package/catalog/docs/templates/guide.md +1 -1
- package/catalog/docs/templates/postmortem.md +1 -1
- package/catalog/docs/templates/research.md +1 -1
- package/catalog/docs/templates/runbook.md +1 -1
- package/catalog/docs/templates/system.md +12 -3
- package/catalog/docs/templates/tech-debt.md +1 -0
- package/commands/blueprint.md +37 -4
- package/dist/esm/audit/resolve-audit-script.d.ts +24 -0
- package/dist/esm/audit/resolve-audit-script.js +27 -0
- package/dist/esm/blueprint/db/enums.d.ts +1 -1
- package/dist/esm/blueprint/index.d.ts +0 -1
- package/dist/esm/blueprint/index.js +0 -2
- package/dist/esm/blueprint/local.d.ts +0 -3
- package/dist/esm/blueprint/local.js +0 -2
- package/dist/esm/blueprint/service/BlueprintCreationService.js +5 -2
- package/dist/esm/blueprint/utils/package-assets.d.ts +11 -0
- package/dist/esm/blueprint/utils/package-assets.js +33 -4
- package/dist/esm/build/sync-catalog-doc-templates.d.ts +23 -0
- package/dist/esm/build/sync-catalog-doc-templates.js +93 -0
- package/dist/esm/cli/commands/audit.js +2 -7
- package/dist/esm/cli/commands/blueprint/router.js +5 -2
- package/dist/esm/cli/commands/blueprint/template-resolver.js +8 -4
- package/dist/esm/cli/commands/init/host-visibility.js +4 -2
- package/dist/esm/cli/commands/init/index.js +46 -7
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +12 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +141 -6
- package/dist/esm/cli/commands/typecheck.js +10 -4
- package/dist/esm/e2e/command-builder.js +26 -7
- package/dist/esm/e2e/execution.js +4 -0
- package/dist/esm/e2e/run-planner.js +1 -0
- package/dist/esm/e2e/types.d.ts +1 -0
- package/dist/esm/format/index.js +7 -1
- package/dist/esm/lint/index.js +3 -1
- package/dist/esm/mcp/blueprint-server.js +361 -66
- package/dist/esm/mcp/tools/audit.js +2 -8
- package/dist/esm/mcp/tools/e2e.d.ts +1 -1
- package/dist/esm/package.json +3 -0
- package/dist/esm/secret-gate/runner.js +4 -0
- package/dist/esm/test/command-builder.d.ts +1 -0
- package/dist/esm/test/command-builder.js +8 -2
- package/dist/esm/test-helpers/hermetic-env.d.ts +25 -0
- package/dist/esm/test-helpers/hermetic-env.js +31 -0
- package/dist/esm/tool-runtime/index.d.ts +5 -0
- package/dist/esm/tool-runtime/index.js +23 -0
- package/dist/esm/tool-runtime/resolve-runner.d.ts +13 -0
- package/dist/esm/tool-runtime/resolve-runner.js +40 -0
- package/package.json +12 -18
- package/skills/plan-refine/SKILL.md +5 -4
- package/dist/esm/blueprint/dag/cycle-detector.d.ts +0 -12
- package/dist/esm/blueprint/dag/cycle-detector.js +0 -46
- package/dist/esm/blueprint/dag/executor.d.ts +0 -140
- package/dist/esm/blueprint/dag/executor.js +0 -292
- package/dist/esm/blueprint/dag/index.d.ts +0 -20
- package/dist/esm/blueprint/dag/index.js +0 -17
- package/dist/esm/blueprint/dag/interfaces.d.ts +0 -56
- package/dist/esm/blueprint/dag/interfaces.js +0 -13
- package/dist/esm/blueprint/dag/local/independence.d.ts +0 -107
- package/dist/esm/blueprint/dag/local/independence.js +0 -231
- package/dist/esm/blueprint/dag/local/index.d.ts +0 -14
- package/dist/esm/blueprint/dag/local/index.js +0 -14
- package/dist/esm/blueprint/dag/local/package-graph.d.ts +0 -66
- package/dist/esm/blueprint/dag/local/package-graph.js +0 -148
- package/dist/esm/blueprint/dag/plan-parser.d.ts +0 -54
- package/dist/esm/blueprint/dag/plan-parser.js +0 -236
- package/dist/esm/blueprint/dag/task-graph-algorithms.d.ts +0 -13
- package/dist/esm/blueprint/dag/task-graph-algorithms.js +0 -236
- package/dist/esm/blueprint/dag/task-graph.d.ts +0 -171
- package/dist/esm/blueprint/dag/task-graph.js +0 -370
- package/dist/esm/blueprint/dag/types.d.ts +0 -17
- package/dist/esm/blueprint/dag/types.js +0 -2
- package/dist/esm/blueprint/graph/index.d.ts +0 -5
- package/dist/esm/blueprint/graph/index.js +0 -5
- package/dist/esm/blueprint/graph/mermaid-parser.d.ts +0 -3
- package/dist/esm/blueprint/graph/mermaid-parser.js +0 -93
- package/dist/esm/blueprint/graph/mermaid-serializer.d.ts +0 -3
- package/dist/esm/blueprint/graph/mermaid-serializer.js +0 -20
- package/dist/esm/blueprint/graph/schema.d.ts +0 -89
- package/dist/esm/blueprint/graph/schema.js +0 -104
- package/dist/esm/blueprint/graph/task-graph-adapter.d.ts +0 -6
- package/dist/esm/blueprint/graph/task-graph-adapter.js +0 -30
|
@@ -18,6 +18,7 @@ import path from 'node:path';
|
|
|
18
18
|
import matter from 'gray-matter';
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
import { parseBlueprint } from '#core/parser';
|
|
21
|
+
import { setBlueprintFrontmatterFields } from '#lifecycle/engine';
|
|
21
22
|
import { openDb } from '#db/connection.js';
|
|
22
23
|
import { resolveBlueprintProjectionDbPath } from '#db/paths.js';
|
|
23
24
|
import { findTemplate } from '#db/templates.js';
|
|
@@ -93,6 +94,13 @@ async function resolveSyncAdapter(cwd) {
|
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
const DEFAULT_PLATFORM_MUTATION_TIMEOUT_MS = 5_000;
|
|
97
|
+
function todayIsoDate() {
|
|
98
|
+
return new Date().toISOString().split('T')[0] ?? new Date().toISOString();
|
|
99
|
+
}
|
|
100
|
+
function formatBlueprintProgress(totalTasks, doneTasks, blockedTasks) {
|
|
101
|
+
const percent = totalTasks === 0 ? 0 : Math.round((doneTasks / totalTasks) * 100);
|
|
102
|
+
return `${percent}% (${doneTasks}/${totalTasks} tasks done, ${blockedTasks} blocked, updated ${todayIsoDate()})`;
|
|
103
|
+
}
|
|
96
104
|
function readPlatformMutationTimeoutMs() {
|
|
97
105
|
const parsed = Number.parseInt(process.env['WP_BLUEPRINT_PLATFORM_MUTATION_TIMEOUT_MS'] ??
|
|
98
106
|
String(DEFAULT_PLATFORM_MUTATION_TIMEOUT_MS), 10);
|
|
@@ -242,6 +250,18 @@ function openDbRW(cwd) {
|
|
|
242
250
|
async function reIngest(cwd) {
|
|
243
251
|
await reIngestProjection(cwd);
|
|
244
252
|
}
|
|
253
|
+
async function persistBlueprintMarkdown(input) {
|
|
254
|
+
const { projectCwd, slug, overviewPath, markdown } = input;
|
|
255
|
+
mkdirSync(path.dirname(overviewPath), { recursive: true });
|
|
256
|
+
parseBlueprint(markdown, slug);
|
|
257
|
+
writeFileSync(overviewPath, markdown, 'utf8');
|
|
258
|
+
await reIngest(projectCwd);
|
|
259
|
+
const refreshed = getCurrentProjectBlueprint(projectCwd, slug);
|
|
260
|
+
if (!refreshed.blueprint) {
|
|
261
|
+
throw new Error(`Blueprint "${slug}" did not appear in the projection after write`);
|
|
262
|
+
}
|
|
263
|
+
return refreshed.blueprint;
|
|
264
|
+
}
|
|
245
265
|
function findBlueprintDir(blueprintRoot, slug, states) {
|
|
246
266
|
for (const state of states) {
|
|
247
267
|
const d = path.join(blueprintRoot, state, slug);
|
|
@@ -963,10 +983,6 @@ async function handlePromote(projectResolver, cwd, raw) {
|
|
|
963
983
|
return err('wp_blueprint_promote failed', `Blueprint "${slug}" not found in any state directory`);
|
|
964
984
|
const { dir: currentDir, state: currentState } = found;
|
|
965
985
|
const overviewPath = path.join(currentDir, '_overview.md');
|
|
966
|
-
const ts = readVt(projectCwd);
|
|
967
|
-
const mtime = existsSync(overviewPath) ? statSync(overviewPath).mtimeMs : 0;
|
|
968
|
-
if ((ts[slug] ?? 0) < mtime)
|
|
969
|
-
return err('wp_blueprint_promote refused', `Blueprint "${slug}" not validated since last write. Run wp_blueprint_validate first.`);
|
|
970
986
|
if (to_state === 'completed') {
|
|
971
987
|
try {
|
|
972
988
|
assertBlueprintCanComplete(overviewPath, slug);
|
|
@@ -999,40 +1015,34 @@ async function handlePromote(projectResolver, cwd, raw) {
|
|
|
999
1015
|
catch (e) {
|
|
1000
1016
|
return err('wp_blueprint_promote failed', toStr(e));
|
|
1001
1017
|
}
|
|
1002
|
-
const { renameSync } = await import('node:fs');
|
|
1003
|
-
const destDir = path.join(root, to_state, slug);
|
|
1004
|
-
mkdirSync(path.dirname(destDir), { recursive: true });
|
|
1005
1018
|
try {
|
|
1006
|
-
|
|
1019
|
+
const transitioned = await applyLocalBlueprintTransition({
|
|
1020
|
+
found,
|
|
1021
|
+
projectCwd,
|
|
1022
|
+
slug,
|
|
1023
|
+
to_state,
|
|
1024
|
+
});
|
|
1025
|
+
const payload = {
|
|
1026
|
+
summary: `Blueprint "${slug}" promoted from "${currentState}" to "${to_state}"`,
|
|
1027
|
+
slug,
|
|
1028
|
+
from_state: currentState,
|
|
1029
|
+
to_state,
|
|
1030
|
+
new_path: transitioned.overviewPath,
|
|
1031
|
+
status: transitioned.blueprint.status,
|
|
1032
|
+
content_hash: transitioned.blueprint.content_hash,
|
|
1033
|
+
revision: transitioned.blueprint.content_hash,
|
|
1034
|
+
ingested_at: transitioned.blueprint.ingested_at,
|
|
1035
|
+
failures: [],
|
|
1036
|
+
bytes: 0,
|
|
1037
|
+
tokensSaved: 0,
|
|
1038
|
+
};
|
|
1039
|
+
if (currentState === 'draft' && to_state === 'planned')
|
|
1040
|
+
appendHint(payload, projectCwd, 'PLAN_REFINE');
|
|
1041
|
+
return finishPayload(payload);
|
|
1007
1042
|
}
|
|
1008
1043
|
catch (e) {
|
|
1009
|
-
return err('wp_blueprint_promote failed',
|
|
1010
|
-
}
|
|
1011
|
-
const destOverview = path.join(destDir, '_overview.md');
|
|
1012
|
-
if (existsSync(destOverview)) {
|
|
1013
|
-
const fm = matter(readFileSync(destOverview, 'utf8'));
|
|
1014
|
-
fm.data['status'] = to_state;
|
|
1015
|
-
writeFileSync(destOverview, matter.stringify(fm.content, fm.data), 'utf8');
|
|
1016
|
-
}
|
|
1017
|
-
try {
|
|
1018
|
-
await reIngest(projectCwd);
|
|
1019
|
-
}
|
|
1020
|
-
catch {
|
|
1021
|
-
/* non-fatal */
|
|
1044
|
+
return err('wp_blueprint_promote failed', toStr(e));
|
|
1022
1045
|
}
|
|
1023
|
-
const payload = {
|
|
1024
|
-
summary: `Blueprint "${slug}" promoted from "${currentState}" to "${to_state}"`,
|
|
1025
|
-
slug,
|
|
1026
|
-
from_state: currentState,
|
|
1027
|
-
to_state,
|
|
1028
|
-
new_path: destOverview,
|
|
1029
|
-
failures: [],
|
|
1030
|
-
bytes: 0,
|
|
1031
|
-
tokensSaved: 0,
|
|
1032
|
-
};
|
|
1033
|
-
if (currentState === 'draft' && to_state === 'planned')
|
|
1034
|
-
appendHint(payload, projectCwd, 'PLAN_REFINE');
|
|
1035
|
-
return finishPayload(payload);
|
|
1036
1046
|
}
|
|
1037
1047
|
const finalizeSchema = z.object({ project_id: z.string().optional(), slug: z.string() });
|
|
1038
1048
|
async function handleFinalize(projectResolver, cwd, raw) {
|
|
@@ -1102,39 +1112,32 @@ async function handleFinalize(projectResolver, cwd, raw) {
|
|
|
1102
1112
|
catch (e) {
|
|
1103
1113
|
return err('wp_blueprint_finalize failed', toStr(e));
|
|
1104
1114
|
}
|
|
1105
|
-
const { renameSync } = await import('node:fs');
|
|
1106
|
-
const destDir = path.join(root, 'completed', slug);
|
|
1107
|
-
mkdirSync(path.dirname(destDir), { recursive: true });
|
|
1108
1115
|
try {
|
|
1109
|
-
|
|
1116
|
+
const transitioned = await applyLocalBlueprintTransition({
|
|
1117
|
+
found,
|
|
1118
|
+
projectCwd,
|
|
1119
|
+
slug,
|
|
1120
|
+
to_state: 'completed',
|
|
1121
|
+
});
|
|
1122
|
+
const payload = {
|
|
1123
|
+
summary: `Blueprint "${slug}" finalized and moved to completed`,
|
|
1124
|
+
slug,
|
|
1125
|
+
new_path: transitioned.overviewPath,
|
|
1126
|
+
status: transitioned.blueprint.status,
|
|
1127
|
+
content_hash: transitioned.blueprint.content_hash,
|
|
1128
|
+
revision: transitioned.blueprint.content_hash,
|
|
1129
|
+
ingested_at: transitioned.blueprint.ingested_at,
|
|
1130
|
+
failures: [],
|
|
1131
|
+
bytes: 0,
|
|
1132
|
+
tokensSaved: 0,
|
|
1133
|
+
};
|
|
1134
|
+
if (hasRecentAuditFinding(projectCwd))
|
|
1135
|
+
appendHint(payload, projectCwd, 'AUDIT_FIX');
|
|
1136
|
+
return finishPayload(payload);
|
|
1110
1137
|
}
|
|
1111
1138
|
catch (e) {
|
|
1112
|
-
return err('wp_blueprint_finalize failed',
|
|
1113
|
-
}
|
|
1114
|
-
const destOverview = path.join(destDir, '_overview.md');
|
|
1115
|
-
if (existsSync(destOverview)) {
|
|
1116
|
-
const fm = matter(readFileSync(destOverview, 'utf8'));
|
|
1117
|
-
fm.data['status'] = 'completed';
|
|
1118
|
-
fm.data['completed_at'] = new Date().toISOString().split('T')[0] ?? '';
|
|
1119
|
-
writeFileSync(destOverview, matter.stringify(fm.content, fm.data), 'utf8');
|
|
1120
|
-
}
|
|
1121
|
-
try {
|
|
1122
|
-
await reIngest(projectCwd);
|
|
1123
|
-
}
|
|
1124
|
-
catch {
|
|
1125
|
-
/* non-fatal */
|
|
1139
|
+
return err('wp_blueprint_finalize failed', toStr(e));
|
|
1126
1140
|
}
|
|
1127
|
-
const payload = {
|
|
1128
|
-
summary: `Blueprint "${slug}" finalized and moved to completed`,
|
|
1129
|
-
slug,
|
|
1130
|
-
new_path: destOverview,
|
|
1131
|
-
failures: [],
|
|
1132
|
-
bytes: 0,
|
|
1133
|
-
tokensSaved: 0,
|
|
1134
|
-
};
|
|
1135
|
-
if (hasRecentAuditFinding(projectCwd))
|
|
1136
|
-
appendHint(payload, projectCwd, 'AUDIT_FIX');
|
|
1137
|
-
return finishPayload(payload);
|
|
1138
1141
|
}
|
|
1139
1142
|
function assertBlueprintCanComplete(overviewPath, slug) {
|
|
1140
1143
|
const markdown = readFileSync(overviewPath, 'utf8');
|
|
@@ -1698,6 +1701,243 @@ const createSchema = MutationTarget.extend({
|
|
|
1698
1701
|
request_id: z.string().min(1).optional(),
|
|
1699
1702
|
head_at_ingest: z.string().nullable().optional(),
|
|
1700
1703
|
});
|
|
1704
|
+
const putTaskSchema = z.object({
|
|
1705
|
+
id: z.string().min(1),
|
|
1706
|
+
title: z.string().min(1),
|
|
1707
|
+
status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'dropped']).default('todo'),
|
|
1708
|
+
wave: z.string().optional(),
|
|
1709
|
+
lane: z.string().optional(),
|
|
1710
|
+
description: z.string().optional(),
|
|
1711
|
+
acceptance: z.array(z.string().min(1)).min(1),
|
|
1712
|
+
});
|
|
1713
|
+
const putDocumentSchema = z.object({
|
|
1714
|
+
type: z.literal('blueprint').default('blueprint'),
|
|
1715
|
+
title: z.string().min(1),
|
|
1716
|
+
status: z.enum(['draft', 'planned', 'in-progress', 'completed', 'parked', 'archived']),
|
|
1717
|
+
complexity: z.enum(['XS', 'S', 'M', 'L', 'XL']),
|
|
1718
|
+
owner: z.string().min(1),
|
|
1719
|
+
created: z.string().min(1),
|
|
1720
|
+
last_updated: z.string().min(1),
|
|
1721
|
+
progress: z.string().optional(),
|
|
1722
|
+
tags: z.array(z.string()).optional(),
|
|
1723
|
+
product_wedge_anchor: z.object({
|
|
1724
|
+
stage_outcome: z.string().min(1),
|
|
1725
|
+
consuming_surface: z.string().min(1),
|
|
1726
|
+
new_user_visible_capability: z.string().min(1),
|
|
1727
|
+
}),
|
|
1728
|
+
summary: z.string().min(1),
|
|
1729
|
+
tasks: z.array(putTaskSchema).min(1),
|
|
1730
|
+
});
|
|
1731
|
+
const putSchema = MutationTarget.extend({
|
|
1732
|
+
slug: z.string().min(1),
|
|
1733
|
+
document: putDocumentSchema,
|
|
1734
|
+
request_id: z.string().min(1).optional(),
|
|
1735
|
+
head_at_ingest: z.string().nullable().optional(),
|
|
1736
|
+
});
|
|
1737
|
+
function renderBlueprintMarkdownFromDocument(slug, document) {
|
|
1738
|
+
const frontmatter = [
|
|
1739
|
+
'---',
|
|
1740
|
+
`type: ${document.type}`,
|
|
1741
|
+
`title: ${document.title}`,
|
|
1742
|
+
`status: ${document.status}`,
|
|
1743
|
+
`complexity: ${document.complexity}`,
|
|
1744
|
+
`owner: ${document.owner}`,
|
|
1745
|
+
`created: '${document.created}'`,
|
|
1746
|
+
`last_updated: '${document.last_updated}'`,
|
|
1747
|
+
...(document.progress ? [`progress: ${JSON.stringify(document.progress)}`] : []),
|
|
1748
|
+
...(document.tags && document.tags.length > 0
|
|
1749
|
+
? ['tags:', ...document.tags.map((tag) => ` - ${tag}`)]
|
|
1750
|
+
: []),
|
|
1751
|
+
'---',
|
|
1752
|
+
'',
|
|
1753
|
+
];
|
|
1754
|
+
const sections = [
|
|
1755
|
+
`# ${document.title}`,
|
|
1756
|
+
'',
|
|
1757
|
+
'## Product wedge anchor',
|
|
1758
|
+
'',
|
|
1759
|
+
`- **Stage outcome:** ${document.product_wedge_anchor.stage_outcome}`,
|
|
1760
|
+
`- **Consuming surface:** ${document.product_wedge_anchor.consuming_surface}`,
|
|
1761
|
+
`- **New user-visible capability:** ${document.product_wedge_anchor.new_user_visible_capability}`,
|
|
1762
|
+
'',
|
|
1763
|
+
'## Summary',
|
|
1764
|
+
'',
|
|
1765
|
+
document.summary,
|
|
1766
|
+
'',
|
|
1767
|
+
];
|
|
1768
|
+
const taskBlocks = document.tasks.flatMap((task) => [
|
|
1769
|
+
`#### Task ${task.id}: ${task.title}`,
|
|
1770
|
+
'',
|
|
1771
|
+
`**Status:** ${task.status}`,
|
|
1772
|
+
...(task.wave ? [`**Wave:** ${task.wave}`] : []),
|
|
1773
|
+
...(task.lane ? [`**Lane:** ${task.lane}`] : []),
|
|
1774
|
+
...(task.description ? ['', task.description] : []),
|
|
1775
|
+
'',
|
|
1776
|
+
'**Acceptance:**',
|
|
1777
|
+
...task.acceptance.map((item) => `- [ ] ${item}`),
|
|
1778
|
+
'',
|
|
1779
|
+
]);
|
|
1780
|
+
return [...frontmatter, ...sections, ...taskBlocks].join('\n').trimEnd() + '\n';
|
|
1781
|
+
}
|
|
1782
|
+
async function handleBlueprintPut(projectResolver, cwd, raw) {
|
|
1783
|
+
const p = putSchema.safeParse(raw);
|
|
1784
|
+
if (!p.success)
|
|
1785
|
+
return err('wp_blueprint_put validation error', p.error.message);
|
|
1786
|
+
const { project_id, slug, document, request_id, head_at_ingest } = p.data;
|
|
1787
|
+
const resolvedProject = await resolveToolProject(projectResolver, cwd, project_id);
|
|
1788
|
+
if ('content' in resolvedProject)
|
|
1789
|
+
return resolvedProject;
|
|
1790
|
+
const projectCwd = resolvedProject.cwd;
|
|
1791
|
+
await ensureProjectionReady(projectCwd);
|
|
1792
|
+
const freshnessFailure = validateMutationFreshnessToken(projectCwd, head_at_ingest, 'wp_blueprint_put', 'wp_blueprint_get');
|
|
1793
|
+
if (freshnessFailure)
|
|
1794
|
+
return freshnessFailure;
|
|
1795
|
+
const payloadHash = hashMutationPayload({ slug, document });
|
|
1796
|
+
const replay = request_id !== undefined
|
|
1797
|
+
? readMutationReplay(projectCwd, 'wp_blueprint_put', request_id, payloadHash)
|
|
1798
|
+
: null;
|
|
1799
|
+
if (replay)
|
|
1800
|
+
return replay;
|
|
1801
|
+
const root = resolveBlueprintRoot(projectCwd);
|
|
1802
|
+
const found = findBlueprintDir(root, slug, ALL_STATES);
|
|
1803
|
+
if (found && found.state !== document.status) {
|
|
1804
|
+
return err('wp_blueprint_put refused', `Blueprint "${slug}" currently lives in "${found.state}" and cannot be rewritten as "${document.status}" without a lifecycle transition.`);
|
|
1805
|
+
}
|
|
1806
|
+
if (!found && document.status !== 'draft') {
|
|
1807
|
+
return err('wp_blueprint_put refused', `New blueprint "${slug}" must start in "draft"; use wp_blueprint_transition for later lifecycle moves.`);
|
|
1808
|
+
}
|
|
1809
|
+
const overviewPath = found
|
|
1810
|
+
? path.join(found.dir, '_overview.md')
|
|
1811
|
+
: path.join(root, document.status, slug, '_overview.md');
|
|
1812
|
+
try {
|
|
1813
|
+
const markdown = renderBlueprintMarkdownFromDocument(slug, document);
|
|
1814
|
+
const blueprint = await persistBlueprintMarkdown({
|
|
1815
|
+
projectCwd,
|
|
1816
|
+
slug,
|
|
1817
|
+
overviewPath,
|
|
1818
|
+
markdown,
|
|
1819
|
+
});
|
|
1820
|
+
const payload = {
|
|
1821
|
+
summary: `Blueprint "${slug}" written to ${overviewPath}`,
|
|
1822
|
+
slug,
|
|
1823
|
+
path: overviewPath,
|
|
1824
|
+
status: blueprint.status,
|
|
1825
|
+
content_hash: blueprint.content_hash,
|
|
1826
|
+
ingested_at: blueprint.ingested_at,
|
|
1827
|
+
revision: blueprint.content_hash,
|
|
1828
|
+
idempotent: false,
|
|
1829
|
+
failures: [],
|
|
1830
|
+
next_action: makeNextAction('verify_task', 'Blueprint written. Next: validate or transition the latest revision token through the structured surface.'),
|
|
1831
|
+
project_id: resolvedProject.project_id ?? projectCwd,
|
|
1832
|
+
};
|
|
1833
|
+
if (request_id !== undefined) {
|
|
1834
|
+
recordMutationReplay(projectCwd, 'wp_blueprint_put', request_id, payloadHash, payload);
|
|
1835
|
+
}
|
|
1836
|
+
return finishPayload(payload);
|
|
1837
|
+
}
|
|
1838
|
+
catch (e) {
|
|
1839
|
+
return err('wp_blueprint_put failed', toStr(e));
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
const transitionSchema = MutationTarget.extend({
|
|
1843
|
+
slug: z.string().min(1),
|
|
1844
|
+
to_state: z.enum(['draft', 'planned', 'in-progress', 'completed', 'parked', 'archived']),
|
|
1845
|
+
expected_version: z.string().min(1),
|
|
1846
|
+
});
|
|
1847
|
+
async function handleBlueprintTransition(projectResolver, cwd, raw) {
|
|
1848
|
+
const p = transitionSchema.safeParse(raw);
|
|
1849
|
+
if (!p.success)
|
|
1850
|
+
return err('wp_blueprint_transition validation error', p.error.message);
|
|
1851
|
+
const { project_id, slug, to_state, expected_version } = p.data;
|
|
1852
|
+
const resolvedProject = await resolveToolProject(projectResolver, cwd, project_id);
|
|
1853
|
+
if ('content' in resolvedProject)
|
|
1854
|
+
return resolvedProject;
|
|
1855
|
+
const projectCwd = resolvedProject.cwd;
|
|
1856
|
+
await ensureProjectionReady(projectCwd);
|
|
1857
|
+
const target = dbPath(projectCwd);
|
|
1858
|
+
if (!existsSync(target))
|
|
1859
|
+
return err('wp_blueprint_transition failed', 'Blueprint DB not found');
|
|
1860
|
+
const current = getCurrentProjectBlueprint(projectCwd, slug);
|
|
1861
|
+
if (!current.blueprint) {
|
|
1862
|
+
return err('wp_blueprint_transition failed', `Blueprint "${slug}" not found`);
|
|
1863
|
+
}
|
|
1864
|
+
if (current.blueprint.content_hash !== expected_version) {
|
|
1865
|
+
return jsonContent({
|
|
1866
|
+
summary: `wp_blueprint_transition rejected a stale blueprint revision`,
|
|
1867
|
+
failures: [
|
|
1868
|
+
`expected_version "${expected_version}" does not match current content hash "${current.blueprint.content_hash}"`,
|
|
1869
|
+
],
|
|
1870
|
+
error: 'stale_blueprint_revision',
|
|
1871
|
+
next_action: makeNextAction('reingest_project', 'Call wp_blueprint_get again to fetch the latest content_hash before retrying the transition.'),
|
|
1872
|
+
bytes: 0,
|
|
1873
|
+
tokensSaved: 0,
|
|
1874
|
+
}, true);
|
|
1875
|
+
}
|
|
1876
|
+
const root = resolveBlueprintRoot(projectCwd);
|
|
1877
|
+
const found = findBlueprintDir(root, slug, ALL_STATES);
|
|
1878
|
+
if (!found)
|
|
1879
|
+
return err('wp_blueprint_transition failed', `Blueprint "${slug}" not found on disk`);
|
|
1880
|
+
try {
|
|
1881
|
+
const refreshed = await applyLocalBlueprintTransition({
|
|
1882
|
+
found,
|
|
1883
|
+
projectCwd,
|
|
1884
|
+
slug,
|
|
1885
|
+
to_state,
|
|
1886
|
+
});
|
|
1887
|
+
return finishPayload({
|
|
1888
|
+
summary: `Blueprint "${slug}" transitioned to ${to_state}`,
|
|
1889
|
+
slug,
|
|
1890
|
+
old_status: current.blueprint.status,
|
|
1891
|
+
new_status: to_state,
|
|
1892
|
+
status: refreshed.blueprint.status,
|
|
1893
|
+
content_hash: refreshed.blueprint.content_hash,
|
|
1894
|
+
revision: refreshed.blueprint.content_hash,
|
|
1895
|
+
ingested_at: refreshed.blueprint.ingested_at,
|
|
1896
|
+
failures: [],
|
|
1897
|
+
project_id: resolvedProject.project_id ?? projectCwd,
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
catch (e) {
|
|
1901
|
+
return err('wp_blueprint_transition failed', toStr(e));
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
async function applyLocalBlueprintTransition(input) {
|
|
1905
|
+
const { projectCwd, slug, to_state, found } = input;
|
|
1906
|
+
const root = resolveBlueprintRoot(projectCwd);
|
|
1907
|
+
const overviewPath = path.join(found.dir, '_overview.md');
|
|
1908
|
+
const parsed = runValidate(overviewPath);
|
|
1909
|
+
if (!parsed.valid) {
|
|
1910
|
+
throw new Error(parsed.gaps.join('; '));
|
|
1911
|
+
}
|
|
1912
|
+
const markdown = readFileSync(overviewPath, 'utf8');
|
|
1913
|
+
const currentBlueprint = parseBlueprint(markdown, slug);
|
|
1914
|
+
const updated = setBlueprintFrontmatterFields(markdown, {
|
|
1915
|
+
status: to_state,
|
|
1916
|
+
last_updated: todayIsoDate(),
|
|
1917
|
+
completed_at: to_state === 'completed' ? todayIsoDate() : undefined,
|
|
1918
|
+
progress: formatBlueprintProgress(currentBlueprint.tasks.length, currentBlueprint.tasks.filter((task) => task.status === 'done').length, currentBlueprint.tasks.filter((task) => task.status === 'blocked').length),
|
|
1919
|
+
});
|
|
1920
|
+
parseBlueprint(updated, slug);
|
|
1921
|
+
const destDir = path.join(root, to_state, slug);
|
|
1922
|
+
mkdirSync(path.dirname(destDir), { recursive: true });
|
|
1923
|
+
let finalOverviewPath = overviewPath;
|
|
1924
|
+
if (found.state !== to_state) {
|
|
1925
|
+
const { renameSync } = await import('node:fs');
|
|
1926
|
+
renameSync(found.dir, destDir);
|
|
1927
|
+
finalOverviewPath = path.join(destDir, '_overview.md');
|
|
1928
|
+
}
|
|
1929
|
+
writeFileSync(finalOverviewPath, updated, 'utf8');
|
|
1930
|
+
await reIngest(projectCwd);
|
|
1931
|
+
const refreshed = getCurrentProjectBlueprint(projectCwd, slug);
|
|
1932
|
+
if (!refreshed.blueprint) {
|
|
1933
|
+
throw new Error(`Blueprint "${slug}" did not appear in the projection after transition`);
|
|
1934
|
+
}
|
|
1935
|
+
return {
|
|
1936
|
+
blueprint: refreshed.blueprint,
|
|
1937
|
+
overviewPath: finalOverviewPath,
|
|
1938
|
+
fromState: found.state,
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1701
1941
|
async function handleBlueprintCreate(projectResolver, cwd, raw) {
|
|
1702
1942
|
const p = createSchema.safeParse(raw);
|
|
1703
1943
|
if (!p.success)
|
|
@@ -1728,9 +1968,12 @@ async function handleBlueprintCreate(projectResolver, cwd, raw) {
|
|
|
1728
1968
|
.replace(/{COMPLEXITY}/g, complexity)
|
|
1729
1969
|
.replace(/{DATE}/g, today)
|
|
1730
1970
|
.replace('{GOAL}', goal);
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1971
|
+
await persistBlueprintMarkdown({
|
|
1972
|
+
projectCwd,
|
|
1973
|
+
slug,
|
|
1974
|
+
overviewPath,
|
|
1975
|
+
markdown: content,
|
|
1976
|
+
});
|
|
1734
1977
|
const b = bytes(content);
|
|
1735
1978
|
const payload = {
|
|
1736
1979
|
summary: `Blueprint "${slug}" created at ${overviewPath}`,
|
|
@@ -1788,6 +2031,58 @@ export async function registerBlueprintTools(registrar, cwd, projectResolver = c
|
|
|
1788
2031
|
},
|
|
1789
2032
|
required: ['title', 'goal_prompt'],
|
|
1790
2033
|
}, undefined, (r) => handleNew(cwd, r), { title: 'Blueprint New', readOnlyHint: true, openWorldHint: false });
|
|
2034
|
+
registrar.registerTool('wp_blueprint_put', 'Create or replace a blueprint from a structured whole-document payload. Renders markdown, validates it, re-ingests the projection, and returns revision metadata.', {
|
|
2035
|
+
type: 'object',
|
|
2036
|
+
properties: {
|
|
2037
|
+
project_id: { type: 'string' },
|
|
2038
|
+
slug: { type: 'string' },
|
|
2039
|
+
document: { type: 'object' },
|
|
2040
|
+
request_id: { type: 'string' },
|
|
2041
|
+
head_at_ingest: { type: ['string', 'null'] },
|
|
2042
|
+
},
|
|
2043
|
+
required: ['project_id', 'slug', 'document'],
|
|
2044
|
+
}, {
|
|
2045
|
+
...summaryEnvelopeOutputSchema,
|
|
2046
|
+
properties: {
|
|
2047
|
+
...summaryEnvelopeOutputSchema.properties,
|
|
2048
|
+
slug: { type: 'string' },
|
|
2049
|
+
path: { type: 'string' },
|
|
2050
|
+
status: { type: 'string' },
|
|
2051
|
+
content_hash: { type: 'string' },
|
|
2052
|
+
ingested_at: { type: 'number' },
|
|
2053
|
+
revision: { type: 'string' },
|
|
2054
|
+
idempotent: { type: 'boolean' },
|
|
2055
|
+
project_id: { type: 'string' },
|
|
2056
|
+
next_action: nextActionOutputSchema,
|
|
2057
|
+
},
|
|
2058
|
+
}, (r) => handleBlueprintPut(projectResolver, cwd, r), { title: 'Blueprint Put', destructiveHint: false, openWorldHint: false });
|
|
2059
|
+
registrar.registerTool('wp_blueprint_transition', 'Transition a blueprint lifecycle state using an expected revision token. Revalidates the latest markdown, rewrites frontmatter atomically, re-ingests the projection, and returns updated revision metadata.', {
|
|
2060
|
+
type: 'object',
|
|
2061
|
+
properties: {
|
|
2062
|
+
project_id: { type: 'string' },
|
|
2063
|
+
slug: { type: 'string' },
|
|
2064
|
+
to_state: {
|
|
2065
|
+
type: 'string',
|
|
2066
|
+
enum: ['draft', 'planned', 'in-progress', 'completed', 'parked', 'archived'],
|
|
2067
|
+
},
|
|
2068
|
+
expected_version: { type: 'string' },
|
|
2069
|
+
},
|
|
2070
|
+
required: ['project_id', 'slug', 'to_state', 'expected_version'],
|
|
2071
|
+
}, {
|
|
2072
|
+
...summaryEnvelopeOutputSchema,
|
|
2073
|
+
properties: {
|
|
2074
|
+
...summaryEnvelopeOutputSchema.properties,
|
|
2075
|
+
slug: { type: 'string' },
|
|
2076
|
+
old_status: { type: 'string' },
|
|
2077
|
+
new_status: { type: 'string' },
|
|
2078
|
+
status: { type: 'string' },
|
|
2079
|
+
content_hash: { type: 'string' },
|
|
2080
|
+
revision: { type: 'string' },
|
|
2081
|
+
ingested_at: { type: 'number' },
|
|
2082
|
+
project_id: { type: 'string' },
|
|
2083
|
+
next_action: nextActionOutputSchema,
|
|
2084
|
+
},
|
|
2085
|
+
}, (r) => handleBlueprintTransition(projectResolver, cwd, r), { title: 'Blueprint Transition', destructiveHint: false, openWorldHint: false });
|
|
1791
2086
|
registrar.registerTool('wp_blueprint_validate', 'Validate _overview.md structure. Returns { valid, gaps }. Must pass before wp_blueprint_promote.', { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] }, undefined, (r) => handleValidate(cwd, r), { title: 'Blueprint Validate', readOnlyHint: false, openWorldHint: false });
|
|
1792
2087
|
registrar.registerTool('wp_blueprint_task_next', 'Return the next ready task (all deps done). Accepts optional project_id for nested-workspace disambiguation. Returns { summary, task }.', {
|
|
1793
2088
|
type: 'object',
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
* — the handler never throws out, so the MCP server stays responsive.
|
|
16
16
|
*/
|
|
17
17
|
import { spawn } from 'node:child_process';
|
|
18
|
-
import { existsSync } from 'node:fs';
|
|
19
18
|
import { z } from 'zod';
|
|
20
|
-
import {
|
|
19
|
+
import { resolveAuditScriptPath } from '#audit/resolve-audit-script';
|
|
21
20
|
import { applyOutputTransform } from '#output-transforms/index';
|
|
22
21
|
import { createSummaryOutputSchema, createSummaryResult } from './_shared/result.js';
|
|
23
22
|
const KINDS = [
|
|
@@ -62,12 +61,7 @@ const outputSchema = createSummaryOutputSchema({
|
|
|
62
61
|
kind: z.enum(KINDS),
|
|
63
62
|
});
|
|
64
63
|
function resolveAuditScript(name) {
|
|
65
|
-
|
|
66
|
-
const fromSource = new URL(`../../audit/${name}`, import.meta.url);
|
|
67
|
-
if (existsSync(fromSource)) {
|
|
68
|
-
return fromSource.pathname;
|
|
69
|
-
}
|
|
70
|
-
return resolvePackageAsset(`src/audit/${name}`);
|
|
64
|
+
return resolveAuditScriptPath(name, { moduleUrl: import.meta.url });
|
|
71
65
|
}
|
|
72
66
|
async function runScript(script) {
|
|
73
67
|
return new Promise((resolve) => {
|
|
@@ -12,8 +12,8 @@ declare const inputSchema: z.ZodObject<{
|
|
|
12
12
|
suite: z.ZodOptional<z.ZodString>;
|
|
13
13
|
runner: z.ZodOptional<z.ZodEnum<{
|
|
14
14
|
command: "command";
|
|
15
|
-
playwright: "playwright";
|
|
16
15
|
vitest: "vitest";
|
|
16
|
+
playwright: "playwright";
|
|
17
17
|
}>>;
|
|
18
18
|
config: z.ZodOptional<z.ZodString>;
|
|
19
19
|
files: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
package/dist/esm/package.json
CHANGED
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
"#mcp/*": "./mcp/*.js",
|
|
49
49
|
"#content/*.js": "./content/*.js",
|
|
50
50
|
"#content/*": "./content/*.js",
|
|
51
|
+
"#tool-runtime": "./tool-runtime/index.js",
|
|
52
|
+
"#tool-runtime/*.js": "./tool-runtime/*.js",
|
|
53
|
+
"#tool-runtime/*": "./tool-runtime/*.js",
|
|
51
54
|
"#output-transforms/*.js": "./output-transforms/*.js",
|
|
52
55
|
"#output-transforms/*": "./output-transforms/*.js",
|
|
53
56
|
"#lint/*.js": "./lint/*.js",
|
|
@@ -5,9 +5,13 @@ const SIGNAL_TO_EXIT_CODE = {
|
|
|
5
5
|
SIGKILL: 9,
|
|
6
6
|
SIGTERM: 15,
|
|
7
7
|
};
|
|
8
|
+
const DIRECT_ENV_PROFILES = new Set(['none', 'public']);
|
|
8
9
|
export function buildSecretGateCommand(options) {
|
|
9
10
|
const runner = options.runner?.trim() || 'with-secrets';
|
|
10
11
|
const envProfile = options.envProfile?.trim();
|
|
12
|
+
if (runner === 'with-secrets' && envProfile && DIRECT_ENV_PROFILES.has(envProfile)) {
|
|
13
|
+
return { command: options.command, args: [...(options.args ?? [])] };
|
|
14
|
+
}
|
|
11
15
|
const args = envProfile
|
|
12
16
|
? ['--env-profile', envProfile, '--', options.command, ...(options.args ?? [])]
|
|
13
17
|
: ['--', options.command, ...(options.args ?? [])];
|
|
@@ -17,6 +17,7 @@ export interface TestCommandOptions {
|
|
|
17
17
|
concurrencyLimit?: number;
|
|
18
18
|
log?: VpRunLogMode;
|
|
19
19
|
passthrough?: readonly string[];
|
|
20
|
+
filterOutput?: boolean;
|
|
20
21
|
}
|
|
21
22
|
export declare function buildTestCommand(target: ResolvedTestTarget, options?: TestCommandOptions): CommandConfig;
|
|
22
23
|
export declare function buildVpTestCommand(filters: readonly string[], options?: TestCommandOptions): CommandConfig;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getManagedRunner } from '#tool-runtime';
|
|
1
2
|
export function buildTestCommand(target, options = {}) {
|
|
2
3
|
if (target.type === 'file') {
|
|
3
4
|
return buildVitestCommand(target.values, options);
|
|
@@ -17,8 +18,12 @@ export function buildVpTestCommand(filters, options = {}) {
|
|
|
17
18
|
if (passthrough.length > 0) {
|
|
18
19
|
args.push('--', ...passthrough);
|
|
19
20
|
}
|
|
21
|
+
const resolution = getManagedRunner('vp', { filterOutput: options.filterOutput });
|
|
20
22
|
const env = buildVpRunEnv(options);
|
|
21
|
-
|
|
23
|
+
const mergedArgs = [...resolution.args, ...args];
|
|
24
|
+
return env
|
|
25
|
+
? { command: resolution.command, args: mergedArgs, env }
|
|
26
|
+
: { command: resolution.command, args: mergedArgs };
|
|
22
27
|
}
|
|
23
28
|
export function buildVitestCommand(files, options = {}) {
|
|
24
29
|
const args = [options.watch ? '--watch' : 'run'];
|
|
@@ -40,7 +45,8 @@ export function buildVitestCommand(files, options = {}) {
|
|
|
40
45
|
args.push('--config', configFile);
|
|
41
46
|
}
|
|
42
47
|
args.push(...buildVitestPassthrough(options), ...testFiles);
|
|
43
|
-
|
|
48
|
+
const resolution = getManagedRunner('vitest', { filterOutput: options.filterOutput });
|
|
49
|
+
return { command: resolution.command, args: [...resolution.args, ...args] };
|
|
44
50
|
}
|
|
45
51
|
export function getVpTestTask(options) {
|
|
46
52
|
if (options.mutation)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic environment baseline for the whole test suite.
|
|
3
|
+
*
|
|
4
|
+
* agent-kit's suite is routinely run *inside* an agent session (Claude Code,
|
|
5
|
+
* Codex), and those sessions export ambient variables that the production code
|
|
6
|
+
* legitimately honors at runtime:
|
|
7
|
+
*
|
|
8
|
+
* - `CLAUDE_PROJECT_DIR` — `resolveProjectRoot` (src/mcp/tools/_shared/project-root.ts)
|
|
9
|
+
* ranks this above the discovered cwd, so a leaked value makes blueprint-server
|
|
10
|
+
* tests target the real repo + its shared SQLite DB instead of their temp dir,
|
|
11
|
+
* producing wrong-data assertions and lock-contention timeouts.
|
|
12
|
+
* - `WP_SKIP_UPDATE_CHECK` — suppresses the managed-CLI refresh spawn that the
|
|
13
|
+
* init scaffolder tests assert fires; a leaked value (exported by the shell or
|
|
14
|
+
* left behind by a sibling test file under the shared forks worker) drops the
|
|
15
|
+
* expected spawn and fails spawn-count assertions.
|
|
16
|
+
*
|
|
17
|
+
* The fix is isolation, not changing the runtime precedence: reset these before
|
|
18
|
+
* every test so the suite is deterministic regardless of the launching
|
|
19
|
+
* environment. Tests that exercise these variables set them explicitly in their
|
|
20
|
+
* own body (which runs after this hook), so behavior coverage is unaffected.
|
|
21
|
+
*/
|
|
22
|
+
export declare const LEAKY_ENV_KEYS: readonly ["CLAUDE_PROJECT_DIR", "WP_SKIP_UPDATE_CHECK"];
|
|
23
|
+
/** Delete every agent-session-leaked env var so each test starts hermetic. */
|
|
24
|
+
export declare function resetLeakyEnv(): void;
|
|
25
|
+
//# sourceMappingURL=hermetic-env.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { beforeEach } from 'vitest';
|
|
2
|
+
/**
|
|
3
|
+
* Hermetic environment baseline for the whole test suite.
|
|
4
|
+
*
|
|
5
|
+
* agent-kit's suite is routinely run *inside* an agent session (Claude Code,
|
|
6
|
+
* Codex), and those sessions export ambient variables that the production code
|
|
7
|
+
* legitimately honors at runtime:
|
|
8
|
+
*
|
|
9
|
+
* - `CLAUDE_PROJECT_DIR` — `resolveProjectRoot` (src/mcp/tools/_shared/project-root.ts)
|
|
10
|
+
* ranks this above the discovered cwd, so a leaked value makes blueprint-server
|
|
11
|
+
* tests target the real repo + its shared SQLite DB instead of their temp dir,
|
|
12
|
+
* producing wrong-data assertions and lock-contention timeouts.
|
|
13
|
+
* - `WP_SKIP_UPDATE_CHECK` — suppresses the managed-CLI refresh spawn that the
|
|
14
|
+
* init scaffolder tests assert fires; a leaked value (exported by the shell or
|
|
15
|
+
* left behind by a sibling test file under the shared forks worker) drops the
|
|
16
|
+
* expected spawn and fails spawn-count assertions.
|
|
17
|
+
*
|
|
18
|
+
* The fix is isolation, not changing the runtime precedence: reset these before
|
|
19
|
+
* every test so the suite is deterministic regardless of the launching
|
|
20
|
+
* environment. Tests that exercise these variables set them explicitly in their
|
|
21
|
+
* own body (which runs after this hook), so behavior coverage is unaffected.
|
|
22
|
+
*/
|
|
23
|
+
export const LEAKY_ENV_KEYS = ['CLAUDE_PROJECT_DIR', 'WP_SKIP_UPDATE_CHECK'];
|
|
24
|
+
/** Delete every agent-session-leaked env var so each test starts hermetic. */
|
|
25
|
+
export function resetLeakyEnv() {
|
|
26
|
+
for (const key of LEAKY_ENV_KEYS) {
|
|
27
|
+
delete process.env[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
beforeEach(resetLeakyEnv);
|
|
31
|
+
//# sourceMappingURL=hermetic-env.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ManagedRunnerResolution, type ResolveRunnerOptions } from './resolve-runner.js';
|
|
2
|
+
export declare function getManagedRunner(tool: string, options?: ResolveRunnerOptions): ManagedRunnerResolution;
|
|
3
|
+
export declare function clearManagedRunnerCache(): void;
|
|
4
|
+
export type { ManagedRunnerResolution, ResolveRunnerOptions };
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|