clinkx 1.0.0 → 1.1.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.
- package/CHANGELOG.md +86 -0
- package/README.md +72 -0
- package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
- package/clinkx-workflows/dist/chairman-knobs.js +27 -0
- package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +10 -0
- package/clinkx-workflows/dist/clink-client.js +39 -1
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +29 -1
- package/clinkx-workflows/dist/config.js +35 -2
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/context-preflight.d.ts +5 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -1
- package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
- package/clinkx-workflows/dist/council-execution-policy.js +110 -0
- package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
- package/clinkx-workflows/dist/council-expander.d.ts +9 -8
- package/clinkx-workflows/dist/council-expander.js +18 -9
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5557 -4
- package/clinkx-workflows/dist/dashboard/activity-contract.js +55 -3
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +9 -5
- package/clinkx-workflows/dist/dashboard/activity-reader.js +15 -2
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +133 -25
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +36 -2
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +142 -19
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +20027 -480
- package/clinkx-workflows/dist/dashboard/contracts.js +133 -6
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/index.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/index.js +4 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/lazy-content.js +53 -2
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +147 -4
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +11 -11
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +4 -56
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +2 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +6 -5
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +4 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +52 -8
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/server-factory.js +73 -21
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +223 -56
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +26 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +10 -3
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +9 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +793 -59
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
- package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
- package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
- package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
- package/clinkx-workflows/dist/delegate-signature.js +14 -0
- package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
- package/clinkx-workflows/dist/delegate-validate.d.ts +32 -2
- package/clinkx-workflows/dist/delegate-validate.js +51 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -1
- package/clinkx-workflows/dist/engine.d.ts +113 -3
- package/clinkx-workflows/dist/engine.js +476 -80
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +22 -2
- package/clinkx-workflows/dist/errors.js +30 -2
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fidelity.d.ts +47 -1
- package/clinkx-workflows/dist/fidelity.js +54 -1
- package/clinkx-workflows/dist/fidelity.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +24 -6
- package/clinkx-workflows/dist/fingerprint.js +32 -9
- package/clinkx-workflows/dist/fingerprint.js.map +1 -1
- package/clinkx-workflows/dist/index.js +53 -1
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/logger.d.ts +1 -1
- package/clinkx-workflows/dist/logger.js +17 -3
- package/clinkx-workflows/dist/logger.js.map +1 -1
- package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
- package/clinkx-workflows/dist/monotonic-clock.js +22 -0
- package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +52 -52
- package/clinkx-workflows/dist/plan-review/publication.d.ts +4 -4
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +16 -16
- package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
- package/clinkx-workflows/dist/roster-contract.js +180 -0
- package/clinkx-workflows/dist/roster-contract.js.map +1 -0
- package/clinkx-workflows/dist/rosters.d.ts +14 -0
- package/clinkx-workflows/dist/rosters.js +169 -0
- package/clinkx-workflows/dist/rosters.js.map +1 -0
- package/clinkx-workflows/dist/run-health.d.ts +7 -0
- package/clinkx-workflows/dist/run-health.js +25 -0
- package/clinkx-workflows/dist/run-health.js.map +1 -0
- package/clinkx-workflows/dist/schema.d.ts +64 -0
- package/clinkx-workflows/dist/schema.js +24 -0
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/server.js +2 -0
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +15 -2
- package/clinkx-workflows/dist/shutdown.js +60 -16
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +23 -0
- package/clinkx-workflows/dist/state.d.ts +5 -0
- package/clinkx-workflows/dist/state.js +128 -21
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
- package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
- package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
- package/clinkx-workflows/dist/types.d.ts +6 -0
- package/clinkx-workflows/dist/validators/council.d.ts +50 -50
- package/clinkx-workflows/dist/validators/plan-review.d.ts +3 -3
- package/clinkx-workflows/dist/workflow-tools.d.ts +70 -0
- package/clinkx-workflows/dist/workflow-tools.js +760 -168
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +3 -0
- package/clinkx-workflows/templates/council/chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-debugger.txt +3 -0
- package/clinkx-workflows/templates/council/discover-analyst.txt +3 -0
- package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/member.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/reviewer.txt +3 -0
- package/clinkx-workflows/workflows/council-answer.yaml +3 -0
- package/clinkx-workflows/workflows/council-code-review.yaml +3 -0
- package/clinkx-workflows/workflows/council-debug.yaml +3 -0
- package/clinkx-workflows/workflows/council-default.yaml +7 -0
- package/clinkx-workflows/workflows/council-discover.yaml +3 -0
- package/clinkx-workflows/workflows/council-plan-review.yaml +3 -0
- package/conf/adapters/agy.json +9 -2
- package/conf/adapters/claude.json +7 -0
- package/conf/adapters/codex.json +7 -0
- package/conf/adapters/glm.json +19 -3
- package/conf/adapters/grok.json +9 -2
- package/conf/adapters/hapi/agy.json +9 -2
- package/conf/adapters/hapi/claude.json +7 -0
- package/conf/adapters/hapi/codex.json +7 -0
- package/conf/adapters/hapi/glm.json +20 -4
- package/conf/rosters/default-council.json +21 -0
- package/dist/activity-journal.d.ts +17 -3
- package/dist/activity-journal.js +404 -79
- package/dist/activity-journal.js.map +1 -1
- package/dist/codex/agent-tools.js +36 -0
- package/dist/codex/agent-tools.js.map +1 -1
- package/dist/codex/app-server-client.d.ts +3 -0
- package/dist/codex/app-server-client.js +11 -0
- package/dist/codex/app-server-client.js.map +1 -1
- package/dist/codex/app-server-registry.d.ts +18 -0
- package/dist/codex/app-server-registry.js +88 -2
- package/dist/codex/app-server-registry.js.map +1 -1
- package/dist/codex/client-types.d.ts +7 -1
- package/dist/codex/protocol.d.ts +4 -4
- package/dist/config.d.ts +220 -23
- package/dist/config.js +104 -1
- package/dist/config.js.map +1 -1
- package/dist/context-builder/bundle.d.ts +13 -0
- package/dist/context-builder/bundle.js +46 -0
- package/dist/context-builder/bundle.js.map +1 -1
- package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
- package/dist/context-builder/delegate-finalizer-effective.js +82 -0
- package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +18 -0
- package/dist/context-builder/delegate-packet.js +73 -9
- package/dist/context-builder/delegate-packet.js.map +1 -1
- package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
- package/dist/context-builder/delegate-reconstruction.js +110 -41
- package/dist/context-builder/delegate-reconstruction.js.map +1 -1
- package/dist/context-builder/delegate-validation.d.ts +31 -0
- package/dist/context-builder/delegate-validation.js +87 -10
- package/dist/context-builder/delegate-validation.js.map +1 -1
- package/dist/context-builder/delegate.d.ts +9 -0
- package/dist/context-builder/delegate.js +210 -59
- package/dist/context-builder/delegate.js.map +1 -1
- package/dist/context-builder/diagnostics.d.ts +1 -1
- package/dist/context-builder/diagnostics.js.map +1 -1
- package/dist/context-builder/discovery-progress.d.ts +25 -0
- package/dist/context-builder/discovery-progress.js +64 -0
- package/dist/context-builder/discovery-progress.js.map +1 -0
- package/dist/context-builder/discovery-watchdog.d.ts +15 -0
- package/dist/context-builder/discovery-watchdog.js +39 -0
- package/dist/context-builder/discovery-watchdog.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +7 -0
- package/dist/context-builder/discovery.js +119 -7
- package/dist/context-builder/discovery.js.map +1 -1
- package/dist/context-builder/external-gaps.d.ts +73 -0
- package/dist/context-builder/external-gaps.js +248 -0
- package/dist/context-builder/external-gaps.js.map +1 -0
- package/dist/context-builder/globs.d.ts +1 -0
- package/dist/context-builder/globs.js +15 -0
- package/dist/context-builder/globs.js.map +1 -1
- package/dist/context-builder/handler.d.ts +42 -1
- package/dist/context-builder/handler.js +690 -62
- package/dist/context-builder/handler.js.map +1 -1
- package/dist/context-builder/invocation-sidecar.d.ts +41 -0
- package/dist/context-builder/invocation-sidecar.js +189 -0
- package/dist/context-builder/invocation-sidecar.js.map +1 -0
- package/dist/context-builder/lifecycle-contract.d.ts +51 -51
- package/dist/context-builder/mutation-lock.d.ts +32 -0
- package/dist/context-builder/mutation-lock.js +321 -0
- package/dist/context-builder/mutation-lock.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +32 -0
- package/dist/context-builder/review-coverage.js +130 -23
- package/dist/context-builder/review-coverage.js.map +1 -1
- package/dist/context-builder/schema.d.ts +76 -1
- package/dist/context-builder/schema.js +21 -1
- package/dist/context-builder/schema.js.map +1 -1
- package/dist/context-builder/secrets.d.ts +31 -0
- package/dist/context-builder/secrets.js +865 -83
- package/dist/context-builder/secrets.js.map +1 -1
- package/dist/context-builder/status.d.ts +1 -1
- package/dist/context-builder/status.js +1 -0
- package/dist/context-builder/status.js.map +1 -1
- package/dist/context-builder/storage.d.ts +29 -5
- package/dist/context-builder/storage.js +311 -236
- package/dist/context-builder/storage.js.map +1 -1
- package/dist/context-builder/versions.d.ts +19 -4
- package/dist/context-builder/versions.js +19 -4
- package/dist/context-builder/versions.js.map +1 -1
- package/dist/context-builder/workflow-runner.d.ts +6 -0
- package/dist/context-builder/workflow-runner.js +363 -173
- package/dist/context-builder/workflow-runner.js.map +1 -1
- package/dist/dashboard-adapter-catalog.js +7 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -1
- package/dist/dashboard-cli.d.ts +2 -0
- package/dist/dashboard-cli.js +40 -2
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-companion/client.d.ts +1 -0
- package/dist/dashboard-companion/client.js +22 -3
- package/dist/dashboard-companion/client.js.map +1 -1
- package/dist/dashboard-companion/protocol.js +4 -1
- package/dist/dashboard-companion/protocol.js.map +1 -1
- package/dist/dashboard-gateway/index.d.ts +1 -0
- package/dist/dashboard-gateway/index.js +33 -2
- package/dist/dashboard-gateway/index.js.map +1 -1
- package/dist/handler.js +5 -1
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +145 -7
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +18 -3
- package/dist/logger.js.map +1 -1
- package/dist/parsers/agy-stream-json.d.ts +14 -0
- package/dist/parsers/agy-stream-json.js +172 -0
- package/dist/parsers/agy-stream-json.js.map +1 -0
- package/dist/parsers/extract.js +5 -1
- package/dist/parsers/extract.js.map +1 -1
- package/dist/parsers/observed-invocation.d.ts +20 -0
- package/dist/parsers/observed-invocation.js +42 -0
- package/dist/parsers/observed-invocation.js.map +1 -0
- package/dist/pipeline.d.ts +13 -0
- package/dist/pipeline.js +60 -10
- package/dist/pipeline.js.map +1 -1
- package/dist/progress-report/lock.d.ts +74 -0
- package/dist/progress-report/lock.js +343 -0
- package/dist/progress-report/lock.js.map +1 -0
- package/dist/progress-report/schema.d.ts +808 -0
- package/dist/progress-report/schema.js +119 -0
- package/dist/progress-report/schema.js.map +1 -0
- package/dist/progress-report/store.d.ts +66 -0
- package/dist/progress-report/store.js +766 -0
- package/dist/progress-report/store.js.map +1 -0
- package/dist/progress-report/tools.d.ts +20 -0
- package/dist/progress-report/tools.js +141 -0
- package/dist/progress-report/tools.js.map +1 -0
- package/dist/prompt-mode.d.ts +1 -1
- package/dist/prompt-mode.js +7 -2
- package/dist/prompt-mode.js.map +1 -1
- package/dist/runner.d.ts +7 -0
- package/dist/runner.js +10 -3
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +50 -0
- package/dist/safety.js +182 -1
- package/dist/safety.js.map +1 -1
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +20 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +6 -1
- package/dist/server.js +94 -8
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +1 -1
- package/dist/session-log-stream.js +280 -33
- package/dist/session-log-stream.js.map +1 -1
- package/dist/stdio-activity.d.ts +28 -0
- package/dist/stdio-activity.js +72 -0
- package/dist/stdio-activity.js.map +1 -0
- package/package.json +2 -1
- package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +0 -25
- package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +0 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { lstat as fsLstat, open as fsOpen, rm as fsRm, } from "node:fs/promises";
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { ExecutionError } from "../errors.js";
|
|
5
|
+
import { validateCallerRunId } from "./run-id.js";
|
|
6
|
+
const CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX = ".mutation.lock";
|
|
7
|
+
const CONTEXT_BUILDER_MUTATION_LOCK_RECOVERY_SUFFIX = ".recovery";
|
|
8
|
+
const CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS = 1_000;
|
|
9
|
+
const CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS = 10;
|
|
10
|
+
const CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS = 10 * 60 * 1_000;
|
|
11
|
+
/**
|
|
12
|
+
* Serialize cooperative mutations of one context_builder run. The lock lives
|
|
13
|
+
* beside the run directory so it also survives removal of that directory.
|
|
14
|
+
*/
|
|
15
|
+
export async function withContextBuilderMutationLock(target, ops, callback, options = {}) {
|
|
16
|
+
const lockPath = mutationLockPathForTarget(target);
|
|
17
|
+
const acquired = await acquireContextBuilderMutationLock(lockPath, ops, options);
|
|
18
|
+
try {
|
|
19
|
+
return await callback();
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
await removeMutationLockIfCurrent(lockPath, acquired.fingerprint, ops);
|
|
23
|
+
await acquired.handle.close().catch(() => { });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function acquireContextBuilderMutationLock(lockPath, ops, options) {
|
|
27
|
+
const open = ops.open ?? fsOpen;
|
|
28
|
+
const maxAttempts = resolveMutationLockAttempts(options.maxAttempts);
|
|
29
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
30
|
+
try {
|
|
31
|
+
const handle = await open(lockPath, "wx", 0o600);
|
|
32
|
+
try {
|
|
33
|
+
await writeMutationLockMetadata(handle, options.ownerNonce);
|
|
34
|
+
return {
|
|
35
|
+
handle,
|
|
36
|
+
fingerprint: await fingerprintOpenHandle(handle),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const fingerprint = await fingerprintOpenHandle(handle).catch(() => undefined);
|
|
41
|
+
if (fingerprint !== undefined) {
|
|
42
|
+
await removeMutationLockIfCurrent(lockPath, fingerprint, ops);
|
|
43
|
+
}
|
|
44
|
+
await handle.close().catch(() => { });
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
50
|
+
await options.onContention?.();
|
|
51
|
+
if (options.canRecoverStale !== undefined
|
|
52
|
+
&& await breakStaleMutationLock(lockPath, ops, options.canRecoverStale, options.beforeStaleRemoval)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
await delay(CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw new ExecutionError("context_builder timed out waiting for the run mutation lock");
|
|
62
|
+
}
|
|
63
|
+
function resolveMutationLockAttempts(value) {
|
|
64
|
+
if (value === undefined) {
|
|
65
|
+
return CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS;
|
|
66
|
+
}
|
|
67
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
68
|
+
throw new ExecutionError("context_builder mutation lock attempts must be a positive integer");
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function mutationLockPathForTarget(target) {
|
|
73
|
+
validateCallerRunId(target.runId);
|
|
74
|
+
const resolvedRunsDir = resolve(target.runsDir);
|
|
75
|
+
const resolvedRunDir = resolve(target.runDir);
|
|
76
|
+
if (!isPathInsideDirectory(resolvedRunDir, resolvedRunsDir)) {
|
|
77
|
+
throw new ExecutionError("context_builder refused mutation lock outside runs directory");
|
|
78
|
+
}
|
|
79
|
+
return join(resolvedRunsDir, `.${target.runId}${CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX}`);
|
|
80
|
+
}
|
|
81
|
+
async function writeMutationLockMetadata(handle, ownerNonce) {
|
|
82
|
+
const metadata = {
|
|
83
|
+
pid: process.pid,
|
|
84
|
+
hostname: hostname(),
|
|
85
|
+
created_at: new Date().toISOString(),
|
|
86
|
+
...(ownerNonce == null ? {} : { owner_nonce: ownerNonce }),
|
|
87
|
+
};
|
|
88
|
+
await handle.writeFile(`${JSON.stringify(metadata)}\n`, { encoding: "utf8" });
|
|
89
|
+
}
|
|
90
|
+
async function breakStaleMutationLock(lockPath, ops, canRecoverStale, beforeStaleRemoval) {
|
|
91
|
+
const candidate = await observeMutationLock(lockPath, ops);
|
|
92
|
+
if (candidate === undefined) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
if (!isRecoverableStaleMutationLock(candidate)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await candidate.handle.close().catch(() => { });
|
|
102
|
+
}
|
|
103
|
+
const recoveryGate = await tryAcquireMutationLockRecoveryGate(lockPath, ops);
|
|
104
|
+
if (recoveryGate === undefined) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const observed = await observeMutationLock(lockPath, ops);
|
|
109
|
+
if (observed === undefined) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
if (!isRecoverableStaleMutationLock(observed)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
await beforeStaleRemoval?.();
|
|
117
|
+
if (!await canRecoverStale()) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return await removeMutationLockIfCurrent(lockPath, observed.fingerprint, ops);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await observed.handle.close().catch(() => { });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
await removeMutationLockIfCurrent(`${lockPath}${CONTEXT_BUILDER_MUTATION_LOCK_RECOVERY_SUFFIX}`, recoveryGate.fingerprint, ops);
|
|
128
|
+
await recoveryGate.handle.close().catch(() => { });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function tryAcquireMutationLockRecoveryGate(lockPath, ops) {
|
|
132
|
+
const gatePath = `${lockPath}${CONTEXT_BUILDER_MUTATION_LOCK_RECOVERY_SUFFIX}`;
|
|
133
|
+
const open = ops.open ?? fsOpen;
|
|
134
|
+
let handle;
|
|
135
|
+
try {
|
|
136
|
+
handle = await open(gatePath, "wx", 0o600);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
await writeMutationLockMetadata(handle, undefined);
|
|
146
|
+
return {
|
|
147
|
+
handle,
|
|
148
|
+
fingerprint: await fingerprintOpenHandle(handle),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const fingerprint = await fingerprintOpenHandle(handle).catch(() => undefined);
|
|
153
|
+
if (fingerprint !== undefined) {
|
|
154
|
+
await removeMutationLockIfCurrent(gatePath, fingerprint, ops);
|
|
155
|
+
}
|
|
156
|
+
await handle.close().catch(() => { });
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function observeMutationLock(lockPath, ops) {
|
|
161
|
+
const lstat = ops.lstat ?? fsLstat;
|
|
162
|
+
const open = ops.open ?? fsOpen;
|
|
163
|
+
let handle;
|
|
164
|
+
try {
|
|
165
|
+
const pathStats = await lstat(lockPath, { bigint: true });
|
|
166
|
+
if (!pathStats.isFile()) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
handle = await open(lockPath, "r");
|
|
170
|
+
const before = await handle.stat({ bigint: true });
|
|
171
|
+
if (!before.isFile()
|
|
172
|
+
|| before.nlink === 0n
|
|
173
|
+
|| !sameMutationLockIncarnation(fingerprintFromStats(pathStats), fingerprintFromStats(before))) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const raw = await handle.readFile({ encoding: "utf8" });
|
|
177
|
+
const after = await handle.stat({ bigint: true });
|
|
178
|
+
const beforeFingerprint = fingerprintFromStats(before);
|
|
179
|
+
const afterFingerprint = fingerprintFromStats(after);
|
|
180
|
+
if (after.nlink === 0n
|
|
181
|
+
|| !mutationLockFingerprintsEqual(beforeFingerprint, afterFingerprint)) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const observed = {
|
|
185
|
+
handle,
|
|
186
|
+
fingerprint: afterFingerprint,
|
|
187
|
+
metadata: parseMutationLockMetadata(raw),
|
|
188
|
+
};
|
|
189
|
+
handle = undefined;
|
|
190
|
+
return observed;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
await handle?.close().catch(() => { });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function isRecoverableStaleMutationLock(observed) {
|
|
200
|
+
const mtimeMs = Number(observed.fingerprint.mtimeNs / 1000000n);
|
|
201
|
+
if (Date.now() - mtimeMs < CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return observed.metadata === "invalid"
|
|
205
|
+
|| isStaleMutationLockMetadata(observed.metadata);
|
|
206
|
+
}
|
|
207
|
+
async function fingerprintOpenHandle(handle) {
|
|
208
|
+
return fingerprintFromStats(await handle.stat({ bigint: true }));
|
|
209
|
+
}
|
|
210
|
+
async function removeMutationLockIfCurrent(lockPath, expected, ops) {
|
|
211
|
+
// Node has no portable conditional-unlink primitive. Keeping the admitted
|
|
212
|
+
// descriptor open prevents inode reuse while this fingerprint recheck rejects
|
|
213
|
+
// replacements visible before unlink. Ordinary owner and recovery-gate release
|
|
214
|
+
// are safe under cooperative ownership. Stale main-lock removal holds the
|
|
215
|
+
// per-run recovery gate across this boundary; that gate is never stale-recovered.
|
|
216
|
+
const lstat = ops.lstat ?? fsLstat;
|
|
217
|
+
const rm = ops.rm ?? fsRm;
|
|
218
|
+
let current;
|
|
219
|
+
try {
|
|
220
|
+
current = await lstat(lockPath, { bigint: true });
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (!current.isFile()
|
|
226
|
+
|| !mutationLockFingerprintsEqual(fingerprintFromStats(current), expected)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
await rm(lockPath, { force: true });
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function parseMutationLockMetadata(raw) {
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = JSON.parse(raw);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return "invalid";
|
|
244
|
+
}
|
|
245
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
246
|
+
return "invalid";
|
|
247
|
+
}
|
|
248
|
+
const metadata = parsed;
|
|
249
|
+
if (typeof metadata.pid !== "number"
|
|
250
|
+
|| !Number.isInteger(metadata.pid)
|
|
251
|
+
|| typeof metadata.hostname !== "string"
|
|
252
|
+
|| typeof metadata.created_at !== "string") {
|
|
253
|
+
return "invalid";
|
|
254
|
+
}
|
|
255
|
+
if (metadata.owner_nonce !== undefined
|
|
256
|
+
&& typeof metadata.owner_nonce !== "string") {
|
|
257
|
+
return "invalid";
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
pid: metadata.pid,
|
|
261
|
+
hostname: metadata.hostname,
|
|
262
|
+
created_at: metadata.created_at,
|
|
263
|
+
...(metadata.owner_nonce === undefined
|
|
264
|
+
? {}
|
|
265
|
+
: { owner_nonce: metadata.owner_nonce }),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function fingerprintFromStats(stats) {
|
|
269
|
+
return {
|
|
270
|
+
dev: stats.dev,
|
|
271
|
+
ino: stats.ino,
|
|
272
|
+
mtimeNs: stats.mtimeNs,
|
|
273
|
+
ctimeNs: stats.ctimeNs,
|
|
274
|
+
size: stats.size,
|
|
275
|
+
nlink: stats.nlink,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function sameMutationLockIncarnation(left, right) {
|
|
279
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
280
|
+
}
|
|
281
|
+
function mutationLockFingerprintsEqual(left, right) {
|
|
282
|
+
return sameMutationLockIncarnation(left, right)
|
|
283
|
+
&& left.mtimeNs === right.mtimeNs
|
|
284
|
+
&& left.ctimeNs === right.ctimeNs
|
|
285
|
+
&& left.size === right.size
|
|
286
|
+
&& left.nlink === right.nlink;
|
|
287
|
+
}
|
|
288
|
+
function isStaleMutationLockMetadata(metadata) {
|
|
289
|
+
const createdAtMs = Date.parse(metadata.created_at);
|
|
290
|
+
if (!Number.isFinite(createdAtMs)
|
|
291
|
+
|| Date.now() - createdAtMs < CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (metadata.hostname !== hostname()) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
return !isProcessAlive(metadata.pid);
|
|
298
|
+
}
|
|
299
|
+
function isProcessAlive(pid) {
|
|
300
|
+
if (pid <= 0) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
process.kill(pid, 0);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
return !(isNodeError(error) && error.code === "ESRCH");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function isNodeError(error) {
|
|
312
|
+
return error instanceof Error && "code" in error;
|
|
313
|
+
}
|
|
314
|
+
async function delay(ms) {
|
|
315
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
316
|
+
}
|
|
317
|
+
function isPathInsideDirectory(candidate, directory) {
|
|
318
|
+
const relation = relative(directory, candidate);
|
|
319
|
+
return relation !== "" && !relation.startsWith("..") && !isAbsolute(relation);
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=mutation-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation-lock.js","sourceRoot":"","sources":["../../src/context-builder/mutation-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -58,6 +58,7 @@ type ReviewSliceInput = ReviewCandidateInput & ContextReviewRange & {
|
|
|
58
58
|
source: "focus" | "search";
|
|
59
59
|
search_term_index?: number;
|
|
60
60
|
};
|
|
61
|
+
export declare const MAX_REGRESSION_HEAD_SLICE_TARGETS = 24;
|
|
61
62
|
export declare function contextReviewCandidateId(rootIndex: number, path: string): string;
|
|
62
63
|
export declare function buildContextReviewObligations(input: {
|
|
63
64
|
candidates: readonly ReviewCandidateInput[];
|
|
@@ -66,6 +67,37 @@ export declare function buildContextReviewObligations(input: {
|
|
|
66
67
|
slices: readonly ReviewSliceInput[];
|
|
67
68
|
policy: ContextCoveragePolicy | undefined;
|
|
68
69
|
}): ContextReviewObligationModel | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Picks exact-stem regression tests that would link confidently but cannot,
|
|
72
|
+
* only because discovery produced no usable slice for them (typically a capped
|
|
73
|
+
* search on generic terms, or a search hit whose slice was pruned). Linkage
|
|
74
|
+
* needs a range, so without one the target reports a false "no confident
|
|
75
|
+
* regression link". Discovery synthesizes one bounded head slice per returned
|
|
76
|
+
* test and rebuilds the obligations.
|
|
77
|
+
*
|
|
78
|
+
* Deliberately narrower than linkage itself: exact stem only (no proximity or
|
|
79
|
+
* search-token links), the same ambiguity/generic-stem confidence rules, never
|
|
80
|
+
* for non-source targets (docs/config have no regression obligation), and
|
|
81
|
+
* skipped whenever the test is already linked to ANY target, because then
|
|
82
|
+
* linkage is already decided by real evidence. `searchRecords`/`slices` are
|
|
83
|
+
* kept in the input for call-site stability; eligibility no longer reads them.
|
|
84
|
+
*/
|
|
85
|
+
export declare function planRegressionHeadSliceCandidates(input: {
|
|
86
|
+
candidates: readonly (ReviewCandidateInput & {
|
|
87
|
+
line_count: number;
|
|
88
|
+
})[];
|
|
89
|
+
searchRecords: readonly ReviewSearchInput[];
|
|
90
|
+
slices: readonly ReviewCandidateInput[];
|
|
91
|
+
obligations: ContextReviewObligationModel;
|
|
92
|
+
}): ReviewCandidateInput[];
|
|
93
|
+
/**
|
|
94
|
+
* Docs, data, and config-format files carry no regression-test obligation, so
|
|
95
|
+
* an unlinked one reports regression "unavailable" instead of degrading the
|
|
96
|
+
* review. A denylist on purpose: unknown extensions stay source, so a new
|
|
97
|
+
* language is never silently exempted. Not `isGenericConfigPath`, which also
|
|
98
|
+
* matches source modules such as `config.ts`.
|
|
99
|
+
*/
|
|
100
|
+
export declare function isNonSourceReviewPath(path: string): boolean;
|
|
69
101
|
export declare function resolveContextCoveragePolicy(profile: ContextBuilderIntentProfile | undefined): ContextCoveragePolicy;
|
|
70
102
|
export declare function evaluateContextCoverage(input: {
|
|
71
103
|
policy: ContextCoveragePolicy;
|
|
@@ -9,6 +9,10 @@ export const CONTEXT_EVIDENCE_CLASS_ORDER = [
|
|
|
9
9
|
];
|
|
10
10
|
const REVIEW_LINK_MAX_DIRECTORY_DISTANCE = 2;
|
|
11
11
|
const GENERIC_REVIEW_STEMS = new Set(["index", "config", "types", "utils"]);
|
|
12
|
+
// Bounds the synthesized regression head slices discovery may add: at most one
|
|
13
|
+
// per unlinked target and this many overall, so a wide focus directory cannot
|
|
14
|
+
// turn stem linkage into bulk test ingestion.
|
|
15
|
+
export const MAX_REGRESSION_HEAD_SLICE_TARGETS = 24;
|
|
12
16
|
export function contextReviewCandidateId(rootIndex, path) {
|
|
13
17
|
return `c:${String(rootIndex)}:${path}`;
|
|
14
18
|
}
|
|
@@ -132,13 +136,14 @@ export function buildContextReviewObligations(input) {
|
|
|
132
136
|
: [];
|
|
133
137
|
const hasAnyDistinguishingSearch = ambiguousStem &&
|
|
134
138
|
sameStemTargets.some((target) => targetDistinguishingSearchRecords(testSearch, target, sameStemTargets).length > 0);
|
|
135
|
-
const
|
|
136
|
-
?
|
|
137
|
-
:
|
|
138
|
-
.filter((record) => targetStem.length > 0 &&
|
|
139
|
+
const stemLinkingRecords = requiresDistinguishingSearch
|
|
140
|
+
? []
|
|
141
|
+
: testSearch.filter((record) => targetStem.length > 0 &&
|
|
139
142
|
(record.search_term.toLowerCase().includes(targetStem) ||
|
|
140
|
-
record.preview.toLowerCase().includes(targetStem)))
|
|
141
|
-
|
|
143
|
+
record.preview.toLowerCase().includes(targetStem)));
|
|
144
|
+
const linkingSearchIndexes = new Set((requiresDistinguishingSearch
|
|
145
|
+
? distinguishingSearchRecords
|
|
146
|
+
: stemLinkingRecords).map((record) => record.search_term_index));
|
|
142
147
|
const confidentProximity = nearby &&
|
|
143
148
|
!hasAnyDistinguishingSearch &&
|
|
144
149
|
(!ambiguousStem ||
|
|
@@ -149,20 +154,31 @@ export function buildContextReviewObligations(input) {
|
|
|
149
154
|
continue;
|
|
150
155
|
}
|
|
151
156
|
const testSlices = slicesByCandidateForLinking.get(testId) ?? [];
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
const firstSlice = () => [...testSlices].sort(compareReviewSlices).slice(0, 1);
|
|
158
|
+
let candidateSlices;
|
|
159
|
+
if (linkingSearchIndexes.size === 0) {
|
|
160
|
+
candidateSlices = confidentStem || confidentProximity ? firstSlice() : [];
|
|
161
|
+
}
|
|
162
|
+
else if (requiresDistinguishingSearch) {
|
|
163
|
+
candidateSlices = testSlices.filter((slice) => slice.search_term_index != null &&
|
|
164
|
+
linkingSearchIndexes.has(slice.search_term_index) &&
|
|
165
|
+
distinguishingSearchRecords.some((record) => record.search_term_index === slice.search_term_index &&
|
|
166
|
+
record.line >= slice.start_line &&
|
|
167
|
+
record.line <= slice.end_line));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Overlapping search slices are merged under one retained
|
|
171
|
+
// search_term_index, so a slice that physically contains a stem hit
|
|
172
|
+
// links even when it carries a different term index. A confident
|
|
173
|
+
// exact-stem test with no containing slice still links through its
|
|
174
|
+
// first slice rather than reporting a false "no link".
|
|
175
|
+
candidateSlices = testSlices.filter((slice) => (slice.search_term_index != null &&
|
|
176
|
+
linkingSearchIndexes.has(slice.search_term_index)) ||
|
|
177
|
+
stemLinkingRecords.some((record) => record.line >= slice.start_line && record.line <= slice.end_line));
|
|
178
|
+
if (candidateSlices.length === 0 && confidentStem) {
|
|
179
|
+
candidateSlices = firstSlice();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
166
182
|
const ranges = mergeReviewRanges(candidateSlices);
|
|
167
183
|
if (ranges.length > 0) {
|
|
168
184
|
regressionLinks.push({
|
|
@@ -183,15 +199,106 @@ export function buildContextReviewObligations(input) {
|
|
|
183
199
|
classification_reason: classificationReason,
|
|
184
200
|
regression_availability: regressionLinks.length > 0
|
|
185
201
|
? "available"
|
|
186
|
-
:
|
|
187
|
-
? "
|
|
188
|
-
:
|
|
202
|
+
: isNonSourceReviewPath(candidate.path)
|
|
203
|
+
? "unavailable"
|
|
204
|
+
: rootsWithTests.has(candidate.root_index)
|
|
205
|
+
? "unclassifiable"
|
|
206
|
+
: "unavailable",
|
|
189
207
|
regression_links: regressionLinks.sort((left, right) => left.candidate_id.localeCompare(right.candidate_id)),
|
|
190
208
|
};
|
|
191
209
|
targetById.set(target.target_id, target);
|
|
192
210
|
}
|
|
193
211
|
return { targets: [...targetById.values()] };
|
|
194
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Picks exact-stem regression tests that would link confidently but cannot,
|
|
215
|
+
* only because discovery produced no usable slice for them (typically a capped
|
|
216
|
+
* search on generic terms, or a search hit whose slice was pruned). Linkage
|
|
217
|
+
* needs a range, so without one the target reports a false "no confident
|
|
218
|
+
* regression link". Discovery synthesizes one bounded head slice per returned
|
|
219
|
+
* test and rebuilds the obligations.
|
|
220
|
+
*
|
|
221
|
+
* Deliberately narrower than linkage itself: exact stem only (no proximity or
|
|
222
|
+
* search-token links), the same ambiguity/generic-stem confidence rules, never
|
|
223
|
+
* for non-source targets (docs/config have no regression obligation), and
|
|
224
|
+
* skipped whenever the test is already linked to ANY target, because then
|
|
225
|
+
* linkage is already decided by real evidence. `searchRecords`/`slices` are
|
|
226
|
+
* kept in the input for call-site stability; eligibility no longer reads them.
|
|
227
|
+
*/
|
|
228
|
+
export function planRegressionHeadSliceCandidates(input) {
|
|
229
|
+
const linkedTestIds = new Set(input.obligations.targets.flatMap((target) => target.regression_links.map((link) => link.candidate_id)));
|
|
230
|
+
const testsByRootAndStem = new Map();
|
|
231
|
+
for (const candidate of input.candidates) {
|
|
232
|
+
if (!isTestPath(candidate.path) || candidate.line_count <= 0) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
appendIndexedCandidate(testsByRootAndStem, rootAndStemKey(candidate), candidate);
|
|
236
|
+
}
|
|
237
|
+
const targetCountByRootAndStem = new Map();
|
|
238
|
+
for (const target of input.obligations.targets) {
|
|
239
|
+
const key = rootAndStemKey(target);
|
|
240
|
+
targetCountByRootAndStem.set(key, (targetCountByRootAndStem.get(key) ?? 0) + 1);
|
|
241
|
+
}
|
|
242
|
+
const planned = [];
|
|
243
|
+
const plannedIds = new Set();
|
|
244
|
+
for (const target of [...input.obligations.targets].sort((left, right) => left.target_id.localeCompare(right.target_id))) {
|
|
245
|
+
if (planned.length >= MAX_REGRESSION_HEAD_SLICE_TARGETS) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
if (target.regression_links.length > 0 || isNonSourceReviewPath(target.path)) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const key = rootAndStemKey(target);
|
|
252
|
+
if ((targetCountByRootAndStem.get(key) ?? 0) > 1) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const targetStem = normalizedPathStem(target.path).toLowerCase();
|
|
256
|
+
const genericStem = GENERIC_REVIEW_STEMS.has(targetStem);
|
|
257
|
+
const eligible = (testsByRootAndStem.get(key) ?? []).filter((test) => {
|
|
258
|
+
const testId = contextReviewCandidateId(test.root_index, test.path);
|
|
259
|
+
const nearby = directoryDistance(target.path, test.path) <=
|
|
260
|
+
REVIEW_LINK_MAX_DIRECTORY_DISTANCE;
|
|
261
|
+
return (!genericStem || nearby) && !linkedTestIds.has(testId);
|
|
262
|
+
});
|
|
263
|
+
const chosen = eligible.sort((left, right) => directoryDistance(target.path, left.path) -
|
|
264
|
+
directoryDistance(target.path, right.path) ||
|
|
265
|
+
left.path.localeCompare(right.path))[0];
|
|
266
|
+
if (chosen == null) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const chosenId = contextReviewCandidateId(chosen.root_index, chosen.path);
|
|
270
|
+
if (plannedIds.has(chosenId)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
plannedIds.add(chosenId);
|
|
274
|
+
planned.push({ root_index: chosen.root_index, path: chosen.path });
|
|
275
|
+
}
|
|
276
|
+
return planned;
|
|
277
|
+
}
|
|
278
|
+
const NON_SOURCE_REVIEW_EXTENSIONS = new Set([
|
|
279
|
+
".md", ".mdx", ".markdown", ".txt", ".rst", ".adoc",
|
|
280
|
+
".json", ".jsonc", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf",
|
|
281
|
+
".lock", ".csv", ".tsv", ".xml", ".po", ".pot", ".mo",
|
|
282
|
+
]);
|
|
283
|
+
// Exact extensionless names only: `README.md`/`LICENSE.txt` are already caught
|
|
284
|
+
// by the extension denylist, and a prefix match would exempt source files such
|
|
285
|
+
// as `readme_helper.ts`.
|
|
286
|
+
const NON_SOURCE_REVIEW_BASENAME = /^(?:license|licence|copying|changelog|readme)$/i;
|
|
287
|
+
/**
|
|
288
|
+
* Docs, data, and config-format files carry no regression-test obligation, so
|
|
289
|
+
* an unlinked one reports regression "unavailable" instead of degrading the
|
|
290
|
+
* review. A denylist on purpose: unknown extensions stay source, so a new
|
|
291
|
+
* language is never silently exempted. Not `isGenericConfigPath`, which also
|
|
292
|
+
* matches source modules such as `config.ts`.
|
|
293
|
+
*/
|
|
294
|
+
export function isNonSourceReviewPath(path) {
|
|
295
|
+
const basename = path.slice(path.lastIndexOf("/") + 1);
|
|
296
|
+
if (NON_SOURCE_REVIEW_BASENAME.test(basename)) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
const dot = basename.lastIndexOf(".");
|
|
300
|
+
return dot > 0 && NON_SOURCE_REVIEW_EXTENSIONS.has(basename.slice(dot).toLowerCase());
|
|
301
|
+
}
|
|
195
302
|
export function resolveContextCoveragePolicy(profile) {
|
|
196
303
|
if (profile !== "review_deep") {
|
|
197
304
|
return {
|