@deftai/directive-core 0.106.0 → 0.108.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/dist/architecture/sor-preflight.js +2 -0
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +232 -0
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +69 -2
- package/dist/hooks/dispatcher.js +609 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +75 -7
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +9 -18
- package/dist/policy/write-fence.js +3 -0
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/preflight-cache/evaluate.js +9 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +53 -12
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +19 -0
- package/dist/release/types.d.ts +14 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +38 -0
- package/dist/scm/design-critique-chip.js +187 -0
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +4 -1
- package/dist/scm/readiness.js +35 -5
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +3 -1
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +42 -0
- package/dist/session/git.js +219 -3
- package/dist/session/occupancy.d.ts +239 -12
- package/dist/session/occupancy.js +820 -27
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +17 -1
- package/dist/session/session-start.js +49 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +33 -3
- package/dist/session/verify-session-ritual.js +89 -19
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +77 -0
- package/dist/swarm/launch.js +248 -38
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +21 -7
- package/dist/triage/help/registry-data.js +53 -5
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +7 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Minimal runtime capability probe for github_auth_modes (#1557a / #1784). */
|
|
2
|
+
import { hasExplicitHostGhSelection, probeManagedRuntime, RUNTIME_REASON_CI_MARKER, RUNTIME_REASON_CURSOR_MARKER_AMBIGUOUS, RUNTIME_REASON_CURSOR_SANDBOX_MARKER, RUNTIME_REASON_EXPLICIT_HOST_GH, RUNTIME_REASON_MANAGED_RUNTIME_PROBE, RUNTIME_REASON_NO_RUNTIME_MARKER, } from "../platform/cursor-managed-runtime.js";
|
|
2
3
|
export const RUNTIME_MODE_LOCAL_UNSANDBOXED = "local-unsandboxed";
|
|
3
4
|
export const RUNTIME_MODE_CURSOR_NATIVE_SANDBOX = "cursor-native-sandbox";
|
|
4
5
|
export const RUNTIME_MODE_CLOUD_HEADLESS = "cloud-headless";
|
|
@@ -11,24 +12,52 @@ const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
|
11
12
|
function isTruthy(value) {
|
|
12
13
|
return value !== undefined && TRUTHY.has(value.trim().toLowerCase());
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Classify the execution runtime.
|
|
17
|
+
*
|
|
18
|
+
* Ordering is load-bearing (#3859):
|
|
19
|
+
* 1. CI markers, so a CI runner is never reclassified by a Cursor signal.
|
|
20
|
+
* 2. A positive Cursor managed-runtime read, above every other Cursor signal.
|
|
21
|
+
* 3. Cursor native sandbox.
|
|
22
|
+
* 4. A Cursor marker with no managed read leaves the runtime AMBIGUOUS. Only
|
|
23
|
+
* an explicit operator/dispatcher selection resolves it to local; absent
|
|
24
|
+
* that, the verdict is unchanged from before #3859.
|
|
25
|
+
*/
|
|
26
|
+
function classifyRuntimeMode(environ = process.env, options = {}) {
|
|
15
27
|
for (const key of ["GROK_BUILD", "GITHUB_ACTIONS", "CI", "BUILDKITE", "DEFT_AGENT_RUNTIME"]) {
|
|
16
28
|
if (isTruthy(environ[key])) {
|
|
17
|
-
return RUNTIME_MODE_CLOUD_HEADLESS;
|
|
29
|
+
return { mode: RUNTIME_MODE_CLOUD_HEADLESS, reason: RUNTIME_REASON_CI_MARKER };
|
|
18
30
|
}
|
|
19
31
|
}
|
|
32
|
+
const managedProbe = options.managedRuntimeProbe ?? probeManagedRuntime;
|
|
33
|
+
if (managedProbe(environ).verdict === "managed") {
|
|
34
|
+
return {
|
|
35
|
+
mode: RUNTIME_MODE_CLOUD_HEADLESS,
|
|
36
|
+
reason: RUNTIME_REASON_MANAGED_RUNTIME_PROBE,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
20
39
|
if (isTruthy(environ.CURSOR_SANDBOX) || isTruthy(environ.CURSOR_SANDBOX_LANDLOCK_STATUS)) {
|
|
21
|
-
return
|
|
40
|
+
return {
|
|
41
|
+
mode: RUNTIME_MODE_CURSOR_NATIVE_SANDBOX,
|
|
42
|
+
reason: RUNTIME_REASON_CURSOR_SANDBOX_MARKER,
|
|
43
|
+
};
|
|
22
44
|
}
|
|
23
45
|
if (isTruthy(environ.CURSOR_AGENT) || isTruthy(environ.CURSOR_COMPOSER)) {
|
|
24
|
-
|
|
46
|
+
if (hasExplicitHostGhSelection(environ)) {
|
|
47
|
+
return { mode: RUNTIME_MODE_LOCAL_UNSANDBOXED, reason: RUNTIME_REASON_EXPLICIT_HOST_GH };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
mode: RUNTIME_MODE_CLOUD_HEADLESS,
|
|
51
|
+
reason: RUNTIME_REASON_CURSOR_MARKER_AMBIGUOUS,
|
|
52
|
+
};
|
|
25
53
|
}
|
|
26
|
-
return RUNTIME_MODE_LOCAL_UNSANDBOXED;
|
|
54
|
+
return { mode: RUNTIME_MODE_LOCAL_UNSANDBOXED, reason: RUNTIME_REASON_NO_RUNTIME_MARKER };
|
|
27
55
|
}
|
|
28
|
-
export function probeRuntimeCapabilities(environ = process.env) {
|
|
29
|
-
|
|
56
|
+
export function probeRuntimeCapabilities(environ = process.env, options = {}) {
|
|
57
|
+
const { mode, reason } = classifyRuntimeMode(environ, options);
|
|
58
|
+
return { runtimeMode: mode, runtimeModeReason: reason };
|
|
30
59
|
}
|
|
31
|
-
export function getPlatformCapabilities() {
|
|
32
|
-
return probeRuntimeCapabilities();
|
|
60
|
+
export function getPlatformCapabilities(options = {}) {
|
|
61
|
+
return probeRuntimeCapabilities(process.env, options);
|
|
33
62
|
}
|
|
34
63
|
//# sourceMappingURL=platform-capabilities.js.map
|
|
@@ -35,7 +35,33 @@ export interface EvaluateCompletedTrackedOptions {
|
|
|
35
35
|
readonly runGit?: GitRunner;
|
|
36
36
|
/** Test seam: override issue state resolution. */
|
|
37
37
|
readonly resolveIssueState?: (ref: IssueRef) => "open" | "closed" | null;
|
|
38
|
+
/**
|
|
39
|
+
* Progress hook (#3673). Fired after tip listing and after the batch
|
|
40
|
+
* blob read so a CLI can announce an up-front count only when elapsed
|
|
41
|
+
* time crosses a measured threshold.
|
|
42
|
+
*/
|
|
43
|
+
readonly onProgress?: (event: CompletedTrackedProgress) => void;
|
|
44
|
+
/** Test seam: override wall clock for progress elapsedMs. */
|
|
45
|
+
readonly now?: () => number;
|
|
46
|
+
}
|
|
47
|
+
/** Default silence window so a ~2s batch read does not emit progress noise. */
|
|
48
|
+
export declare const COMPLETED_TRACKED_PROGRESS_THRESHOLD_MS = 3000;
|
|
49
|
+
/**
|
|
50
|
+
* Up-front count floor (#3673 Greptile P2). Listing is cheap; the batch
|
|
51
|
+
* read is the remaining stall. Announce the blob count after listing
|
|
52
|
+
* (before `cat-file --batch`) when the corpus is large enough that a
|
|
53
|
+
* silent wait is the old DONE-path failure mode. Fixture-sized runs stay
|
|
54
|
+
* quiet unless the duration threshold is also crossed.
|
|
55
|
+
*/
|
|
56
|
+
export declare const COMPLETED_TRACKED_PROGRESS_MIN_BLOBS = 32;
|
|
57
|
+
export interface CompletedTrackedProgress {
|
|
58
|
+
readonly phase: "listed" | "read";
|
|
59
|
+
readonly terminalCount: number;
|
|
60
|
+
readonly nonterminalCount: number;
|
|
61
|
+
readonly elapsedMs: number;
|
|
38
62
|
}
|
|
63
|
+
export declare function shouldAnnounceProgress(elapsedMs: number, thresholdMs?: number): boolean;
|
|
64
|
+
export declare function shouldAnnounceUpFrontCount(terminalCount: number, nonterminalCount: number, minBlobs?: number): boolean;
|
|
39
65
|
/**
|
|
40
66
|
* Resolve the git tip to inspect for tracked completed/cancelled land.
|
|
41
67
|
* Prefers origin/<deliveryBranch> when present.
|
|
@@ -16,7 +16,7 @@ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveL
|
|
|
16
16
|
import { collectGithubRefs } from "../orphan-active/refs.js";
|
|
17
17
|
import { resolveDeliveryBranch } from "../policy/delivery-branch.js";
|
|
18
18
|
import { defaultRunGh } from "../pr-protected-issues/gh.js";
|
|
19
|
-
import { defaultGitRunner } from "../session/git.js";
|
|
19
|
+
import { defaultGitRunner, showBlobsBatch } from "../session/git.js";
|
|
20
20
|
import { CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE } from "../triage/queue/constants.js";
|
|
21
21
|
import { resolveRepo } from "../triage/queue/repo.js";
|
|
22
22
|
/** Lifecycle folders scanned for local "known origin" xBRIEFs. */
|
|
@@ -31,6 +31,22 @@ export const LOCAL_ORIGIN_FOLDERS = [
|
|
|
31
31
|
export const TIP_NONTERMINAL_FOLDERS = ["proposed", "pending", "active"];
|
|
32
32
|
/** Terminal tip folders that satisfy the land rule. */
|
|
33
33
|
export const TIP_TERMINAL_FOLDERS = ["completed", "cancelled"];
|
|
34
|
+
/** Default silence window so a ~2s batch read does not emit progress noise. */
|
|
35
|
+
export const COMPLETED_TRACKED_PROGRESS_THRESHOLD_MS = 3_000;
|
|
36
|
+
/**
|
|
37
|
+
* Up-front count floor (#3673 Greptile P2). Listing is cheap; the batch
|
|
38
|
+
* read is the remaining stall. Announce the blob count after listing
|
|
39
|
+
* (before `cat-file --batch`) when the corpus is large enough that a
|
|
40
|
+
* silent wait is the old DONE-path failure mode. Fixture-sized runs stay
|
|
41
|
+
* quiet unless the duration threshold is also crossed.
|
|
42
|
+
*/
|
|
43
|
+
export const COMPLETED_TRACKED_PROGRESS_MIN_BLOBS = 32;
|
|
44
|
+
export function shouldAnnounceProgress(elapsedMs, thresholdMs = COMPLETED_TRACKED_PROGRESS_THRESHOLD_MS) {
|
|
45
|
+
return elapsedMs >= thresholdMs;
|
|
46
|
+
}
|
|
47
|
+
export function shouldAnnounceUpFrontCount(terminalCount, nonterminalCount, minBlobs = COMPLETED_TRACKED_PROGRESS_MIN_BLOBS) {
|
|
48
|
+
return terminalCount + nonterminalCount >= minBlobs;
|
|
49
|
+
}
|
|
34
50
|
function readJson(path) {
|
|
35
51
|
try {
|
|
36
52
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -223,13 +239,6 @@ function listTreePaths(projectRoot, tip, prefixes, runGit) {
|
|
|
223
239
|
.map((line) => line.trim().replace(/\\/g, "/"))
|
|
224
240
|
.filter((line) => line.length > 0 && hasArtifactSuffix(line));
|
|
225
241
|
}
|
|
226
|
-
function showBlob(projectRoot, tip, path, runGit) {
|
|
227
|
-
const result = runGit(projectRoot, ["show", `${tip}:${path}`]);
|
|
228
|
-
if (result.code !== 0) {
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
return result.stdout;
|
|
232
|
-
}
|
|
233
242
|
function terminalPrefixes() {
|
|
234
243
|
const out = [];
|
|
235
244
|
for (const root of [MIGRATED_ARTIFACT_DIR, LEGACY_ARTIFACT_DIR]) {
|
|
@@ -248,11 +257,11 @@ function nonterminalPrefixes() {
|
|
|
248
257
|
}
|
|
249
258
|
return out;
|
|
250
259
|
}
|
|
251
|
-
function
|
|
260
|
+
function issuesFromBlobBodies(paths, bodies, defaultRepo, originPrefix) {
|
|
252
261
|
const hits = [];
|
|
253
262
|
for (const path of paths) {
|
|
254
|
-
const body =
|
|
255
|
-
if (body === null) {
|
|
263
|
+
const body = bodies.get(path);
|
|
264
|
+
if (body === undefined || body === null) {
|
|
256
265
|
continue;
|
|
257
266
|
}
|
|
258
267
|
let parsed;
|
|
@@ -375,17 +384,31 @@ export function evaluateCompletedTracked(projectRoot, options = {}) {
|
|
|
375
384
|
tip: null,
|
|
376
385
|
};
|
|
377
386
|
}
|
|
387
|
+
const startedAt = (options.now ?? Date.now)();
|
|
388
|
+
const emitProgress = (phase, terminalCount, nonterminalCount) => {
|
|
389
|
+
options.onProgress?.({
|
|
390
|
+
phase,
|
|
391
|
+
terminalCount,
|
|
392
|
+
nonterminalCount,
|
|
393
|
+
elapsedMs: (options.now ?? Date.now)() - startedAt,
|
|
394
|
+
});
|
|
395
|
+
};
|
|
378
396
|
const localHits = scanLocalOrigins(root, defaultRepo);
|
|
379
397
|
const tipNonterminalPaths = listTreePaths(root, tip, nonterminalPrefixes(), runGit);
|
|
380
|
-
const tipNonterminalHits = issuesFromBlobPaths(root, tip, tipNonterminalPaths, defaultRepo, runGit, `tip:${tip}`);
|
|
381
|
-
let originMap = mergeOrigins([...localHits, ...tipNonterminalHits]);
|
|
382
398
|
// Delivery tip only — the land invariant is post-merge tip truth (#3264 AC).
|
|
383
399
|
// Lifecycle PRs that commit completed/ on a feature branch are not expected
|
|
384
400
|
// to satisfy this gate until merge; run the verb with --tip HEAD when
|
|
385
401
|
// validating an in-flight land PR. The gate is a standalone verify verb
|
|
386
402
|
// (not wired into check:consumer) so product PRs are not deadlocked.
|
|
387
403
|
const tipTerminalPaths = listTreePaths(root, tip, terminalPrefixes(), runGit);
|
|
388
|
-
|
|
404
|
+
emitProgress("listed", tipTerminalPaths.length, tipNonterminalPaths.length);
|
|
405
|
+
// One process for every tip artifact. Issue identity still comes from
|
|
406
|
+
// parsed plan.references / plan.metadata["x-tracking"] — never the path.
|
|
407
|
+
const tipBodies = showBlobsBatch(root, tip, [...tipNonterminalPaths, ...tipTerminalPaths], runGit);
|
|
408
|
+
emitProgress("read", tipTerminalPaths.length, tipNonterminalPaths.length);
|
|
409
|
+
const tipNonterminalHits = issuesFromBlobBodies(tipNonterminalPaths, tipBodies, defaultRepo, `tip:${tip}`);
|
|
410
|
+
let originMap = mergeOrigins([...localHits, ...tipNonterminalHits]);
|
|
411
|
+
const tipTerminalHits = issuesFromBlobBodies(tipTerminalPaths, tipBodies, defaultRepo, `tip:${tip}`);
|
|
389
412
|
const landedKeys = new Set(tipTerminalHits.map((h) => issueKey(h.issue)));
|
|
390
413
|
const issueFilter = options.issue ?? null;
|
|
391
414
|
if (issueFilter !== null) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse newly added completed/ blobs that bypass runTransition (#3679).
|
|
3
|
+
*
|
|
4
|
+
* Historical corpus is advisory (doctor). New work in the change set is hard
|
|
5
|
+
* (verify:completed-write-guard). Does not read completionProvenance and does
|
|
6
|
+
* not change verify:completed-tracked.
|
|
7
|
+
*
|
|
8
|
+
* Disk reads are capped at COMPLETED_WRITE_GUARD_MAX_BYTES so a huge
|
|
9
|
+
* contributor-controlled completed/ add fails through the guard instead of
|
|
10
|
+
* exhausting memory on the required gate path.
|
|
11
|
+
*/
|
|
12
|
+
export type CompletedWriteGuardCode = 0 | 1 | 2;
|
|
13
|
+
export interface CompletedWriteGuardFinding {
|
|
14
|
+
readonly relPath: string;
|
|
15
|
+
readonly detail: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CompletedWriteGuardResult {
|
|
18
|
+
readonly code: CompletedWriteGuardCode;
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly findings: readonly CompletedWriteGuardFinding[];
|
|
21
|
+
}
|
|
22
|
+
export interface CompletedWriteGuardOptions {
|
|
23
|
+
readonly baseRef?: string;
|
|
24
|
+
/** Inject added repo-relative POSIX paths (skips git). */
|
|
25
|
+
readonly addedFiles?: readonly string[];
|
|
26
|
+
/** Inject payloads: relPath -> raw JSON. */
|
|
27
|
+
readonly payloads?: ReadonlyMap<string, string>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* In-repo completed xBRIEFs average ~5.5 KiB and peak near 44 KiB.
|
|
31
|
+
* 1 MiB is ~23× that peak and still refuses a multi-hundred-MB add
|
|
32
|
+
* before the bytes are loaded.
|
|
33
|
+
*/
|
|
34
|
+
export declare const COMPLETED_WRITE_GUARD_MAX_BYTES = 1048576;
|
|
35
|
+
/**
|
|
36
|
+
* Hard check: newly added completed/ artifacts must show runTransition evidence.
|
|
37
|
+
*/
|
|
38
|
+
export declare function evaluateCompletedWriteGuard(projectRoot: string, options?: CompletedWriteGuardOptions): CompletedWriteGuardResult;
|
|
39
|
+
export interface CompletedWriteCorpusResult {
|
|
40
|
+
readonly findings: readonly CompletedWriteGuardFinding[];
|
|
41
|
+
readonly scanned: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Corpus scan for doctor: artifacts without transition evidence.
|
|
45
|
+
* Caller marks these advisory so historical pre-stamp files do not red doctor.
|
|
46
|
+
*/
|
|
47
|
+
export declare function scanCompletedWriteCorpus(projectRoot: string): CompletedWriteCorpusResult;
|
|
48
|
+
//# sourceMappingURL=completed-write-guard.d.ts.map
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse newly added completed/ blobs that bypass runTransition (#3679).
|
|
3
|
+
*
|
|
4
|
+
* Historical corpus is advisory (doctor). New work in the change set is hard
|
|
5
|
+
* (verify:completed-write-guard). Does not read completionProvenance and does
|
|
6
|
+
* not change verify:completed-tracked.
|
|
7
|
+
*
|
|
8
|
+
* Disk reads are capped at COMPLETED_WRITE_GUARD_MAX_BYTES so a huge
|
|
9
|
+
* contributor-controlled completed/ add fails through the guard instead of
|
|
10
|
+
* exhausting memory on the required gate path.
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { GitCommandError, GitNotFoundError } from "../encoding/git.js";
|
|
16
|
+
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
|
|
17
|
+
import { hasTransitionWrite, LEFTOVER_LAND_PR_REMEDIATION } from "../scope/lifecycle-write.js";
|
|
18
|
+
import { resolveDefaultBaseRef, unquoteGitPath } from "../scope-provenance/evaluate.js";
|
|
19
|
+
/**
|
|
20
|
+
* In-repo completed xBRIEFs average ~5.5 KiB and peak near 44 KiB.
|
|
21
|
+
* 1 MiB is ~23× that peak and still refuses a multi-hundred-MB add
|
|
22
|
+
* before the bytes are loaded.
|
|
23
|
+
*/
|
|
24
|
+
export const COMPLETED_WRITE_GUARD_MAX_BYTES = 1_048_576;
|
|
25
|
+
const COMPLETED_REL_RE = /^(?:xbrief|vbrief)\/completed\/[^/]+$/;
|
|
26
|
+
function normalizeRepoRelPath(raw) {
|
|
27
|
+
return raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
28
|
+
}
|
|
29
|
+
function isCompletedArtifactRel(relPath) {
|
|
30
|
+
const n = normalizeRepoRelPath(relPath);
|
|
31
|
+
if (!COMPLETED_REL_RE.test(n)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const base = n.split("/").pop() ?? "";
|
|
35
|
+
return hasArtifactSuffix(base);
|
|
36
|
+
}
|
|
37
|
+
function parsePlan(raw) {
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.parse(raw);
|
|
40
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const plan = data.plan;
|
|
44
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return plan;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function gitEnv(projectRoot) {
|
|
54
|
+
const env = { ...process.env };
|
|
55
|
+
delete env.GIT_DIR;
|
|
56
|
+
delete env.GIT_WORK_TREE;
|
|
57
|
+
env.GIT_CEILING_DIRECTORIES = dirname(resolve(projectRoot));
|
|
58
|
+
return env;
|
|
59
|
+
}
|
|
60
|
+
function git(args, projectRoot) {
|
|
61
|
+
const result = spawnSync("git", args, {
|
|
62
|
+
cwd: projectRoot,
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
65
|
+
env: gitEnv(projectRoot),
|
|
66
|
+
});
|
|
67
|
+
if (result.error !== undefined) {
|
|
68
|
+
const e = result.error;
|
|
69
|
+
if (e.code === "ENOENT") {
|
|
70
|
+
throw new GitNotFoundError("'git' executable not found on PATH");
|
|
71
|
+
}
|
|
72
|
+
throw new GitCommandError(`git ${args.join(" ")} failed: ${String(e.message)}`);
|
|
73
|
+
}
|
|
74
|
+
if (result.signal !== null && result.signal !== undefined) {
|
|
75
|
+
throw new GitCommandError(`git ${args.join(" ")} killed by signal ${String(result.signal)}`);
|
|
76
|
+
}
|
|
77
|
+
const status = result.status ?? 1;
|
|
78
|
+
const stderr = String(result.stderr ?? "").trim();
|
|
79
|
+
if (status !== 0 && stderr.length > 0) {
|
|
80
|
+
return { status, stdout: `${result.stdout ?? ""}\n${stderr}` };
|
|
81
|
+
}
|
|
82
|
+
return { status, stdout: result.stdout ?? "" };
|
|
83
|
+
}
|
|
84
|
+
function addedPathsFromNameStatus(stdout) {
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const line of stdout.split("\n")) {
|
|
87
|
+
const t = line.replace(/\r$/, "").trim();
|
|
88
|
+
if (t.length === 0) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const parts = t.split("\t");
|
|
92
|
+
const status = parts[0] ?? "";
|
|
93
|
+
if (status.startsWith("A")) {
|
|
94
|
+
const path = parts[1];
|
|
95
|
+
if (path !== undefined) {
|
|
96
|
+
out.push(unquoteGitPath(path));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (status.startsWith("R")) {
|
|
100
|
+
const dest = parts[2] ?? parts[1];
|
|
101
|
+
if (dest !== undefined) {
|
|
102
|
+
out.push(unquoteGitPath(dest));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
function discoverAddedFiles(projectRoot, baseRef) {
|
|
109
|
+
const inside = git(["rev-parse", "--is-inside-work-tree"], projectRoot);
|
|
110
|
+
if (inside.status !== 0) {
|
|
111
|
+
throw new GitCommandError("not a git working tree");
|
|
112
|
+
}
|
|
113
|
+
let resolved = baseRef;
|
|
114
|
+
if (baseRef === "HEAD" || baseRef === "") {
|
|
115
|
+
const upgraded = resolveDefaultBaseRef(projectRoot);
|
|
116
|
+
if (upgraded === null) {
|
|
117
|
+
throw new GitCommandError("no merge-base ref (origin/master|main or DEFT_BASE_REF/GITHUB_BASE_REF)");
|
|
118
|
+
}
|
|
119
|
+
resolved = upgraded;
|
|
120
|
+
}
|
|
121
|
+
const hasBase = git(["rev-parse", "--verify", "-q", resolved], projectRoot).status === 0;
|
|
122
|
+
if (!hasBase) {
|
|
123
|
+
throw new GitCommandError(`base ref '${resolved}' not found; pass --base-ref`);
|
|
124
|
+
}
|
|
125
|
+
const out = new Set();
|
|
126
|
+
const range = resolved.includes("...") ? resolved : `${resolved}...HEAD`;
|
|
127
|
+
const committed = git(["diff", "--name-status", "--diff-filter=AR", range], projectRoot);
|
|
128
|
+
if (committed.status !== 0) {
|
|
129
|
+
const detail = committed.stdout.trim() || `git diff ${range} exited ${String(committed.status)}`;
|
|
130
|
+
throw new GitCommandError(`committed change-set unavailable for '${range}': ${detail}. ` +
|
|
131
|
+
"Pass --base-ref to a merge-base ancestor of HEAD.");
|
|
132
|
+
}
|
|
133
|
+
for (const p of addedPathsFromNameStatus(committed.stdout)) {
|
|
134
|
+
out.add(normalizeRepoRelPath(p));
|
|
135
|
+
}
|
|
136
|
+
const vsHead = git(["diff", "--name-status", "--diff-filter=AR", "HEAD"], projectRoot);
|
|
137
|
+
if (vsHead.status !== 0) {
|
|
138
|
+
const detail = vsHead.stdout.trim() || `git diff HEAD exited ${String(vsHead.status)}`;
|
|
139
|
+
throw new GitCommandError(`working-tree change-set unavailable: ${detail}`);
|
|
140
|
+
}
|
|
141
|
+
for (const p of addedPathsFromNameStatus(vsHead.stdout)) {
|
|
142
|
+
out.add(normalizeRepoRelPath(p));
|
|
143
|
+
}
|
|
144
|
+
const untracked = git(["ls-files", "--others", "--exclude-standard"], projectRoot);
|
|
145
|
+
if (untracked.status !== 0) {
|
|
146
|
+
const detail = untracked.stdout.trim() || `git ls-files exited ${String(untracked.status)}`;
|
|
147
|
+
throw new GitCommandError(`untracked change-set unavailable: ${detail}`);
|
|
148
|
+
}
|
|
149
|
+
for (const line of untracked.stdout.split("\n")) {
|
|
150
|
+
const p = normalizeRepoRelPath(unquoteGitPath(line));
|
|
151
|
+
if (p.length > 0) {
|
|
152
|
+
out.add(p);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return [...out];
|
|
156
|
+
}
|
|
157
|
+
function readPayload(projectRoot, relPath, payloads) {
|
|
158
|
+
const n = normalizeRepoRelPath(relPath);
|
|
159
|
+
const injected = payloads?.get(n);
|
|
160
|
+
if (injected !== undefined) {
|
|
161
|
+
if (injected.length > COMPLETED_WRITE_GUARD_MAX_BYTES) {
|
|
162
|
+
return {
|
|
163
|
+
kind: "unsafe",
|
|
164
|
+
detail: `${n}: completed/ artifact is ${String(injected.length)} bytes; ` +
|
|
165
|
+
`exceeds the ${String(COMPLETED_WRITE_GUARD_MAX_BYTES)}-byte read limit`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return { kind: "ok", raw: injected };
|
|
169
|
+
}
|
|
170
|
+
const parts = n.split("/").filter((part) => part.length > 0 && part !== ".");
|
|
171
|
+
let abs = resolve(projectRoot);
|
|
172
|
+
let st;
|
|
173
|
+
for (const part of parts) {
|
|
174
|
+
if (part === "..") {
|
|
175
|
+
return {
|
|
176
|
+
kind: "unsafe",
|
|
177
|
+
detail: `${n}: completed/ path escapes project root; refuse without following`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
abs = join(abs, part);
|
|
181
|
+
try {
|
|
182
|
+
st = lstatSync(abs);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return { kind: "missing" };
|
|
186
|
+
}
|
|
187
|
+
if (st.isSymbolicLink()) {
|
|
188
|
+
return {
|
|
189
|
+
kind: "unsafe",
|
|
190
|
+
detail: `${n}: completed/ path contains a symlink; refuse without following`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (st === undefined || !st.isFile()) {
|
|
195
|
+
return {
|
|
196
|
+
kind: "unsafe",
|
|
197
|
+
detail: `${n}: completed/ add is not a regular file; refuse without reading`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (st.size > COMPLETED_WRITE_GUARD_MAX_BYTES) {
|
|
201
|
+
return {
|
|
202
|
+
kind: "unsafe",
|
|
203
|
+
detail: `${n}: completed/ artifact is ${String(st.size)} bytes; ` +
|
|
204
|
+
`exceeds the ${String(COMPLETED_WRITE_GUARD_MAX_BYTES)}-byte read limit`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
return { kind: "ok", raw: readFileSync(abs, "utf8") };
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return { kind: "missing" };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Hard check: newly added completed/ artifacts must show runTransition evidence.
|
|
216
|
+
*/
|
|
217
|
+
export function evaluateCompletedWriteGuard(projectRoot, options = {}) {
|
|
218
|
+
const root = resolve(projectRoot);
|
|
219
|
+
let added;
|
|
220
|
+
try {
|
|
221
|
+
if (options.addedFiles !== undefined) {
|
|
222
|
+
added = options.addedFiles.map(normalizeRepoRelPath);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const inside = git(["rev-parse", "--is-inside-work-tree"], root);
|
|
226
|
+
if (inside.status !== 0 || inside.stdout.trim() !== "true") {
|
|
227
|
+
return {
|
|
228
|
+
code: 0,
|
|
229
|
+
findings: [],
|
|
230
|
+
message: "verify_completed_write_guard: skipped -- not a git working tree.",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
let baseRef = options.baseRef ?? "";
|
|
234
|
+
if (baseRef.length === 0 || baseRef === "HEAD") {
|
|
235
|
+
const resolved = resolveDefaultBaseRef(root);
|
|
236
|
+
if (resolved === null) {
|
|
237
|
+
return {
|
|
238
|
+
code: 2,
|
|
239
|
+
findings: [],
|
|
240
|
+
message: "verify_completed_write_guard: no merge-base ref found " +
|
|
241
|
+
"(origin/master|main, DEFT_BASE_REF, or GITHUB_BASE_REF). " +
|
|
242
|
+
"Pass --base-ref. Unguarded completed/ adds cannot be skipped.",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
baseRef = resolved;
|
|
246
|
+
}
|
|
247
|
+
added = discoverAddedFiles(root, baseRef);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
if (err instanceof GitNotFoundError) {
|
|
252
|
+
return {
|
|
253
|
+
code: 2,
|
|
254
|
+
findings: [],
|
|
255
|
+
message: "verify_completed_write_guard: 'git' executable not found on PATH.\n" +
|
|
256
|
+
" Recovery: install git or run inside a git working tree.",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (err instanceof GitCommandError) {
|
|
260
|
+
const msg = err.message.toLowerCase();
|
|
261
|
+
if (msg.includes("not a git repository") ||
|
|
262
|
+
msg.includes("outside repository") ||
|
|
263
|
+
msg.includes("not a git working tree")) {
|
|
264
|
+
return {
|
|
265
|
+
code: 0,
|
|
266
|
+
findings: [],
|
|
267
|
+
message: `verify_completed_write_guard: skipped -- not a git working tree (${err.message}).`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
code: 2,
|
|
272
|
+
findings: [],
|
|
273
|
+
message: `verify_completed_write_guard: git failed -- ${err.message}`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
throw err;
|
|
277
|
+
}
|
|
278
|
+
const findings = [];
|
|
279
|
+
for (const rel of added) {
|
|
280
|
+
if (!isCompletedArtifactRel(rel)) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const payload = readPayload(root, rel, options.payloads);
|
|
284
|
+
if (payload.kind === "missing") {
|
|
285
|
+
findings.push({
|
|
286
|
+
relPath: rel,
|
|
287
|
+
detail: `${rel}: added under completed/ but unreadable`,
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (payload.kind === "unsafe") {
|
|
292
|
+
findings.push({
|
|
293
|
+
relPath: rel,
|
|
294
|
+
detail: payload.detail,
|
|
295
|
+
});
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const plan = parsePlan(payload.raw);
|
|
299
|
+
if (plan === null) {
|
|
300
|
+
findings.push({
|
|
301
|
+
relPath: rel,
|
|
302
|
+
detail: `${rel}: added under completed/ with unreadable plan`,
|
|
303
|
+
});
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (!hasTransitionWrite(plan)) {
|
|
307
|
+
findings.push({
|
|
308
|
+
relPath: rel,
|
|
309
|
+
detail: `${rel}: added under completed/ without a runTransition write`,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (findings.length === 0) {
|
|
314
|
+
return {
|
|
315
|
+
code: 0,
|
|
316
|
+
findings: [],
|
|
317
|
+
message: "verify_completed_write_guard: clean -- no unguarded completed/ adds",
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
code: 1,
|
|
322
|
+
findings,
|
|
323
|
+
message: `verify_completed_write_guard: ${findings.length} unguarded completed/ add(s) (#3679).\n` +
|
|
324
|
+
findings.map((f) => ` - ${f.detail}`).join("\n") +
|
|
325
|
+
`\n${LEFTOVER_LAND_PR_REMEDIATION}`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function listCompletedArtifactRels(projectRoot) {
|
|
329
|
+
const out = [];
|
|
330
|
+
for (const dirName of [MIGRATED_ARTIFACT_DIR, LEGACY_ARTIFACT_DIR]) {
|
|
331
|
+
const completedDir = join(projectRoot, dirName, "completed");
|
|
332
|
+
if (!existsSync(completedDir)) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
let names = [];
|
|
336
|
+
try {
|
|
337
|
+
names = readdirSync(completedDir).filter((n) => hasArtifactSuffix(n));
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
for (const name of names) {
|
|
343
|
+
out.push(`${dirName}/completed/${name}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return out.sort();
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Corpus scan for doctor: artifacts without transition evidence.
|
|
350
|
+
* Caller marks these advisory so historical pre-stamp files do not red doctor.
|
|
351
|
+
*/
|
|
352
|
+
export function scanCompletedWriteCorpus(projectRoot) {
|
|
353
|
+
const findings = [];
|
|
354
|
+
const rels = listCompletedArtifactRels(projectRoot);
|
|
355
|
+
for (const rel of rels) {
|
|
356
|
+
const payload = readPayload(resolve(projectRoot), rel, undefined);
|
|
357
|
+
if (payload.kind !== "ok") {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const plan = parsePlan(payload.raw);
|
|
361
|
+
if (plan === null) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (!hasTransitionWrite(plan)) {
|
|
365
|
+
findings.push({
|
|
366
|
+
relPath: rel,
|
|
367
|
+
detail: `${rel}: completed/ artifact has no runTransition write (historical advisory)`,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return { findings, scanned: rels.length };
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=completed-write-guard.js.map
|
package/dist/lifecycle/index.js
CHANGED
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
* so distinct execution contexts are not collapsed.
|
|
10
10
|
*/
|
|
11
11
|
import type { LiteralAcceptanceCommand, RejectedLiteralCommand } from "./types.js";
|
|
12
|
+
/** Reason stamped when an inline backtick is recorded as a mention, not AC (#3721). */
|
|
13
|
+
export declare const INLINE_PROSE_MENTION_REASON = "inline backtick in prose is a mention, not a stated acceptance command (#3721)";
|
|
14
|
+
/**
|
|
15
|
+
* True when a capture-only row came from an inline backtick in prose (#3721).
|
|
16
|
+
* Those mentions are not stated acceptance commands. Labeled, prompt, and fence
|
|
17
|
+
* spans stay stated.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isInlineProseMention(cmd: {
|
|
20
|
+
readonly source?: string;
|
|
21
|
+
readonly sourceSpan?: string | null;
|
|
22
|
+
}): boolean;
|
|
12
23
|
/** Result of capture including the operator-visible rejected ledger. */
|
|
13
24
|
export interface CaptureLiteralAcceptanceResult {
|
|
14
25
|
readonly commands: readonly LiteralAcceptanceCommand[];
|
|
@@ -57,6 +68,11 @@ export declare function isProseDerivedRejection(rejected: RejectedLiteralCommand
|
|
|
57
68
|
* prose-derived rejections no longer depends on this (#3511).
|
|
58
69
|
*/
|
|
59
70
|
export declare function hasStructuredAcceptanceCommands(plan: Record<string, unknown> | null | undefined): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Command strings the operator recorded as not acceptance commands (#3721).
|
|
73
|
+
* Listing a string here dispositions a capture without asserting it is AC.
|
|
74
|
+
*/
|
|
75
|
+
export declare function readNotAcceptanceCommands(plan: Record<string, unknown> | null | undefined): ReadonlySet<string>;
|
|
60
76
|
/** Format rejected ledger for CLI / complete-gate messages. */
|
|
61
77
|
export declare function formatRejectedLedger(rejected: readonly RejectedLiteralCommand[]): string;
|
|
62
78
|
//# sourceMappingURL=capture.d.ts.map
|