@webpresso/agent-kit 3.1.30 → 3.3.0
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.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/README.md +53 -15
- package/bin/_run.js +183 -20
- package/catalog/AGENTS.md.tpl +3 -3
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +28 -1
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/verify/SKILL.md +4 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +4 -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.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +80 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +13 -1
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +101 -2
- package/dist/esm/blueprint/core/schema.d.ts +1 -0
- package/dist/esm/blueprint/core/schema.js +26 -0
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +87 -20
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +68 -12
- 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/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/sync/client.d.ts +2 -1
- package/dist/esm/blueprint/sync/client.js +1 -1
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/dossier.d.ts +1 -0
- package/dist/esm/blueprint/trust/dossier.js +40 -6
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +74 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +15 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +43 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +8 -1
- package/dist/esm/cli/commands/agent-launch.js +93 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +21 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +59 -40
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +13 -1
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +30 -6
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.js +2 -2
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +88 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +325 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +3 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- 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/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +20 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +61 -5
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -24
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +288 -202
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +22 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +230 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/opencode-probe.d.ts +107 -0
- package/dist/esm/cli/commands/opencode-probe.js +323 -0
- package/dist/esm/cli/commands/opencode-rotate.d.ts +72 -0
- package/dist/esm/cli/commands/opencode-rotate.js +147 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/quality-runner.js +3 -3
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +751 -209
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- 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/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- 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 +6 -0
- package/dist/esm/cli/commands/worktree/router-dispatch.js +126 -9
- package/dist/esm/cli/commands/worktree/router.js +10 -0
- package/dist/esm/cli/direct-provider-launch.js +9 -5
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +5 -3
- package/dist/esm/hooks/doctor.js +67 -16
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +54 -0
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +6 -1
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +50 -9
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +92 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +5 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +109 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +5 -1
- package/dist/esm/mcp/tools/audit.js +42 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -0
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-fetch-and-index.d.ts +1 -1
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- 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 +27 -14
- package/dist/esm/review/availability.d.ts +33 -0
- package/dist/esm/review/availability.js +122 -20
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.d.ts +11 -1
- package/dist/esm/review/execution/adapters.js +36 -10
- package/dist/esm/review/execution/artifacts.d.ts +1 -1
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +121 -21
- package/dist/esm/review/execution/supervisor.js +36 -4
- package/dist/esm/review/execution/types.d.ts +9 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +25 -0
- package/dist/esm/review/subject.js +116 -26
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- 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 +32 -25
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -2
- package/dist/esm/test-helpers/global-setup.js +79 -32
- package/dist/esm/typecheck/planner.d.ts +9 -1
- package/dist/esm/typecheck/planner.js +31 -0
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/orphan-scan.d.ts +50 -0
- package/dist/esm/worktrees/orphan-scan.js +211 -0
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -13
|
@@ -7,4 +7,10 @@ export interface VitestWorkerBudget {
|
|
|
7
7
|
readonly subprocess: number;
|
|
8
8
|
}
|
|
9
9
|
export declare function parsePositiveInteger(value: string | undefined): number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Allocate one worker ceiling across concurrently running Vitest projects.
|
|
12
|
+
* Standalone project runs keep `computeVitestWorkerBudget`; only the supervisor
|
|
13
|
+
* uses this aggregate contract.
|
|
14
|
+
*/
|
|
15
|
+
export declare function computeConcurrentVitestWorkerBudget(options?: VitestWorkerBudgetOptions): VitestWorkerBudget;
|
|
10
16
|
export declare function computeVitestWorkerBudget(options?: VitestWorkerBudgetOptions): VitestWorkerBudget;
|
|
@@ -9,12 +9,70 @@ function splitSharedBudget(totalWorkers) {
|
|
|
9
9
|
if (totalWorkers <= 2) {
|
|
10
10
|
return { unit: 1, subprocess: 1 };
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
// Even split. The lanes carry near-equal total work (measured 2026-07-23 on
|
|
13
|
+
// an 8-core host: unit ≈320 worker-seconds, subprocess ≈266 — see
|
|
14
|
+
// docs/ci-test-perf.md), so the previous 2/3-unit skew starved the
|
|
15
|
+
// subprocess lane: its makespan, not unit's, bounded the whole run
|
|
16
|
+
// (subprocess lane >296s at 2 workers vs 168s at 4 on the same host).
|
|
17
|
+
// CI is unaffected — it pins WP_VITEST_MAX_WORKERS=2, taking the early
|
|
18
|
+
// return above.
|
|
19
|
+
const unit = Math.max(1, Math.round(totalWorkers / 2));
|
|
13
20
|
return {
|
|
14
21
|
unit,
|
|
15
22
|
subprocess: Math.max(1, totalWorkers - unit),
|
|
16
23
|
};
|
|
17
24
|
}
|
|
25
|
+
function readWorkerOverride(env, name) {
|
|
26
|
+
const raw = env[name];
|
|
27
|
+
if (raw === undefined)
|
|
28
|
+
return;
|
|
29
|
+
const parsed = parsePositiveInteger(raw);
|
|
30
|
+
if (parsed === undefined) {
|
|
31
|
+
throw new Error(`${name} must be a positive integer, got ${JSON.stringify(raw)}`);
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Allocate one worker ceiling across concurrently running Vitest projects.
|
|
37
|
+
* Standalone project runs keep `computeVitestWorkerBudget`; only the supervisor
|
|
38
|
+
* uses this aggregate contract.
|
|
39
|
+
*/
|
|
40
|
+
export function computeConcurrentVitestWorkerBudget(options = {}) {
|
|
41
|
+
const env = options.env ?? process.env;
|
|
42
|
+
const sharedOverride = readWorkerOverride(env, "WP_VITEST_MAX_WORKERS");
|
|
43
|
+
const unitOverride = readWorkerOverride(env, "WP_VITEST_UNIT_MAX_WORKERS");
|
|
44
|
+
const subprocessOverride = readWorkerOverride(env, "WP_VITEST_SUBPROCESS_MAX_WORKERS");
|
|
45
|
+
if (sharedOverride === undefined &&
|
|
46
|
+
unitOverride !== undefined &&
|
|
47
|
+
subprocessOverride !== undefined) {
|
|
48
|
+
return { unit: unitOverride, subprocess: subprocessOverride };
|
|
49
|
+
}
|
|
50
|
+
const total = sharedOverride ?? (env.CI === "true" ? 2 : defaultSharedWorkers(options));
|
|
51
|
+
if (total < 2) {
|
|
52
|
+
throw new Error(`WP_VITEST_MAX_WORKERS must be at least 2 for concurrent projects, got ${total}`);
|
|
53
|
+
}
|
|
54
|
+
if (unitOverride !== undefined && subprocessOverride !== undefined) {
|
|
55
|
+
if (unitOverride + subprocessOverride > total) {
|
|
56
|
+
throw new Error(`Lane worker overrides (${unitOverride + subprocessOverride}) exceed aggregate worker budget ${total}`);
|
|
57
|
+
}
|
|
58
|
+
return { unit: unitOverride, subprocess: subprocessOverride };
|
|
59
|
+
}
|
|
60
|
+
if (unitOverride !== undefined) {
|
|
61
|
+
const subprocess = total - unitOverride;
|
|
62
|
+
if (subprocess < 1) {
|
|
63
|
+
throw new Error(`Unit worker override ${unitOverride} leaves no aggregate subprocess worker`);
|
|
64
|
+
}
|
|
65
|
+
return { unit: unitOverride, subprocess };
|
|
66
|
+
}
|
|
67
|
+
if (subprocessOverride !== undefined) {
|
|
68
|
+
const unit = total - subprocessOverride;
|
|
69
|
+
if (unit < 1) {
|
|
70
|
+
throw new Error(`Subprocess worker override ${subprocessOverride} leaves no aggregate unit worker`);
|
|
71
|
+
}
|
|
72
|
+
return { unit, subprocess: subprocessOverride };
|
|
73
|
+
}
|
|
74
|
+
return splitSharedBudget(total);
|
|
75
|
+
}
|
|
18
76
|
export function computeVitestWorkerBudget(options = {}) {
|
|
19
77
|
const env = options.env ?? process.env;
|
|
20
78
|
const sharedOverride = parsePositiveInteger(env.WP_VITEST_MAX_WORKERS);
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import type { TestProject } from "vitest/node";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function setup(project?: Pick<TestProject, "name">): void;
|
|
2
|
+
export declare function setup(project?: Pick<TestProject, "name">): Promise<void>;
|
|
@@ -1,44 +1,91 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import lockfile from "proper-lockfile";
|
|
4
5
|
import { syncSastRuleAssets } from "#build/sast-rule-assets.js";
|
|
6
|
+
import { runFormat } from "#format/index.js";
|
|
5
7
|
import { ensureHostRuntimeArtifactsForSerialTests } from "./runtime-binary-artifacts.js";
|
|
6
8
|
const REPO_ROOT = process.cwd();
|
|
7
9
|
const DIST_SENTINEL = join(REPO_ROOT, "dist", "esm", "index.js");
|
|
8
10
|
const MIGRATION_SENTINEL = join(REPO_ROOT, "dist", "esm", "blueprint", "db", "migrations", "0001_seed.sql");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
26
|
-
builtDist = true;
|
|
27
|
-
}
|
|
28
|
-
if (!existsSync(MIGRATION_SENTINEL)) {
|
|
29
|
-
execFileSync("bun", ["src/build/blueprint-migration-assets.ts"], {
|
|
30
|
-
cwd: REPO_ROOT,
|
|
31
|
-
encoding: "utf8",
|
|
32
|
-
env: { ...process.env, HUSKY: "0" },
|
|
33
|
-
});
|
|
11
|
+
// Lock target lives outside dist/ (tshy rewrites dist/) and under the always-present, gitignored
|
|
12
|
+
// node_modules/.cache so a concurrent test run cannot clobber it mid-build.
|
|
13
|
+
const BUILD_LOCK_TARGET = join(REPO_ROOT, "node_modules", ".cache", "wp-test-setup");
|
|
14
|
+
// Serialize the dist build across concurrent test runs sharing this checkout: tshy's non-atomic
|
|
15
|
+
// dist/package.json writes corrupt each other if two runs build at once. proper-lockfile gives a
|
|
16
|
+
// bounded wait plus native stale-owner recovery (a dead builder's lock is reclaimed after `stale`).
|
|
17
|
+
// The retry budget spans well past a cold build so a second run waits rather than failing closed.
|
|
18
|
+
async function withDistBuildLock(fn) {
|
|
19
|
+
mkdirSync(join(REPO_ROOT, "node_modules", ".cache"), { recursive: true });
|
|
20
|
+
const release = await lockfile.lock(BUILD_LOCK_TARGET, {
|
|
21
|
+
realpath: false,
|
|
22
|
+
stale: 120_000,
|
|
23
|
+
retries: { retries: 40, factor: 1.4, minTimeout: 200, maxTimeout: 5_000 },
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
34
27
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
cwd: REPO_ROOT,
|
|
38
|
-
encoding: "utf8",
|
|
39
|
-
env: { ...process.env, HUSKY: "0" },
|
|
40
|
-
});
|
|
28
|
+
finally {
|
|
29
|
+
await release();
|
|
41
30
|
}
|
|
31
|
+
}
|
|
32
|
+
// h-019: scripts/run-vitest-projects.ts spawns the "unit" and "subprocess" vitest projects as two
|
|
33
|
+
// independent, unsynchronized child processes (Promise.all) — the same exposure exists for any two
|
|
34
|
+
// `vitest run --project <name>` invocations that happen to share this checkout. tshy's package.json
|
|
35
|
+
// write below is non-atomic (truncate+write, not write-temp+rename): a project whose setup() returned
|
|
36
|
+
// without taking this lock could start reading package.json (module resolution, etc.) while a sibling
|
|
37
|
+
// project's process was mid-tshy-write, observing a truncated/empty file (JSONError "File is empty").
|
|
38
|
+
// Every project therefore passes through this SAME gate before its tests can start, whether or not
|
|
39
|
+
// that project needs dist output for itself: whichever process reaches the lock first performs the
|
|
40
|
+
// idempotent, re-checked-under-lock build; every other process blocks until that release, then finds
|
|
41
|
+
// the sentinels already satisfied and returns immediately. In the steady state (dist/ already built)
|
|
42
|
+
// this is a single existsSync check with no lock contention at all.
|
|
43
|
+
async function ensureDistPrepared() {
|
|
44
|
+
// Fast path: nothing to build. Only pay the lock when a build artifact is actually missing.
|
|
45
|
+
if (existsSync(DIST_SENTINEL) && existsSync(MIGRATION_SENTINEL))
|
|
46
|
+
return;
|
|
47
|
+
await withDistBuildLock(async () => {
|
|
48
|
+
let builtDist = false;
|
|
49
|
+
// Re-check under the lock: another run may have completed the build while we waited.
|
|
50
|
+
if (!existsSync(DIST_SENTINEL)) {
|
|
51
|
+
execFileSync("./node_modules/.bin/tshy", [], {
|
|
52
|
+
cwd: REPO_ROOT,
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
env: { ...process.env, HUSKY: "0" },
|
|
55
|
+
});
|
|
56
|
+
builtDist = true;
|
|
57
|
+
// tshy rewrites package.json's exports with a trailing newline the repo formatter forbids
|
|
58
|
+
// (`.editorconfig [*.json] insert_final_newline = false`), which would leave a clean worktree
|
|
59
|
+
// dirty. The real `build` script (package.json `"build"`) restores it with `wp format --file
|
|
60
|
+
// package.json`; mirror that here in-process (runFormat spawns only `vp fmt`, avoiding a second
|
|
61
|
+
// cold CLI launch) so `wp test` never dirties package.json. See package-scripts.test.ts.
|
|
62
|
+
// Fail loudly if the restore does not succeed — a silent failure would ship a dirty package.json.
|
|
63
|
+
const restored = await runFormat({ files: ["package.json"], cwd: REPO_ROOT });
|
|
64
|
+
if (!restored.passed) {
|
|
65
|
+
throw new Error(`global-setup: could not restore package.json formatting after tshy (exit ${restored.exitCode})` +
|
|
66
|
+
`${restored.spawnError ? `: ${restored.spawnError}` : ""}\n${restored.output}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!existsSync(MIGRATION_SENTINEL)) {
|
|
70
|
+
execFileSync("bun", ["src/build/blueprint-migration-assets.ts"], {
|
|
71
|
+
cwd: REPO_ROOT,
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
env: { ...process.env, HUSKY: "0" },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (builtDist) {
|
|
77
|
+
execFileSync("bun", ["src/build/normalize-tsconfig-json-exports.ts"], {
|
|
78
|
+
cwd: REPO_ROOT,
|
|
79
|
+
encoding: "utf8",
|
|
80
|
+
env: { ...process.env, HUSKY: "0" },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Vitest passes the selected TestProject to globalSetup, once per project process.
|
|
86
|
+
export async function setup(project) {
|
|
87
|
+
syncSastRuleAssets(REPO_ROOT);
|
|
88
|
+
await ensureDistPrepared();
|
|
42
89
|
if (project?.name === "serial-subprocess") {
|
|
43
90
|
ensureHostRuntimeArtifactsForSerialTests({ rootDir: REPO_ROOT });
|
|
44
91
|
}
|
|
@@ -13,7 +13,7 @@ export interface PlannedTypecheckCommand {
|
|
|
13
13
|
}
|
|
14
14
|
export interface TypecheckExecutionPlan {
|
|
15
15
|
readonly repoRoot: string;
|
|
16
|
-
readonly mode: "default" | "files" | "packages";
|
|
16
|
+
readonly mode: "default" | "files" | "packages" | "tests";
|
|
17
17
|
readonly commands: readonly PlannedTypecheckCommand[];
|
|
18
18
|
readonly resolvedScopes: readonly TypecheckScope[];
|
|
19
19
|
readonly preambleLine?: string;
|
|
@@ -24,6 +24,14 @@ export interface PlanTypecheckExecutionOptions {
|
|
|
24
24
|
readonly files?: readonly string[];
|
|
25
25
|
readonly packages?: readonly string[];
|
|
26
26
|
readonly pretty?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Run the separate test-surface typecheck lane (`tsconfig.test.json`)
|
|
29
|
+
* instead of the default production-source lane. Mutually exclusive with
|
|
30
|
+
* `files`/`packages`. This lane is invoked via `wp typecheck --tests` and is
|
|
31
|
+
* it is not part of the default `wp typecheck` gate or `ci-preflight`
|
|
32
|
+
* until the test-surface error corpus is burned down to zero.
|
|
33
|
+
*/
|
|
34
|
+
readonly tests?: boolean;
|
|
27
35
|
}
|
|
28
36
|
export declare function planTypecheckExecution(options: PlanTypecheckExecutionOptions): TypecheckExecutionPlan;
|
|
29
37
|
export declare function formatResolvedScopesLine(scopes: readonly TypecheckScope[]): string;
|
|
@@ -3,6 +3,7 @@ import { isAbsolute, join, relative, resolve } from "node:path";
|
|
|
3
3
|
import { getPackageScript, readPackageJson, isRecursiveWpScript } from "#cli/package-scripts.js";
|
|
4
4
|
import { getWorkspacePackages } from "#quality-engine/target-resolver.js";
|
|
5
5
|
import { getManagedRunner, resolveEffectiveRunnerRoot } from "#tool-runtime";
|
|
6
|
+
const TEST_TSCONFIG_FILENAME = "tsconfig.test.json";
|
|
6
7
|
const GENERATED_ROOT_PREFIXES = [".webpresso/generated"];
|
|
7
8
|
const OUTPUT_SEGMENTS = new Set(["coverage", "dist", "node_modules"]);
|
|
8
9
|
export function planTypecheckExecution(options) {
|
|
@@ -14,6 +15,17 @@ export function planTypecheckExecution(options) {
|
|
|
14
15
|
if (files.length > 0 && packages.length > 0) {
|
|
15
16
|
throw new Error("Cannot use both --file and --package for typecheck targeting.");
|
|
16
17
|
}
|
|
18
|
+
if (options.tests) {
|
|
19
|
+
if (files.length > 0 || packages.length > 0) {
|
|
20
|
+
throw new Error("Cannot use --tests together with --file or --package.");
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
repoRoot,
|
|
24
|
+
mode: "tests",
|
|
25
|
+
commands: [buildTestsCommand(defaultScopeRoot, options.pretty)],
|
|
26
|
+
resolvedScopes: [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
17
29
|
if (packages.length > 0) {
|
|
18
30
|
const scopes = resolvePackageScopes(repoRoot, realRepoRoot, packages);
|
|
19
31
|
return {
|
|
@@ -63,6 +75,25 @@ function buildUntargetedCommand(cwd, pretty = false) {
|
|
|
63
75
|
cwd,
|
|
64
76
|
};
|
|
65
77
|
}
|
|
78
|
+
function buildTestsCommand(cwd, pretty = false) {
|
|
79
|
+
const testTsconfigPath = join(cwd, TEST_TSCONFIG_FILENAME);
|
|
80
|
+
if (!existsSync(testTsconfigPath)) {
|
|
81
|
+
throw new Error(`--tests requires ${TEST_TSCONFIG_FILENAME} at the repo root (not found at ${testTsconfigPath}).`);
|
|
82
|
+
}
|
|
83
|
+
const resolution = getManagedRunner("tsc");
|
|
84
|
+
return {
|
|
85
|
+
scope: null,
|
|
86
|
+
command: resolution.command,
|
|
87
|
+
args: [
|
|
88
|
+
...resolution.args,
|
|
89
|
+
"--noEmit",
|
|
90
|
+
"-p",
|
|
91
|
+
TEST_TSCONFIG_FILENAME,
|
|
92
|
+
...(pretty ? [] : ["--pretty", "false"]),
|
|
93
|
+
],
|
|
94
|
+
cwd,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
66
97
|
function buildTargetedCommand(scope, pretty = false) {
|
|
67
98
|
const checkTypesScript = getPackageScript(scope.root, "check-types");
|
|
68
99
|
if (checkTypesScript && !isRecursiveWpScript(checkTypesScript, "typecheck")) {
|
|
@@ -70,6 +70,13 @@ export declare function decideUltragoalRun(input: {
|
|
|
70
70
|
readonly currentSourceSnapshot?: string;
|
|
71
71
|
readonly now?: Date;
|
|
72
72
|
}): UltragoalRunDecision;
|
|
73
|
+
/**
|
|
74
|
+
* Preflight every blueprint's document shape BEFORE a durable state
|
|
75
|
+
* transition. Ledger callbacks run after state is committed, so a flat
|
|
76
|
+
* blueprint must be rejected here or the command throws with state already
|
|
77
|
+
* written. Missing blueprints are tolerated exactly like the ledger append.
|
|
78
|
+
*/
|
|
79
|
+
export declare function assertUltragoalBlueprintShapes(cwd: string, slugs: readonly string[]): void;
|
|
73
80
|
export declare function createUltragoal(input: {
|
|
74
81
|
readonly cwd?: string;
|
|
75
82
|
readonly brief: string;
|
|
@@ -7,6 +7,7 @@ import { BlueprintCreationService } from "#service/BlueprintCreationService.js";
|
|
|
7
7
|
import { BlueprintService } from "#service/BlueprintService.js";
|
|
8
8
|
import { scanBlueprintDirectory } from "#service/scanner.js";
|
|
9
9
|
import { resolveBlueprintRoot } from "#utils/blueprint-root.js";
|
|
10
|
+
import { assertFolderBlueprintIdentity, resolveBlueprintDocumentIdentity, } from "#utils/document-paths.js";
|
|
10
11
|
import { assertUltragoalEnabled, assertUltragoalMutationAllowed } from "./guards.js";
|
|
11
12
|
import { evaluateUltragoalCompletion, hasRepeatedQaError, MAX_STOP_STREAK, MAX_VALIDATION_ROUNDS, } from "./completion.js";
|
|
12
13
|
import { formatUltragoalHandoff } from "./handoff.js";
|
|
@@ -222,14 +223,43 @@ export function decideUltragoalRun(input) {
|
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
function findBlueprint(cwd, slug) {
|
|
226
|
+
const baseDir = resolveBlueprintRoot(cwd);
|
|
225
227
|
const entries = scanBlueprintDirectory({
|
|
226
|
-
baseDir
|
|
228
|
+
baseDir,
|
|
227
229
|
includeSpecialFolders: true,
|
|
228
230
|
});
|
|
229
231
|
const found = entries.find((entry) => entry.slug === slug || entry.slug.endsWith(`/${slug}`));
|
|
230
232
|
if (!found)
|
|
231
233
|
throw new Error(`Blueprint not found: ${slug}`);
|
|
232
|
-
|
|
234
|
+
const identity = resolveBlueprintDocumentIdentity(baseDir, found.path);
|
|
235
|
+
if (!identity) {
|
|
236
|
+
throw new Error(`Blueprint ${found.slug} resolved to an invalid document path: ${found.path}`);
|
|
237
|
+
}
|
|
238
|
+
return { path: found.path, shape: identity.shape, slug: found.slug };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Preflight every blueprint's document shape BEFORE a durable state
|
|
242
|
+
* transition. Ledger callbacks run after state is committed, so a flat
|
|
243
|
+
* blueprint must be rejected here or the command throws with state already
|
|
244
|
+
* written. Missing blueprints are tolerated exactly like the ledger append.
|
|
245
|
+
*/
|
|
246
|
+
export function assertUltragoalBlueprintShapes(cwd, slugs) {
|
|
247
|
+
for (const slug of slugs) {
|
|
248
|
+
let found;
|
|
249
|
+
try {
|
|
250
|
+
found = findBlueprint(cwd, slug);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (error instanceof Error && error.message === `Blueprint not found: ${slug}`)
|
|
254
|
+
continue;
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
assertFolderBlueprintIdentity({
|
|
258
|
+
documentPath: found.path,
|
|
259
|
+
lifecycleSlug: found.slug,
|
|
260
|
+
shape: found.shape,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
233
263
|
}
|
|
234
264
|
function appendUltragoalLedgerEvent(cwd, slug, state, event) {
|
|
235
265
|
let found;
|
|
@@ -241,6 +271,11 @@ function appendUltragoalLedgerEvent(cwd, slug, state, event) {
|
|
|
241
271
|
return;
|
|
242
272
|
throw error;
|
|
243
273
|
}
|
|
274
|
+
assertFolderBlueprintIdentity({
|
|
275
|
+
documentPath: found.path,
|
|
276
|
+
lifecycleSlug: found.slug,
|
|
277
|
+
shape: found.shape,
|
|
278
|
+
});
|
|
244
279
|
const reviewsPath = join(dirname(found.path), "reviews.md");
|
|
245
280
|
mkdirSync(dirname(reviewsPath), { recursive: true });
|
|
246
281
|
const prefix = existsSync(reviewsPath) ? "\n" : "# Blueprint reviews\n\n";
|
|
@@ -281,6 +316,7 @@ export async function createUltragoal(input) {
|
|
|
281
316
|
sessionId: input.sessionId ?? process.env.WP_SESSION_ID ?? randomUUID(),
|
|
282
317
|
ultragoalId: input.ultragoalId ?? `ultragoal-${randomUUID()}`,
|
|
283
318
|
});
|
|
319
|
+
assertUltragoalBlueprintShapes(cwd, blueprintSlugs);
|
|
284
320
|
await initializeUltragoalState(state, cwd, (written) => {
|
|
285
321
|
for (const slug of blueprintSlugs)
|
|
286
322
|
appendUltragoalLedgerEvent(cwd, slug, written, "created");
|
|
@@ -337,6 +373,9 @@ export async function getUltragoalHandoff(cwd = process.cwd()) {
|
|
|
337
373
|
}
|
|
338
374
|
export async function cancelUltragoal(cwd = process.cwd()) {
|
|
339
375
|
assertUltragoalMutationAllowed(cwd);
|
|
376
|
+
const current = await readUltragoalState(cwd);
|
|
377
|
+
if (current)
|
|
378
|
+
assertUltragoalBlueprintShapes(cwd, current.blueprintSlugs);
|
|
340
379
|
const next = await updateUltragoalState((state) => transitionUltragoalState(state, "cancelled"), cwd, (written) => {
|
|
341
380
|
for (const slug of written.blueprintSlugs) {
|
|
342
381
|
appendUltragoalLedgerEvent(cwd, slug, written, "cancelled");
|
|
@@ -394,6 +433,7 @@ export async function runCurrentUltragoal(cwd = process.cwd(), deps = {}) {
|
|
|
394
433
|
now: deps.now?.() ?? new Date(),
|
|
395
434
|
});
|
|
396
435
|
if (committedDecision.state !== status.state) {
|
|
436
|
+
assertUltragoalBlueprintShapes(cwd, committedDecision.state.blueprintSlugs);
|
|
397
437
|
await updateUltragoalState((current) => {
|
|
398
438
|
if (current.ultragoalId !== status.state?.ultragoalId ||
|
|
399
439
|
current.updatedAt !== status.state.updatedAt ||
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, copyFileSync, existsSync, fdatasyncSync, fsyncSync, openSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
1
|
+
import { closeSync, copyFileSync, existsSync, fdatasyncSync, fchmodSync, fsyncSync, openSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
export function writeJsonFile(path, data, options = {}) {
|
|
@@ -45,6 +45,8 @@ function writeFileAtomicWithPolicy(path, content, options, allowCrossDeviceFallb
|
|
|
45
45
|
let installed = false;
|
|
46
46
|
try {
|
|
47
47
|
fd = openSync(tmpPath, "w", normalized.mode);
|
|
48
|
+
if (normalized.mode !== undefined)
|
|
49
|
+
fchmodSync(fd, normalized.mode);
|
|
48
50
|
writeFileSync(fd, content, normalized.writeOptions);
|
|
49
51
|
fdatasyncSync(fd);
|
|
50
52
|
closeSync(fd);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface RemoveCodexProjectTrustOptions {
|
|
2
|
+
readonly configFilePath?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Remove the exact Codex project-trust table for a worktree that has just been
|
|
6
|
+
* removed. This deliberately does not touch nested hook state or other project
|
|
7
|
+
* entries, which may remain valid user-owned configuration.
|
|
8
|
+
*/
|
|
9
|
+
export declare function removeCodexProjectTrust(worktreePath: string, options?: RemoveCodexProjectTrustOptions): Promise<"removed" | "unchanged">;
|
|
10
|
+
/**
|
|
11
|
+
* Remove exact Codex project-trust tables in one locked rewrite. Prune can
|
|
12
|
+
* reconcile several externally deleted worktrees at once, so batching avoids
|
|
13
|
+
* taking the global config lock and rewriting the file for every entry.
|
|
14
|
+
*/
|
|
15
|
+
export declare function removeCodexProjectTrusts(worktreePaths: readonly string[], options?: RemoveCodexProjectTrustOptions): Promise<"removed" | "unchanged">;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import lockfile from "proper-lockfile";
|
|
3
|
+
import { defaultCodexConfigFilePath } from "#cli/commands/init/scaffolders/agent-hooks/codex-trust-sync.js";
|
|
4
|
+
import { writeFileAtomic } from "#shared-utils/write-json-file.js";
|
|
5
|
+
/**
|
|
6
|
+
* Remove the exact Codex project-trust table for a worktree that has just been
|
|
7
|
+
* removed. This deliberately does not touch nested hook state or other project
|
|
8
|
+
* entries, which may remain valid user-owned configuration.
|
|
9
|
+
*/
|
|
10
|
+
export function removeCodexProjectTrust(worktreePath, options = {}) {
|
|
11
|
+
return removeCodexProjectTrusts([worktreePath], options);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Remove exact Codex project-trust tables in one locked rewrite. Prune can
|
|
15
|
+
* reconcile several externally deleted worktrees at once, so batching avoids
|
|
16
|
+
* taking the global config lock and rewriting the file for every entry.
|
|
17
|
+
*/
|
|
18
|
+
export async function removeCodexProjectTrusts(worktreePaths, options = {}) {
|
|
19
|
+
if (worktreePaths.length === 0)
|
|
20
|
+
return "unchanged";
|
|
21
|
+
const configFilePath = options.configFilePath ?? defaultCodexConfigFilePath();
|
|
22
|
+
if (!existsSync(configFilePath))
|
|
23
|
+
return "unchanged";
|
|
24
|
+
const release = await lockfile.lock(configFilePath, {
|
|
25
|
+
realpath: false,
|
|
26
|
+
retries: { retries: 8, factor: 1.5, minTimeout: 25, maxTimeout: 250 },
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
const existing = readFileSync(configFilePath, "utf8");
|
|
30
|
+
const next = removeProjectTrustTables(existing, worktreePaths);
|
|
31
|
+
if (next === existing)
|
|
32
|
+
return "unchanged";
|
|
33
|
+
writeFileAtomic(configFilePath, next, "utf8");
|
|
34
|
+
return "removed";
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await release();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function removeProjectTrustTables(config, worktreePaths) {
|
|
41
|
+
const lines = config.split("\n");
|
|
42
|
+
const headers = new Set(worktreePaths.map((worktreePath) => `[projects.${JSON.stringify(worktreePath)}]`));
|
|
43
|
+
let removed = false;
|
|
44
|
+
for (let start = 0; start < lines.length;) {
|
|
45
|
+
if (!headers.has(lines[start]?.trim() ?? "")) {
|
|
46
|
+
start += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let end = start + 1;
|
|
50
|
+
while (end < lines.length && !/^\s*\[.+\]\s*$/u.test(lines[end] ?? ""))
|
|
51
|
+
end += 1;
|
|
52
|
+
lines.splice(start, end - start);
|
|
53
|
+
removed = true;
|
|
54
|
+
}
|
|
55
|
+
return removed ? lines.join("\n") : config;
|
|
56
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type WorktreeInventory } from "./identity.js";
|
|
2
|
+
export type OrphanClassification = "alive" | "orphan" | "broken" | "unrecognized";
|
|
3
|
+
export interface OrphanScanCandidate {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly repoNamespace: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OrphanScanRow extends OrphanScanCandidate {
|
|
8
|
+
readonly classification: OrphanClassification;
|
|
9
|
+
readonly dirty: boolean | "unknown";
|
|
10
|
+
/**
|
|
11
|
+
* Recursive size, computed ONLY for the final `orphan` classification.
|
|
12
|
+
* `alive` worktrees are actively worked on (often large `node_modules`
|
|
13
|
+
* trees) and are never removable, and `broken`/`unrecognized` rows need
|
|
14
|
+
* manual review regardless of size, so walking their full tree would burn
|
|
15
|
+
* significant time for no decision-relevant signal. `"unknown"` also
|
|
16
|
+
* covers a measurement failure/timeout on an orphan candidate.
|
|
17
|
+
*/
|
|
18
|
+
readonly sizeBytes: number | "unknown";
|
|
19
|
+
readonly removable: boolean;
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
}
|
|
22
|
+
export interface OrphanClassifyDeps {
|
|
23
|
+
readonly resolveRepoRoot: (candidate: OrphanScanCandidate) => string | undefined;
|
|
24
|
+
readonly repoRootExists: (repoRoot: string) => boolean;
|
|
25
|
+
readonly buildInventory: (repoRoot: string) => WorktreeInventory;
|
|
26
|
+
readonly probeDirty: (path: string) => boolean | "unknown";
|
|
27
|
+
readonly measureSize: (path: string) => number;
|
|
28
|
+
}
|
|
29
|
+
/** Classify a single on-disk candidate. Pure aside from the injected deps. */
|
|
30
|
+
export declare function classifyOrphanCandidate(candidate: OrphanScanCandidate, deps: OrphanClassifyDeps): OrphanScanRow;
|
|
31
|
+
/** Namespace-keyed lookup of a known-live `repoRoot`, first-registered-wins. */
|
|
32
|
+
export declare function buildRegistryRepoRootMap(entries: readonly {
|
|
33
|
+
readonly repoNamespace: string;
|
|
34
|
+
readonly repoRoot: string;
|
|
35
|
+
}[]): ReadonlyMap<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Fallback primary-repo resolver for candidates the registry never recorded:
|
|
38
|
+
* a linked worktree's `.git` is a file containing `gitdir: <repoRoot>/.git/
|
|
39
|
+
* worktrees/<name>`. Returns `undefined` when unreadable or unparseable
|
|
40
|
+
* (e.g. `.git` is a directory, or the pointer target was already reaped).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveRepoRootFromGitPointer(path: string, readGitPointer: (gitPath: string) => string | undefined): string | undefined;
|
|
43
|
+
export interface DiscoverOrphanCandidatesOptions {
|
|
44
|
+
readonly repoNamespaceFilter?: string;
|
|
45
|
+
readonly dirExists?: (path: string) => boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Walk every known managed-worktree root and collect worktree-shaped leaf dirs. */
|
|
48
|
+
export declare function discoverOrphanScanCandidates(roots: readonly string[], options?: DiscoverOrphanCandidatesOptions): OrphanScanCandidate[];
|
|
49
|
+
/** Bounded recursive directory size, symlinks excluded, unreadable entries skipped. */
|
|
50
|
+
export declare function measureDirectorySizeBytes(path: string): number;
|