@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,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
4
|
-
import {
|
|
5
|
-
import { CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE } from "../triage/queue/constants.js";
|
|
4
|
+
import { ScmStubError } from "../scm/errors.js";
|
|
6
5
|
import { resolveRepo } from "../triage/queue/repo.js";
|
|
6
|
+
import { AGGREGATE_LATENCY_BUDGET_MS, formatAge, makeGateRunner, OpenIssueInventory, resolveIssueStateAggregate, resolveIssueStateScoped, SCOPED_LATENCY_BUDGET_MS, } from "./issue-state.js";
|
|
7
7
|
import { collectGithubRefs } from "./refs.js";
|
|
8
8
|
function readJson(path) {
|
|
9
9
|
try {
|
|
@@ -60,61 +60,62 @@ function listActiveRunningBriefs(projectRoot) {
|
|
|
60
60
|
}
|
|
61
61
|
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Records how each verdict was reached so the summary can distinguish a
|
|
65
|
+
* verified pass from an unverified one (#3767).
|
|
66
|
+
*/
|
|
67
|
+
class BasisTally {
|
|
68
|
+
inventory = 0;
|
|
69
|
+
live = 0;
|
|
70
|
+
cache = 0;
|
|
71
|
+
maxCacheAgeMs = 0;
|
|
72
|
+
unverifiedReasons = [];
|
|
73
|
+
record(ref, resolution) {
|
|
74
|
+
switch (resolution.basis) {
|
|
75
|
+
case "inventory":
|
|
76
|
+
this.inventory += 1;
|
|
77
|
+
break;
|
|
78
|
+
case "live":
|
|
79
|
+
this.live += 1;
|
|
80
|
+
break;
|
|
81
|
+
case "cache":
|
|
82
|
+
this.cache += 1;
|
|
83
|
+
this.maxCacheAgeMs = Math.max(this.maxCacheAgeMs, resolution.cacheAgeMs);
|
|
84
|
+
break;
|
|
85
|
+
default: {
|
|
86
|
+
const line = `#${ref.number} (${resolution.detail})`;
|
|
87
|
+
if (!this.unverifiedReasons.includes(line)) {
|
|
88
|
+
this.unverifiedReasons.push(line);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
82
92
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
function fetchIssueStateLive(ref, runGh) {
|
|
86
|
-
const path = `repos/${ref.repo}/issues/${ref.number}`;
|
|
87
|
-
const result = runGh(["gh", "api", path]);
|
|
88
|
-
if (result.returncode !== 0) {
|
|
89
|
-
return null;
|
|
93
|
+
get unverified() {
|
|
94
|
+
return this.unverifiedReasons.length;
|
|
90
95
|
}
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
96
|
+
summary() {
|
|
97
|
+
const parts = [];
|
|
98
|
+
if (this.inventory > 0) {
|
|
99
|
+
parts.push(`inventory ${this.inventory}`);
|
|
95
100
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return "closed";
|
|
101
|
+
if (this.live > 0) {
|
|
102
|
+
parts.push(`live ${this.live}`);
|
|
99
103
|
}
|
|
100
|
-
if (
|
|
101
|
-
|
|
104
|
+
if (this.cache > 0) {
|
|
105
|
+
parts.push(`cache ${this.cache} (max age ${formatAge(this.maxCacheAgeMs)})`);
|
|
102
106
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return
|
|
107
|
+
if (this.unverified > 0) {
|
|
108
|
+
parts.push(`unverified ${this.unverified}`);
|
|
109
|
+
}
|
|
110
|
+
return parts.length === 0 ? "no GitHub lookups" : parts.join(", ");
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
|
-
function resolveIssueState(ref,
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
return fetchIssueStateLive(ref, runGh);
|
|
113
|
+
function resolveIssueState(ref, ctx, scoped, tally) {
|
|
114
|
+
const resolution = scoped
|
|
115
|
+
? resolveIssueStateScoped(ref, ctx)
|
|
116
|
+
: resolveIssueStateAggregate(ref, ctx);
|
|
117
|
+
tally.record(ref, resolution);
|
|
118
|
+
return resolution.state;
|
|
118
119
|
}
|
|
119
120
|
function fetchPrMerged(ref, runGh) {
|
|
120
121
|
const path = `repos/${ref.repo}/pulls/${ref.number}`;
|
|
@@ -144,11 +145,18 @@ function shipped(reason) {
|
|
|
144
145
|
function unresolved(reason) {
|
|
145
146
|
return { orphaned: true, reason, kind: "unresolved" };
|
|
146
147
|
}
|
|
147
|
-
function assessOrphanSignature(issueRefs, prRefs,
|
|
148
|
+
function assessOrphanSignature(issueRefs, prRefs, ctx, selectedIssue, tally) {
|
|
149
|
+
const runGh = ctx.runGh;
|
|
150
|
+
const skipGh = ctx.skipGh;
|
|
151
|
+
const scoped = selectedIssue !== null;
|
|
148
152
|
if (selectedIssue !== null) {
|
|
149
|
-
// Confirmed closed origin is shipped even if a linked PR lookup fails.
|
|
150
153
|
const selectedRef = issueRefs.find((ref) => ref.number === selectedIssue);
|
|
151
|
-
|
|
154
|
+
if (selectedRef === undefined) {
|
|
155
|
+
// evaluate() only reaches here for briefs that name the issue.
|
|
156
|
+
return PASS;
|
|
157
|
+
}
|
|
158
|
+
// Confirmed closed origin is shipped even if a linked PR lookup fails.
|
|
159
|
+
const selectedState = resolveIssueState(selectedRef, ctx, scoped, tally);
|
|
152
160
|
if (selectedState === "closed") {
|
|
153
161
|
return shipped(`issue #${selectedIssue} is closed`);
|
|
154
162
|
}
|
|
@@ -165,25 +173,8 @@ function assessOrphanSignature(issueRefs, prRefs, projectRoot, runGh, skipGh, se
|
|
|
165
173
|
}
|
|
166
174
|
}
|
|
167
175
|
// --issue N is one origin: sibling open/unknown must not mask it (#3429).
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
return unresolved(`issue #${selectedIssue} state could not be resolved`);
|
|
171
|
-
}
|
|
172
|
-
return PASS;
|
|
173
|
-
}
|
|
174
|
-
const issueStates = issueRefs.map((ref) => ({
|
|
175
|
-
ref,
|
|
176
|
-
state: resolveIssueState(ref, projectRoot, runGh, skipGh),
|
|
177
|
-
}));
|
|
178
|
-
const unknownIssue = issueStates.find((row) => row.state === null);
|
|
179
|
-
if (unknownIssue !== undefined) {
|
|
180
|
-
return unresolved(`issue #${unknownIssue.ref.number} state could not be resolved`);
|
|
181
|
-
}
|
|
182
|
-
if (issueStates.some((row) => row.state === "closed")) {
|
|
183
|
-
return shipped("all referenced issues are closed");
|
|
184
|
-
}
|
|
185
|
-
if (prRefs.length > 0 && skipGh) {
|
|
186
|
-
return unresolved(`linked PR #${prRefs[0]?.number} state could not be resolved`);
|
|
176
|
+
if (selectedState === null) {
|
|
177
|
+
return unresolved(`issue #${selectedIssue} state could not be resolved`);
|
|
187
178
|
}
|
|
188
179
|
return PASS;
|
|
189
180
|
}
|
|
@@ -199,26 +190,37 @@ function assessOrphanSignature(issueRefs, prRefs, projectRoot, runGh, skipGh, se
|
|
|
199
190
|
if (issueRefs.length === 0) {
|
|
200
191
|
return PASS;
|
|
201
192
|
}
|
|
202
|
-
let resolved = 0;
|
|
203
193
|
for (const ref of issueRefs) {
|
|
204
|
-
const state = resolveIssueState(ref,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
resolved += 1;
|
|
194
|
+
const state = resolveIssueState(ref, ctx, scoped, tally);
|
|
195
|
+
// Unknown keeps the sweep fail-open so offline work is not network-authorized;
|
|
196
|
+
// the unverified basis line stops the pass reading as verified evidence (#3767).
|
|
209
197
|
if (state !== "closed") {
|
|
210
198
|
return PASS;
|
|
211
199
|
}
|
|
212
200
|
}
|
|
213
|
-
|
|
214
|
-
return shipped("all referenced issues are closed");
|
|
215
|
-
}
|
|
216
|
-
return PASS;
|
|
201
|
+
return shipped("all referenced issues are closed");
|
|
217
202
|
}
|
|
218
203
|
function briefReferencesIssue(issues, issue) {
|
|
219
204
|
return issues.some((ref) => ref.number === issue);
|
|
220
205
|
}
|
|
221
|
-
|
|
206
|
+
/** Basis, ghx caveat, and budget lines shared by the pass and refusal messages (#3767). */
|
|
207
|
+
function basisLines(tally, basis) {
|
|
208
|
+
const lines = [` Basis: ${tally.summary()}.`];
|
|
209
|
+
if (tally.unverified > 0) {
|
|
210
|
+
lines.push(" UNVERIFIED: state could not be established for the references below, so this run is", " not evidence that they are unshipped:");
|
|
211
|
+
for (const reason of tally.unverifiedReasons) {
|
|
212
|
+
lines.push(` - ${reason}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (basis.proxied) {
|
|
216
|
+
lines.push(" Note: reads resolved through `ghx`, a cached GET proxy; freshness is bounded by that", " proxy, which this gate cannot inspect (#3737).");
|
|
217
|
+
}
|
|
218
|
+
if (basis.elapsedMs > basis.budgetMs) {
|
|
219
|
+
lines.push(` Budget: took ${formatAge(basis.elapsedMs)} against a ${formatAge(basis.budgetMs)} budget (#3767).`);
|
|
220
|
+
}
|
|
221
|
+
return lines;
|
|
222
|
+
}
|
|
223
|
+
function formatRefusal(orphans, projectRoot, issueFilter, tally, basis) {
|
|
222
224
|
const shippedOrphans = orphans.filter((orphan) => orphan.kind === "shipped");
|
|
223
225
|
const unresolvedOrphans = orphans.filter((orphan) => orphan.kind === "unresolved");
|
|
224
226
|
const noun = orphans.length === 1 ? "" : "s";
|
|
@@ -250,27 +252,49 @@ function formatRefusal(orphans, projectRoot, issueFilter) {
|
|
|
250
252
|
for (const orphan of orphans) {
|
|
251
253
|
lines.push(` - ${orphan.path} (${orphan.reason})`);
|
|
252
254
|
}
|
|
255
|
+
lines.push(...basisLines(tally, basis));
|
|
253
256
|
return lines.join("\n");
|
|
254
257
|
}
|
|
258
|
+
function emptyBasis(budgetMs) {
|
|
259
|
+
return {
|
|
260
|
+
inventory: 0,
|
|
261
|
+
live: 0,
|
|
262
|
+
cache: 0,
|
|
263
|
+
unverified: 0,
|
|
264
|
+
maxCacheAgeMs: null,
|
|
265
|
+
proxied: false,
|
|
266
|
+
elapsedMs: 0,
|
|
267
|
+
budgetMs,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
255
270
|
/**
|
|
256
271
|
* Pure evaluator for orphaned active/running xBRIEF detection (#2321 / #3429).
|
|
257
272
|
* Fails when active/ briefs with plan.status==running reference only closed
|
|
258
273
|
* issues and/or a merged PR — the stop-at:pr-open orphan signature.
|
|
259
274
|
* Pass `issue` to scan one origin after merge. Confirmed shipped prints
|
|
260
275
|
* scope:complete; unresolved lookup still exits 1 with a retry remediation.
|
|
276
|
+
*
|
|
277
|
+
* Issue state resolves by query shape (#3767): scoped `--issue N` takes an
|
|
278
|
+
* authoritative read and stays fail-closed on unknown; the unscoped sweep uses
|
|
279
|
+
* one complete, fail-closed open-issue inventory and stays fail-open on
|
|
280
|
+
* unknown so offline work is not network-authorized. A cache hit is honoured
|
|
281
|
+
* only inside `ISSUE_CACHE_MAX_AGE_MS`, and every verdict reports its basis.
|
|
261
282
|
*/
|
|
262
283
|
export function evaluate(projectRoot, options = {}) {
|
|
263
284
|
const root = resolve(projectRoot);
|
|
264
285
|
const quiet = options.quiet ?? false;
|
|
265
286
|
const skipGh = options.skipGh ?? false;
|
|
266
|
-
const
|
|
267
|
-
const
|
|
287
|
+
const issueFilter = options.issue ?? null;
|
|
288
|
+
const budgetMs = issueFilter === null ? AGGREGATE_LATENCY_BUDGET_MS : SCOPED_LATENCY_BUDGET_MS;
|
|
289
|
+
const clock = options.nowMs ?? Date.now;
|
|
290
|
+
const startedMs = clock();
|
|
268
291
|
if (!existsSync(root)) {
|
|
269
292
|
return {
|
|
270
293
|
code: 2,
|
|
271
294
|
message: `verify:orphan-active: project root does not exist: ${root}`,
|
|
272
295
|
stream: "stderr",
|
|
273
296
|
orphans: [],
|
|
297
|
+
basis: emptyBasis(budgetMs),
|
|
274
298
|
};
|
|
275
299
|
}
|
|
276
300
|
let lifecycleRoot;
|
|
@@ -282,14 +306,12 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
282
306
|
// Consumer/greenfield fixtures may still use legacy vbrief/ only (#2112).
|
|
283
307
|
// Orphan detection applies only to xbrief/active/ — skip cleanly, not config fail.
|
|
284
308
|
if (message.includes("No xbrief/ layout found")) {
|
|
285
|
-
if (quiet) {
|
|
286
|
-
return { code: 0, message: "", stream: "none", orphans: [] };
|
|
287
|
-
}
|
|
288
309
|
return {
|
|
289
310
|
code: 0,
|
|
290
|
-
message: "verify:orphan-active: no xbrief/ lifecycle root; nothing to scan.",
|
|
291
|
-
stream: "stdout",
|
|
311
|
+
message: quiet ? "" : "verify:orphan-active: no xbrief/ lifecycle root; nothing to scan.",
|
|
312
|
+
stream: quiet ? "none" : "stdout",
|
|
292
313
|
orphans: [],
|
|
314
|
+
basis: emptyBasis(budgetMs),
|
|
293
315
|
};
|
|
294
316
|
}
|
|
295
317
|
return {
|
|
@@ -297,58 +319,101 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
297
319
|
message: `verify:orphan-active: ${message}`,
|
|
298
320
|
stream: "stderr",
|
|
299
321
|
orphans: [],
|
|
322
|
+
basis: emptyBasis(budgetMs),
|
|
300
323
|
};
|
|
301
324
|
}
|
|
302
325
|
if (!existsSync(lifecycleRoot)) {
|
|
303
|
-
if (quiet) {
|
|
304
|
-
return { code: 0, message: "", stream: "none", orphans: [] };
|
|
305
|
-
}
|
|
306
326
|
return {
|
|
307
327
|
code: 0,
|
|
308
|
-
message: "verify:orphan-active: no xbrief/ lifecycle root; nothing to scan.",
|
|
309
|
-
stream: "stdout",
|
|
328
|
+
message: quiet ? "" : "verify:orphan-active: no xbrief/ lifecycle root; nothing to scan.",
|
|
329
|
+
stream: quiet ? "none" : "stdout",
|
|
310
330
|
orphans: [],
|
|
331
|
+
basis: emptyBasis(budgetMs),
|
|
311
332
|
};
|
|
312
333
|
}
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
for (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
334
|
+
const defaultRepo = resolveRepo(options.repo, root);
|
|
335
|
+
// Resolve the SCM binary lazily: an offline `--skip-gh` run or an empty
|
|
336
|
+
// active/ must not pay for (or fail on) binary resolution it never uses.
|
|
337
|
+
let runner = options.runGh === undefined ? null : { runGh: options.runGh, proxied: false };
|
|
338
|
+
const runGh = (cmd) => {
|
|
339
|
+
runner ??= makeGateRunner();
|
|
340
|
+
return runner.runGh(cmd);
|
|
341
|
+
};
|
|
342
|
+
const tally = new BasisTally();
|
|
343
|
+
const ctx = {
|
|
344
|
+
projectRoot: root,
|
|
345
|
+
runGh,
|
|
346
|
+
skipGh,
|
|
347
|
+
nowMs: clock(),
|
|
348
|
+
inventory: new OpenIssueInventory(runGh),
|
|
349
|
+
};
|
|
350
|
+
try {
|
|
351
|
+
// #3774: missing gh/ghx is config (code 2), not an uncaught throw.
|
|
352
|
+
const orphans = [];
|
|
353
|
+
let scanned = 0;
|
|
354
|
+
for (const brief of listActiveRunningBriefs(root)) {
|
|
355
|
+
scanned += 1;
|
|
356
|
+
const { issues, prs } = collectGithubRefs(brief.plan, defaultRepo);
|
|
357
|
+
// --issue N is one origin: briefs that name that issue. PR-only briefs stay on the unscoped scan (#3429).
|
|
358
|
+
if (issueFilter !== null && !briefReferencesIssue(issues, issueFilter)) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (issues.length === 0 && prs.length === 0) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const assessment = assessOrphanSignature(issues, prs, ctx, issueFilter, tally);
|
|
365
|
+
if (assessment.orphaned && assessment.reason !== null && assessment.kind !== null) {
|
|
366
|
+
orphans.push({
|
|
367
|
+
path: relBriefPath(brief.path, root),
|
|
368
|
+
reason: assessment.reason,
|
|
369
|
+
kind: assessment.kind,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
320
372
|
}
|
|
321
|
-
|
|
322
|
-
|
|
373
|
+
const basis = {
|
|
374
|
+
inventory: tally.inventory,
|
|
375
|
+
live: tally.live,
|
|
376
|
+
cache: tally.cache,
|
|
377
|
+
unverified: tally.unverified,
|
|
378
|
+
maxCacheAgeMs: tally.cache > 0 ? tally.maxCacheAgeMs : null,
|
|
379
|
+
proxied: runner?.proxied ?? false,
|
|
380
|
+
elapsedMs: Math.max(0, clock() - startedMs),
|
|
381
|
+
budgetMs,
|
|
382
|
+
};
|
|
383
|
+
if (orphans.length > 0) {
|
|
384
|
+
return {
|
|
385
|
+
code: 1,
|
|
386
|
+
message: formatRefusal(orphans, root, issueFilter, tally, basis),
|
|
387
|
+
stream: "stderr",
|
|
388
|
+
orphans,
|
|
389
|
+
basis,
|
|
390
|
+
};
|
|
323
391
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
orphans.push({
|
|
327
|
-
path: relBriefPath(brief.path, root),
|
|
328
|
-
reason: assessment.reason,
|
|
329
|
-
kind: assessment.kind,
|
|
330
|
-
});
|
|
392
|
+
if (quiet) {
|
|
393
|
+
return { code: 0, message: "", stream: "none", orphans: [], basis };
|
|
331
394
|
}
|
|
332
|
-
|
|
333
|
-
|
|
395
|
+
const issueNote = issueFilter === null ? "" : ` for issue #${issueFilter}`;
|
|
396
|
+
const headline = `verify:orphan-active: no orphaned active/running xBRIEFs${issueNote} ` +
|
|
397
|
+
`(scanned ${scanned} running brief${scanned === 1 ? "" : "s"} in active/).`;
|
|
334
398
|
return {
|
|
335
|
-
code:
|
|
336
|
-
message:
|
|
337
|
-
stream: "
|
|
338
|
-
orphans,
|
|
399
|
+
code: 0,
|
|
400
|
+
message: [headline, ...basisLines(tally, basis)].join("\n"),
|
|
401
|
+
stream: "stdout",
|
|
402
|
+
orphans: [],
|
|
403
|
+
basis,
|
|
339
404
|
};
|
|
340
405
|
}
|
|
341
|
-
|
|
342
|
-
|
|
406
|
+
catch (err) {
|
|
407
|
+
if (err instanceof ScmStubError) {
|
|
408
|
+
return {
|
|
409
|
+
code: 2,
|
|
410
|
+
message: `verify:orphan-active: ${err.message}`,
|
|
411
|
+
stream: "stderr",
|
|
412
|
+
orphans: [],
|
|
413
|
+
basis: emptyBasis(budgetMs),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
throw err;
|
|
343
417
|
}
|
|
344
|
-
const scanned = listActiveRunningBriefs(root).length;
|
|
345
|
-
const issueNote = issueFilter === null ? "" : ` for issue #${issueFilter}`;
|
|
346
|
-
return {
|
|
347
|
-
code: 0,
|
|
348
|
-
message: `verify:orphan-active: no orphaned active/running xBRIEFs${issueNote} ` +
|
|
349
|
-
`(scanned ${scanned} running brief${scanned === 1 ? "" : "s"} in active/).`,
|
|
350
|
-
stream: "stdout",
|
|
351
|
-
orphans: [],
|
|
352
|
-
};
|
|
353
418
|
}
|
|
354
419
|
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { RunGhFn } from "../pr-protected-issues/types.js";
|
|
2
|
+
import type { IssueRef } from "./refs.js";
|
|
3
|
+
export type IssueState = "open" | "closed";
|
|
4
|
+
/**
|
|
5
|
+
* How a verdict was reached (#3767). `unverified` means the gate could not
|
|
6
|
+
* establish state, so a pass on that reference proves nothing.
|
|
7
|
+
*/
|
|
8
|
+
export type StateBasis = "inventory" | "live" | "cache" | "unverified";
|
|
9
|
+
export type StateResolution =
|
|
10
|
+
/** Resolved from the complete open-issue inventory or an authoritative read. */
|
|
11
|
+
{
|
|
12
|
+
readonly state: IssueState;
|
|
13
|
+
readonly basis: "inventory" | "live";
|
|
14
|
+
}
|
|
15
|
+
/** Resolved from a triage-cache entry inside the freshness bound. */
|
|
16
|
+
| {
|
|
17
|
+
readonly state: IssueState;
|
|
18
|
+
readonly basis: "cache";
|
|
19
|
+
readonly cacheAgeMs: number;
|
|
20
|
+
}
|
|
21
|
+
/** State could not be established; a pass on this reference proves nothing. */
|
|
22
|
+
| {
|
|
23
|
+
readonly state: null;
|
|
24
|
+
readonly basis: "unverified";
|
|
25
|
+
readonly detail: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A cache hit older than this is no longer evidence (#3767). The measured
|
|
29
|
+
* defect was a ~12 h old cached `open` suppressing the live read that would
|
|
30
|
+
* have corrected it; 15 min keeps warm-cache offline runs working while making
|
|
31
|
+
* an overnight entry non-authoritative.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ISSUE_CACHE_MAX_AGE_MS: number;
|
|
34
|
+
/**
|
|
35
|
+
* Latency budget for scoped `--issue N`: one authoritative issue read plus at
|
|
36
|
+
* most one linked-PR read. Measured ~0.75 s per `gh api` read; the headroom
|
|
37
|
+
* covers a slow network or a `ghx` proxy hop.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SCOPED_LATENCY_BUDGET_MS = 5000;
|
|
40
|
+
/**
|
|
41
|
+
* Latency budget for the unscoped aggregate sweep. One complete open-issue
|
|
42
|
+
* inventory measured 4.2 s constant on deftai/directive (560 open issues),
|
|
43
|
+
* against 14.2 s and 76.6 s for per-brief reads at the WIP cap of 20 on two
|
|
44
|
+
* hosts. The budget is sized for the inventory plus a few confirming reads and
|
|
45
|
+
* keeps the gate inside the fast-preflight tier.
|
|
46
|
+
*/
|
|
47
|
+
export declare const AGGREGATE_LATENCY_BUDGET_MS = 15000;
|
|
48
|
+
export interface GateRunner {
|
|
49
|
+
readonly runGh: RunGhFn;
|
|
50
|
+
/**
|
|
51
|
+
* True when reads resolve through `ghx`, a cached GET proxy. The freshness
|
|
52
|
+
* guarantee is then bounded by that proxy, which this gate cannot inspect.
|
|
53
|
+
*/
|
|
54
|
+
readonly proxied: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Pin plain `gh` for this gate's authoritative reads (#3767). `resolveBinary`
|
|
58
|
+
* prefers `ghx` — a cached GET proxy — so an unpinned "live" read is itself a
|
|
59
|
+
* cache. When `gh` is absent we still run, and report the proxy caveat rather
|
|
60
|
+
* than claiming freshness we cannot deliver. Whether `ghx` should be preferred
|
|
61
|
+
* at all is #3737.
|
|
62
|
+
*/
|
|
63
|
+
export declare function makeGateRunner(): GateRunner;
|
|
64
|
+
interface CachedIssue {
|
|
65
|
+
readonly state: IssueState | null;
|
|
66
|
+
readonly ageMs: number | null;
|
|
67
|
+
}
|
|
68
|
+
/** Read the triage-cache issue state together with the entry's age (#3767). */
|
|
69
|
+
export declare function readCachedIssue(projectRoot: string, ref: IssueRef, nowMs: number): CachedIssue;
|
|
70
|
+
/** Authoritative single-issue REST read. Returns `null` on any failure. */
|
|
71
|
+
export declare function fetchIssueStateLive(ref: IssueRef, runGh: RunGhFn): IssueState | null;
|
|
72
|
+
type InventoryLookup = {
|
|
73
|
+
numbers: ReadonlySet<number>;
|
|
74
|
+
} | {
|
|
75
|
+
error: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* One complete open-issue inventory per repo per run (#3767, reusing #3752).
|
|
79
|
+
* `restIssueListOpenInventory` fails closed on command failure, non-array JSON,
|
|
80
|
+
* malformed rows, buffer exhaustion, and pagination cap — so absence from a
|
|
81
|
+
* successful inventory really does mean "not open", and a truncated read is an
|
|
82
|
+
* error rather than a silent "closed".
|
|
83
|
+
*/
|
|
84
|
+
export declare class OpenIssueInventory {
|
|
85
|
+
private readonly runGh;
|
|
86
|
+
private readonly byRepo;
|
|
87
|
+
private readonly confirmations;
|
|
88
|
+
constructor(runGh: RunGhFn);
|
|
89
|
+
lookup(repo: string): InventoryLookup;
|
|
90
|
+
/**
|
|
91
|
+
* Confirm an inventory absence with one authoritative read, reused for the
|
|
92
|
+
* rest of this run when several briefs name the same issue (#3767).
|
|
93
|
+
*/
|
|
94
|
+
confirm(ref: IssueRef): IssueState | null;
|
|
95
|
+
private fetch;
|
|
96
|
+
}
|
|
97
|
+
export interface ResolveContext {
|
|
98
|
+
readonly projectRoot: string;
|
|
99
|
+
readonly runGh: RunGhFn;
|
|
100
|
+
readonly skipGh: boolean;
|
|
101
|
+
readonly nowMs: number;
|
|
102
|
+
readonly inventory: OpenIssueInventory;
|
|
103
|
+
}
|
|
104
|
+
/** Human-readable duration for basis reporting: `42s`, `9m`, `12h`, `3d`. */
|
|
105
|
+
export declare function formatAge(ms: number): string;
|
|
106
|
+
/**
|
|
107
|
+
* Scoped `--issue N`: a proof obligation before DONE, so read authoritatively
|
|
108
|
+
* first and accept a cache hit only inside the freshness bound. Unknown stays
|
|
109
|
+
* fail-closed at the call site.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resolveIssueStateScoped(ref: IssueRef, ctx: ResolveContext): StateResolution;
|
|
112
|
+
/**
|
|
113
|
+
* Unscoped aggregate: resolve every reference from one complete open-issue
|
|
114
|
+
* inventory rather than a live read per brief. Membership means open. Absence
|
|
115
|
+
* means "not open", which is the direction that tells an operator to run
|
|
116
|
+
* `scope:complete`, so it is confirmed by one authoritative read before the
|
|
117
|
+
* gate acts on it.
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveIssueStateAggregate(ref: IssueRef, ctx: ResolveContext): StateResolution;
|
|
120
|
+
export {};
|
|
121
|
+
//# sourceMappingURL=issue-state.d.ts.map
|