@deftai/directive-core 0.108.0 → 0.109.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/cache/operations.js +1 -1
- package/dist/check/gate-lists.d.ts +14 -0
- package/dist/check/gate-lists.js +26 -3
- package/dist/check/session-completed-ac.d.ts +1 -1
- package/dist/check/session-completed-ac.js +1 -1
- package/dist/consumer-check-contract/evaluate.d.ts +47 -0
- package/dist/consumer-check-contract/evaluate.js +181 -15
- package/dist/delivery-attempt/evaluate.d.ts +9 -1
- package/dist/delivery-attempt/evaluate.js +69 -0
- package/dist/delivery-attempt/index.d.ts +1 -1
- package/dist/delivery-attempt/index.js +1 -1
- package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
- package/dist/deposit/live-procedure-exclusions.js +110 -0
- package/dist/deposit/live-procedure-targets.d.ts +45 -0
- package/dist/deposit/live-procedure-targets.js +274 -0
- package/dist/deposit/python-free.d.ts +6 -0
- package/dist/deposit/python-free.js +15 -0
- package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
- package/dist/deposit/rewrite-deposit-links.js +148 -0
- package/dist/deposit/run-stage-content-pack.d.ts +2 -0
- package/dist/deposit/run-stage-content-pack.js +3 -0
- package/dist/deposit/stage-content-pack.d.ts +17 -0
- package/dist/deposit/stage-content-pack.js +91 -0
- package/dist/design-critique/citation-grammar.d.ts +7 -0
- package/dist/design-critique/citation-grammar.js +1 -1
- package/dist/design-critique/completed-arc-record.d.ts +1 -1
- package/dist/design-critique/completed-arc-record.js +59 -14
- package/dist/hooks/classify/host-session-identity.d.ts +24 -11
- package/dist/hooks/classify/host-session-identity.js +46 -49
- package/dist/hooks/classify/index.d.ts +2 -2
- package/dist/hooks/classify/index.js +2 -2
- package/dist/hooks/classify/paths.d.ts +2 -0
- package/dist/hooks/classify/paths.js +8 -4
- package/dist/hooks/classify/stdin.js +69 -3
- package/dist/hooks/dest-form.d.ts +20 -1
- package/dist/hooks/dest-form.js +158 -21
- package/dist/hooks/dispatcher.d.ts +6 -2
- package/dist/hooks/dispatcher.js +258 -62
- package/dist/hooks/fixtures/cases.d.ts +6 -0
- package/dist/hooks/fixtures/cases.js +53 -0
- package/dist/hooks/git-destructive-log.d.ts +32 -0
- package/dist/hooks/git-destructive-log.js +46 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/shell-write-targets.d.ts +10 -0
- package/dist/hooks/shell-write-targets.js +274 -0
- package/dist/hooks/tools.d.ts +20 -2
- package/dist/hooks/tools.js +30 -1
- package/dist/init-deposit/gitignore.d.ts +7 -0
- package/dist/init-deposit/gitignore.js +24 -0
- package/dist/init-deposit/init-deposit.js +3 -0
- package/dist/init-deposit/refresh.js +7 -0
- package/dist/init-deposit/runtime-writers.d.ts +14 -0
- package/dist/init-deposit/runtime-writers.js +33 -0
- package/dist/intake/issue-ingest.d.ts +6 -1
- package/dist/intake/issue-ingest.js +21 -2
- package/dist/intake/reconcile-issues.js +13 -13
- package/dist/lifecycle/brief-envelope.d.ts +25 -0
- package/dist/lifecycle/brief-envelope.js +42 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/evaluate.js +14 -5
- package/dist/literal-acceptance/index.d.ts +1 -1
- package/dist/literal-acceptance/index.js +1 -1
- package/dist/literal-acceptance/run.d.ts +2 -0
- package/dist/literal-acceptance/run.js +19 -1
- package/dist/orphan-active/candidate-scope.d.ts +53 -0
- package/dist/orphan-active/candidate-scope.js +157 -0
- package/dist/orphan-active/evaluate.d.ts +27 -0
- package/dist/orphan-active/evaluate.js +63 -6
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/policy/merge-approval-head.js +7 -6
- package/dist/pr-closing-keywords/gh.js +32 -9
- package/dist/pr-closing-keywords/main.d.ts +1 -0
- package/dist/pr-closing-keywords/main.js +68 -3
- package/dist/pr-closing-keywords/types.d.ts +2 -0
- package/dist/pr-merge-readiness/gh.js +32 -9
- package/dist/pr-protected-issues/gh.js +36 -9
- package/dist/pr-wait-mergeable/wrappers.js +3 -3
- package/dist/product-first-done-gate/evaluate.js +6 -2
- package/dist/product-first-done-gate/types.js +2 -0
- package/dist/release/consumer-hard-stops.d.ts +48 -0
- package/dist/release/consumer-hard-stops.js +140 -0
- package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
- package/dist/release/consumer-readiness-disclosure.js +51 -0
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/pipeline.js +55 -0
- package/dist/release/run-consumer-readiness.d.ts +15 -0
- package/dist/release/run-consumer-readiness.js +24 -0
- package/dist/release/types.d.ts +6 -0
- package/dist/review-monitor/github-lease.js +4 -3
- package/dist/run-summary/types.d.ts +2 -2
- package/dist/scm/build-command.d.ts +2 -2
- package/dist/scm/build-command.js +2 -2
- package/dist/scm/call-shape.d.ts +25 -0
- package/dist/scm/call-shape.js +59 -0
- package/dist/scm/call.d.ts +7 -4
- package/dist/scm/call.js +41 -11
- package/dist/scm/gh-rest.d.ts +25 -12
- package/dist/scm/gh-rest.js +65 -15
- package/dist/scm/index.d.ts +2 -0
- package/dist/scm/index.js +2 -0
- package/dist/scm/spawn-status.d.ts +33 -0
- package/dist/scm/spawn-status.js +53 -0
- package/dist/scope/acceptance-evidence.d.ts +1 -1
- package/dist/scope/transition.js +4 -14
- package/dist/session/ac-pass-banking.d.ts +2 -2
- package/dist/session/ac-pass-banking.js +2 -2
- package/dist/session/host-session-owner.d.ts +53 -0
- package/dist/session/host-session-owner.js +89 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/occupancy.d.ts +21 -1
- package/dist/session/occupancy.js +70 -12
- package/dist/session/verify-ac-session-cache.d.ts +2 -2
- package/dist/session/verify-ac-session-cache.js +2 -2
- package/dist/triage/evaluate/worktrees.js +153 -6
- package/dist/umbrella-current-shape/index.d.ts +51 -3
- package/dist/umbrella-current-shape/index.js +106 -18
- package/dist/validate-content/deposit-required.d.ts +39 -0
- package/dist/validate-content/deposit-required.js +147 -0
- package/dist/validate-content/index.d.ts +1 -0
- package/dist/validate-content/index.js +1 -0
- package/dist/validate-content/validate-links.d.ts +2 -3
- package/dist/validate-content/validate-links.js +29 -2
- package/dist/vbrief-activate/activate.d.ts +7 -2
- package/dist/vbrief-activate/activate.js +26 -13
- package/dist/verify-ac/evaluate.d.ts +9 -0
- package/dist/verify-ac/evaluate.js +32 -9
- package/dist/verify-source/deposit-closure.d.ts +23 -0
- package/dist/verify-source/deposit-closure.js +162 -0
- package/dist/verify-source/index.d.ts +2 -0
- package/dist/verify-source/index.js +2 -0
- package/dist/verify-source/semantic-single-source.d.ts +36 -0
- package/dist/verify-source/semantic-single-source.js +349 -0
- package/package.json +3 -3
|
@@ -3,12 +3,12 @@ import { mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSy
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { assertProjectionContained, assertWriteTargetSafe, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
5
5
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
6
|
+
import { missingEnvelopeMessage, stampExistingEnvelopes } from "../lifecycle/brief-envelope.js";
|
|
6
7
|
import { call } from "../scm/call.js";
|
|
7
8
|
import { updateDecomposedChildBackReferences } from "../scope/decomposed-refs.js";
|
|
8
9
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
9
10
|
import { resolveProjectRepo } from "../slice/project-context.js";
|
|
10
11
|
import { FOLDER_ALLOWED_STATUSES } from "../vbrief-validate/constants.js";
|
|
11
|
-
import { LEGACY_INFO_ROOT_KEY, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
12
12
|
export const LIFECYCLE_FOLDERS = [
|
|
13
13
|
"proposed",
|
|
14
14
|
"pending",
|
|
@@ -713,14 +713,13 @@ export function applyLifecycleFixes(vbriefDir, report, projectRoot = null) {
|
|
|
713
713
|
plan.status = terminalStatus;
|
|
714
714
|
const stamp = utcNowIso();
|
|
715
715
|
// Stamp `updated` into whichever info envelope the file already uses (#2346).
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
info.updated = stamp;
|
|
716
|
+
// A file carrying neither envelope is already invalid; creating one here hid
|
|
717
|
+
// that cause behind a manufactured version-less `vBRIEFInfo` (#3933), so the
|
|
718
|
+
// sweep now refuses it by name and leaves the artifact in place.
|
|
719
|
+
if (stampExistingEnvelopes(data, stamp).length === 0) {
|
|
720
|
+
failures.push(missingEnvelopeMessage(relPath));
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
724
723
|
plan.updated = stamp;
|
|
725
724
|
propagateItemStatus(plan.items, terminalStatus, stamp);
|
|
726
725
|
try {
|
|
@@ -780,12 +779,13 @@ export function repairCompletedStatusDrift(vbriefDir, drift, projectRoot = null)
|
|
|
780
779
|
continue;
|
|
781
780
|
}
|
|
782
781
|
const stamp = utcNowIso();
|
|
782
|
+
// Same envelope policy as the closed-issue sweep above (#3933).
|
|
783
|
+
if (stampExistingEnvelopes(data, stamp).length === 0) {
|
|
784
|
+
failures.push(missingEnvelopeMessage(entry.rel_path));
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
783
787
|
plan.status = "completed";
|
|
784
788
|
plan.updated = stamp;
|
|
785
|
-
const infoKey = MIGRATED_INFO_ROOT_KEY in data ? MIGRATED_INFO_ROOT_KEY : LEGACY_INFO_ROOT_KEY;
|
|
786
|
-
const info = (data[infoKey] ?? {});
|
|
787
|
-
data[infoKey] = info;
|
|
788
|
-
info.updated = stamp;
|
|
789
789
|
propagateItemStatus(plan.items, "completed", stamp);
|
|
790
790
|
try {
|
|
791
791
|
writeFileSync(src, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared document-envelope policy for lifecycle mutators (#3933).
|
|
3
|
+
*
|
|
4
|
+
* A brief carries one document envelope: `xBRIEFInfo` on canonical v0.8
|
|
5
|
+
* artifacts, `vBRIEFInfo` on legacy v0.6 ones. A mutator stamps whichever
|
|
6
|
+
* envelope the artifact already has and creates none. Creating one on a v0.8
|
|
7
|
+
* brief appends a version-less `vBRIEFInfo`, and `validateVbriefSchema`
|
|
8
|
+
* resolves `vBRIEFInfo` first, so the manufactured block wins and the brief the
|
|
9
|
+
* mutator just wrote fails validation (#2346 / #2862 / #3933).
|
|
10
|
+
*/
|
|
11
|
+
import { LEGACY_INFO_ROOT_KEY, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
12
|
+
export type BriefEnvelopeKey = typeof MIGRATED_INFO_ROOT_KEY | typeof LEGACY_INFO_ROOT_KEY;
|
|
13
|
+
/** Envelope keys in canonical order: v0.8 first, legacy v0.6 second. */
|
|
14
|
+
export declare const BRIEF_ENVELOPE_KEYS: readonly BriefEnvelopeKey[];
|
|
15
|
+
/** Envelope keys the artifact already carries as JSON objects. */
|
|
16
|
+
export declare function presentEnvelopeKeys(data: Record<string, unknown>): BriefEnvelopeKey[];
|
|
17
|
+
/**
|
|
18
|
+
* Stamp `updated` on every envelope the artifact already carries; create none.
|
|
19
|
+
* Returns the stamped keys so a caller can refuse an envelope-less artifact by
|
|
20
|
+
* name instead of manufacturing one.
|
|
21
|
+
*/
|
|
22
|
+
export declare function stampExistingEnvelopes(data: Record<string, unknown>, nowIso: string): BriefEnvelopeKey[];
|
|
23
|
+
/** Refusal naming both accepted envelopes, for a caller-supplied subject (#3933). */
|
|
24
|
+
export declare function missingEnvelopeMessage(subject: string): string;
|
|
25
|
+
//# sourceMappingURL=brief-envelope.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared document-envelope policy for lifecycle mutators (#3933).
|
|
3
|
+
*
|
|
4
|
+
* A brief carries one document envelope: `xBRIEFInfo` on canonical v0.8
|
|
5
|
+
* artifacts, `vBRIEFInfo` on legacy v0.6 ones. A mutator stamps whichever
|
|
6
|
+
* envelope the artifact already has and creates none. Creating one on a v0.8
|
|
7
|
+
* brief appends a version-less `vBRIEFInfo`, and `validateVbriefSchema`
|
|
8
|
+
* resolves `vBRIEFInfo` first, so the manufactured block wins and the brief the
|
|
9
|
+
* mutator just wrote fails validation (#2346 / #2862 / #3933).
|
|
10
|
+
*/
|
|
11
|
+
import { LEGACY_INFO_ROOT_KEY, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
12
|
+
/** Envelope keys in canonical order: v0.8 first, legacy v0.6 second. */
|
|
13
|
+
export const BRIEF_ENVELOPE_KEYS = [
|
|
14
|
+
MIGRATED_INFO_ROOT_KEY,
|
|
15
|
+
LEGACY_INFO_ROOT_KEY,
|
|
16
|
+
];
|
|
17
|
+
/** Envelope keys the artifact already carries as JSON objects. */
|
|
18
|
+
export function presentEnvelopeKeys(data) {
|
|
19
|
+
return BRIEF_ENVELOPE_KEYS.filter((key) => {
|
|
20
|
+
const env = data[key];
|
|
21
|
+
return typeof env === "object" && env !== null && !Array.isArray(env);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Stamp `updated` on every envelope the artifact already carries; create none.
|
|
26
|
+
* Returns the stamped keys so a caller can refuse an envelope-less artifact by
|
|
27
|
+
* name instead of manufacturing one.
|
|
28
|
+
*/
|
|
29
|
+
export function stampExistingEnvelopes(data, nowIso) {
|
|
30
|
+
const stamped = presentEnvelopeKeys(data);
|
|
31
|
+
for (const key of stamped) {
|
|
32
|
+
data[key].updated = nowIso;
|
|
33
|
+
}
|
|
34
|
+
return stamped;
|
|
35
|
+
}
|
|
36
|
+
/** Refusal naming both accepted envelopes, for a caller-supplied subject (#3933). */
|
|
37
|
+
export function missingEnvelopeMessage(subject) {
|
|
38
|
+
return (`${subject} carries neither \`${MIGRATED_INFO_ROOT_KEY}\` (v0.8) nor ` +
|
|
39
|
+
`\`${LEGACY_INFO_ROOT_KEY}\` (v0.6) -- malformed. Lifecycle mutators refuse ` +
|
|
40
|
+
"rather than create one (#3933).");
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=brief-envelope.js.map
|
package/dist/lifecycle/index.js
CHANGED
|
@@ -189,16 +189,25 @@ export function evaluateLiteralAcceptanceFromPlan(plan, options = {}) {
|
|
|
189
189
|
// Rejected stated commands are fail-closed for completion (#3267 Greptile conf residual).
|
|
190
190
|
// A safety-rejected shell-shaped acceptance line is not "optional diagnostic" — operators
|
|
191
191
|
// must promote a safe alternative or remove the stated command from the task statement.
|
|
192
|
+
const mergedRejected = [];
|
|
193
|
+
const seenRejected = new Set();
|
|
194
|
+
for (const row of [...(result.rejected ?? []), ...resolved.rejected]) {
|
|
195
|
+
const key = `${row.command}\0${row.reason}`;
|
|
196
|
+
if (seenRejected.has(key))
|
|
197
|
+
continue;
|
|
198
|
+
seenRejected.add(key);
|
|
199
|
+
mergedRejected.push(row);
|
|
200
|
+
}
|
|
192
201
|
let ok = result.ok;
|
|
193
202
|
let code = result.code;
|
|
194
|
-
let message = appendRejectedNote(result.message,
|
|
195
|
-
if (
|
|
203
|
+
let message = appendRejectedNote(result.message, mergedRejected);
|
|
204
|
+
if (mergedRejected.length > 0) {
|
|
196
205
|
ok = false;
|
|
197
206
|
if (code === 0)
|
|
198
207
|
code = 1;
|
|
199
|
-
const ledger = formatRejectedLedger(
|
|
208
|
+
const ledger = formatRejectedLedger(mergedRejected);
|
|
200
209
|
message =
|
|
201
|
-
`Literal acceptance-command gate FAILED (#3267): ${
|
|
210
|
+
`Literal acceptance-command gate FAILED (#3267): ${mergedRejected.length} ` +
|
|
202
211
|
`safety-rejected stated command(s) block completion until resolved ` +
|
|
203
212
|
`(promote a safe alternative or remove from the task statement).\n` +
|
|
204
213
|
ledger +
|
|
@@ -212,7 +221,7 @@ export function evaluateLiteralAcceptanceFromPlan(plan, options = {}) {
|
|
|
212
221
|
...result,
|
|
213
222
|
ok,
|
|
214
223
|
code,
|
|
215
|
-
rejected:
|
|
224
|
+
rejected: mergedRejected,
|
|
216
225
|
advisoryRejected: resolved.advisoryRejected,
|
|
217
226
|
transcriptPromptSkipped: resolved.transcriptPromptSkipped,
|
|
218
227
|
message,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { attachLiteralAcceptanceCommands, type CaptureLiteralAcceptanceResult, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, hasStructuredAcceptanceCommands, INLINE_PROSE_MENTION_REASON, isInlineProseMention, isProseDerivedRejection, readNotAcceptanceCommands, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
|
|
8
8
|
export { appendLiteralAcceptanceAdvisory, type EvaluateLiteralAcceptanceOptions, evaluateLiteralAcceptanceFromPath, evaluateLiteralAcceptanceFromPlan, formatLiteralAcceptanceAdvisory, isLiteralAcceptanceRequiredAtCeremonyDepth, LITERAL_ACCEPTANCE_ADVISORY_MARKER, type ResolvedLiteralAcceptance, resolveLiteralAcceptanceCommands, resolveLiteralAcceptanceDetailed, stripLiteralAcceptanceAdvisory, } from "./evaluate.js";
|
|
9
|
-
export { defaultLiteralAcceptanceRunner, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
|
|
9
|
+
export { defaultLiteralAcceptanceRunner, isSafetyRefusalRun, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
|
|
10
10
|
export { type CommandSafetyResult, evaluateCommandSafety, evaluateNoopDenylist, evaluateStampAcceptanceSafety, isExecutableLiteralSource, isNoopRefusalReason, isVerbatimStatementSpan, NOOP_ACCEPTANCE_REMEDIATION, REJECTED_NOOP_OUTCOME, type RejectedNoopOutcome, type StampAcceptanceCommand, type StampAcceptanceSafetyInput, type StampAcceptanceSafetyResult, type StampSourceRung, } from "./safety.js";
|
|
11
11
|
export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_NOT_COMMANDS_METADATA_KEY, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, type LiteralAcceptanceCommand, type LiteralAcceptanceGateResult, type LiteralAcceptanceRunner, type LiteralAcceptanceRunResult, type LiteralAcceptanceSource, type RejectedLiteralCommand, } from "./types.js";
|
|
12
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { attachLiteralAcceptanceCommands, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, hasStructuredAcceptanceCommands, INLINE_PROSE_MENTION_REASON, isInlineProseMention, isProseDerivedRejection, readNotAcceptanceCommands, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
|
|
8
8
|
export { appendLiteralAcceptanceAdvisory, evaluateLiteralAcceptanceFromPath, evaluateLiteralAcceptanceFromPlan, formatLiteralAcceptanceAdvisory, isLiteralAcceptanceRequiredAtCeremonyDepth, LITERAL_ACCEPTANCE_ADVISORY_MARKER, resolveLiteralAcceptanceCommands, resolveLiteralAcceptanceDetailed, stripLiteralAcceptanceAdvisory, } from "./evaluate.js";
|
|
9
|
-
export { defaultLiteralAcceptanceRunner, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
|
|
9
|
+
export { defaultLiteralAcceptanceRunner, isSafetyRefusalRun, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
|
|
10
10
|
export { evaluateCommandSafety, evaluateNoopDenylist, evaluateStampAcceptanceSafety, isExecutableLiteralSource, isNoopRefusalReason, isVerbatimStatementSpan, NOOP_ACCEPTANCE_REMEDIATION, REJECTED_NOOP_OUTCOME, } from "./safety.js";
|
|
11
11
|
export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_NOT_COMMANDS_METADATA_KEY, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, } from "./types.js";
|
|
12
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* Self-chosen verification is supplementary — this gate only runs stored literals.
|
|
7
7
|
*/
|
|
8
8
|
import type { LiteralAcceptanceCommand, LiteralAcceptanceGateResult, LiteralAcceptanceRunner, LiteralAcceptanceRunResult } from "./types.js";
|
|
9
|
+
/** True when the row is a pre-execution safety refusal, not a product measurement (#3615). */
|
|
10
|
+
export declare function isSafetyRefusalRun(run: Pick<LiteralAcceptanceRunResult, "exitCode" | "detail">): boolean;
|
|
9
11
|
/** Default shell runner: exact command string via platform shell. */
|
|
10
12
|
export declare function defaultLiteralAcceptanceRunner(input: {
|
|
11
13
|
readonly command: string;
|
|
@@ -9,6 +9,13 @@ import { spawnSync } from "node:child_process";
|
|
|
9
9
|
import { isAbsolute, resolve } from "node:path";
|
|
10
10
|
import { isInlineProseMention } from "./capture.js";
|
|
11
11
|
import { evaluateCommandSafety, isExecutableLiteralSource } from "./safety.js";
|
|
12
|
+
/** True when the row is a pre-execution safety refusal, not a product measurement (#3615). */
|
|
13
|
+
export function isSafetyRefusalRun(run) {
|
|
14
|
+
return run.exitCode === 2 && /^refused:\s*/i.test(run.detail);
|
|
15
|
+
}
|
|
16
|
+
function safetyRefusalReason(run) {
|
|
17
|
+
return run.detail.replace(/^refused:\s*/i, "").trim();
|
|
18
|
+
}
|
|
12
19
|
/** Default shell runner: exact command string via platform shell. */
|
|
13
20
|
export function defaultLiteralAcceptanceRunner(input) {
|
|
14
21
|
// Windows: cmd.exe /c; POSIX: shell true uses sh -c. Exact string preserved.
|
|
@@ -173,6 +180,7 @@ export function runLiteralAcceptanceCommands(commands, options) {
|
|
|
173
180
|
};
|
|
174
181
|
}
|
|
175
182
|
const runs = [];
|
|
183
|
+
const rejected = [];
|
|
176
184
|
for (const cmd of executable) {
|
|
177
185
|
if (typeof cmd.command !== "string" || cmd.command.trim().length === 0) {
|
|
178
186
|
return {
|
|
@@ -181,9 +189,18 @@ export function runLiteralAcceptanceCommands(commands, options) {
|
|
|
181
189
|
message: "Literal acceptance-command gate config error: empty command entry (#3267)",
|
|
182
190
|
commands,
|
|
183
191
|
runs,
|
|
192
|
+
rejected: rejected.length > 0 ? rejected : undefined,
|
|
184
193
|
};
|
|
185
194
|
}
|
|
186
|
-
|
|
195
|
+
const run = runLiteralAcceptanceCommand(cmd, options);
|
|
196
|
+
runs.push(run);
|
|
197
|
+
if (isSafetyRefusalRun(run)) {
|
|
198
|
+
rejected.push({
|
|
199
|
+
command: run.command,
|
|
200
|
+
reason: safetyRefusalReason(run),
|
|
201
|
+
sourceSpan: cmd.sourceSpan ?? null,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
187
204
|
}
|
|
188
205
|
const failed = runs.filter((r) => !r.ok);
|
|
189
206
|
if (failed.length === 0) {
|
|
@@ -213,6 +230,7 @@ export function runLiteralAcceptanceCommands(commands, options) {
|
|
|
213
230
|
lines.join("\n"),
|
|
214
231
|
commands,
|
|
215
232
|
runs,
|
|
233
|
+
rejected: rejected.length > 0 ? rejected : undefined,
|
|
216
234
|
};
|
|
217
235
|
}
|
|
218
236
|
function truncate(s, max) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Candidate diff ownership for the merge-chokepoint orphan run (#3893).
|
|
3
|
+
*
|
|
4
|
+
* The unscoped sweep enumerates every running brief under `active/`. Composed
|
|
5
|
+
* on `check:merge` that gives one stranded brief merge authority over every
|
|
6
|
+
* open PR, and N stranded briefs make N single-brief lifecycle PRs mutually
|
|
7
|
+
* unmergeable — each leaves the other orphan. The pre-merge run cannot see the
|
|
8
|
+
* residue its own candidate is about to create either: that candidate's linked
|
|
9
|
+
* PR still reads `merged_at: null` while the gate runs.
|
|
10
|
+
*
|
|
11
|
+
* Scoping the merge run to the candidate's own diff removes the coupling
|
|
12
|
+
* without touching the detector. Repo-wide truth stays enforced at the
|
|
13
|
+
* delivery tip (HEAD on the delivery line falls back to the full sweep) and
|
|
14
|
+
* after merge (`--issue N`, #3429).
|
|
15
|
+
*/
|
|
16
|
+
import { type GitRunner } from "../session/git.js";
|
|
17
|
+
export type CandidateScope = {
|
|
18
|
+
readonly kind: "diff";
|
|
19
|
+
/** Ref the candidate was diffed against (e.g. `origin/master`). */
|
|
20
|
+
readonly baseRef: string;
|
|
21
|
+
/** Absolute, platform-normalized brief paths this candidate owns. */
|
|
22
|
+
readonly paths: ReadonlySet<string>;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "sweep";
|
|
25
|
+
/** Why this run stayed repo-wide; always printed with the verdict. */
|
|
26
|
+
readonly reason: string;
|
|
27
|
+
};
|
|
28
|
+
export interface CandidateScopeOptions {
|
|
29
|
+
/** Explicit base ref; defaults to `origin/<deliveryBranch>` then the branch. */
|
|
30
|
+
readonly baseRef?: string | null;
|
|
31
|
+
readonly runGit?: GitRunner;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One comparable form for a git path and a readdir path.
|
|
35
|
+
*
|
|
36
|
+
* Only the directory is canonicalized: the file itself may be a deletion in
|
|
37
|
+
* the candidate diff and no longer exist on disk.
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeScopePath(path: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Base ref for the candidate diff: explicit override, else `origin/<branch>`,
|
|
42
|
+
* else the local delivery branch. Null when none resolves.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveCandidateBaseRef(projectRoot: string, override: string | null | undefined, runGit: GitRunner): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Brief paths under `activeDir` that the candidate's own diff touches.
|
|
47
|
+
*
|
|
48
|
+
* Falls back to the repo-wide sweep — never to a narrower scan — when git,
|
|
49
|
+
* the base ref, or the diff is unavailable, so the flag cannot silently
|
|
50
|
+
* shrink what a merge gate looks at.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveCandidateScope(projectRoot: string, activeDir: string, options?: CandidateScopeOptions): CandidateScope;
|
|
53
|
+
//# sourceMappingURL=candidate-scope.d.ts.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Candidate diff ownership for the merge-chokepoint orphan run (#3893).
|
|
3
|
+
*
|
|
4
|
+
* The unscoped sweep enumerates every running brief under `active/`. Composed
|
|
5
|
+
* on `check:merge` that gives one stranded brief merge authority over every
|
|
6
|
+
* open PR, and N stranded briefs make N single-brief lifecycle PRs mutually
|
|
7
|
+
* unmergeable — each leaves the other orphan. The pre-merge run cannot see the
|
|
8
|
+
* residue its own candidate is about to create either: that candidate's linked
|
|
9
|
+
* PR still reads `merged_at: null` while the gate runs.
|
|
10
|
+
*
|
|
11
|
+
* Scoping the merge run to the candidate's own diff removes the coupling
|
|
12
|
+
* without touching the detector. Repo-wide truth stays enforced at the
|
|
13
|
+
* delivery tip (HEAD on the delivery line falls back to the full sweep) and
|
|
14
|
+
* after merge (`--issue N`, #3429).
|
|
15
|
+
*/
|
|
16
|
+
import { realpathSync } from "node:fs";
|
|
17
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
18
|
+
import { resolveDeliveryBranch } from "../policy/delivery-branch.js";
|
|
19
|
+
import { defaultGitRunner } from "../session/git.js";
|
|
20
|
+
/** Symlink-resolved path, or the input when it cannot be resolved. */
|
|
21
|
+
function realpathOrSelf(path) {
|
|
22
|
+
try {
|
|
23
|
+
return realpathSync.native(path);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
try {
|
|
27
|
+
return realpathSync(path);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return path;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Canonical directory: symlinks resolved, then precomposed. Git reports the
|
|
36
|
+
* canonical worktree root while the lifecycle root keeps the caller's
|
|
37
|
+
* spelling, and git precomposes Unicode (`core.precomposeunicode`) while some
|
|
38
|
+
* filesystems hand `readdir` the decomposed form.
|
|
39
|
+
*/
|
|
40
|
+
function canonicalDir(path) {
|
|
41
|
+
return realpathOrSelf(resolve(path)).normalize("NFC");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One comparable form for a git path and a readdir path.
|
|
45
|
+
*
|
|
46
|
+
* Only the directory is canonicalized: the file itself may be a deletion in
|
|
47
|
+
* the candidate diff and no longer exist on disk.
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeScopePath(path) {
|
|
50
|
+
const abs = resolve(path);
|
|
51
|
+
const full = join(canonicalDir(dirname(abs)), basename(abs)).normalize("NFC");
|
|
52
|
+
return process.platform === "win32" ? full.toLowerCase() : full;
|
|
53
|
+
}
|
|
54
|
+
function sweep(reason) {
|
|
55
|
+
return { kind: "sweep", reason };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Split `-z` git output. NUL-delimited paths are never C-quoted, so a brief
|
|
59
|
+
* whose name git would quote (non-ASCII, quotes, backslashes) still compares
|
|
60
|
+
* equal to the readdir path instead of silently falling outside the candidate
|
|
61
|
+
* scope. `GitRunner` returns `-z` output as latin1 bytes, so decode to UTF-8.
|
|
62
|
+
*/
|
|
63
|
+
function splitNulPaths(stdout) {
|
|
64
|
+
return stdout
|
|
65
|
+
.split("\0")
|
|
66
|
+
.map((entry) => Buffer.from(entry, "latin1").toString("utf8"))
|
|
67
|
+
.filter((entry) => entry.length > 0);
|
|
68
|
+
}
|
|
69
|
+
function refExists(cwd, ref, runGit) {
|
|
70
|
+
return runGit(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]).code === 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Base ref for the candidate diff: explicit override, else `origin/<branch>`,
|
|
74
|
+
* else the local delivery branch. Null when none resolves.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveCandidateBaseRef(projectRoot, override, runGit) {
|
|
77
|
+
const explicit = override?.trim() ?? "";
|
|
78
|
+
if (explicit.length > 0) {
|
|
79
|
+
return refExists(projectRoot, explicit, runGit) ? explicit : null;
|
|
80
|
+
}
|
|
81
|
+
const branch = resolveDeliveryBranch(projectRoot, runGit).branch;
|
|
82
|
+
for (const candidate of [`origin/${branch}`, branch]) {
|
|
83
|
+
if (refExists(projectRoot, candidate, runGit)) {
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Brief paths under `activeDir` that the candidate's own diff touches.
|
|
91
|
+
*
|
|
92
|
+
* Falls back to the repo-wide sweep — never to a narrower scan — when git,
|
|
93
|
+
* the base ref, or the diff is unavailable, so the flag cannot silently
|
|
94
|
+
* shrink what a merge gate looks at.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveCandidateScope(projectRoot, activeDir, options = {}) {
|
|
97
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
98
|
+
const root = resolve(projectRoot);
|
|
99
|
+
const topLevel = runGit(root, ["rev-parse", "--show-toplevel"]);
|
|
100
|
+
if (topLevel.code !== 0) {
|
|
101
|
+
return sweep("not a git worktree, so no candidate diff exists");
|
|
102
|
+
}
|
|
103
|
+
const gitRoot = topLevel.stdout.trim();
|
|
104
|
+
if (gitRoot.length === 0) {
|
|
105
|
+
return sweep("git worktree root could not be resolved");
|
|
106
|
+
}
|
|
107
|
+
const baseRef = resolveCandidateBaseRef(root, options.baseRef, runGit);
|
|
108
|
+
if (baseRef === null) {
|
|
109
|
+
const requested = options.baseRef?.trim() ?? "";
|
|
110
|
+
return sweep(requested.length > 0
|
|
111
|
+
? `base ref '${requested}' not found; fetch it or pass a resolvable --base-ref`
|
|
112
|
+
: "no delivery base ref found (no origin/<deliveryBranch> or local branch); fetch it or pass --base-ref");
|
|
113
|
+
}
|
|
114
|
+
// HEAD at or behind the delivery line is the delivery tip itself, not a
|
|
115
|
+
// candidate: keep repo-wide truth there.
|
|
116
|
+
if (runGit(root, ["merge-base", "--is-ancestor", "HEAD", baseRef]).code === 0) {
|
|
117
|
+
return sweep(`HEAD is on the ${baseRef} delivery line, so this run is the delivery-tip check`);
|
|
118
|
+
}
|
|
119
|
+
const mergeBase = runGit(root, ["merge-base", "HEAD", baseRef]);
|
|
120
|
+
if (mergeBase.code !== 0) {
|
|
121
|
+
return sweep(`could not compute the merge base with ${baseRef}`);
|
|
122
|
+
}
|
|
123
|
+
const base = mergeBase.stdout.trim();
|
|
124
|
+
if (base.length === 0) {
|
|
125
|
+
return sweep(`merge base with ${baseRef} is empty`);
|
|
126
|
+
}
|
|
127
|
+
// Canonicalize both sides before the relative: a symlinked checkout or an
|
|
128
|
+
// unequal Unicode spelling of the same root would otherwise read as `..`
|
|
129
|
+
// and restore the repo-wide sweep.
|
|
130
|
+
const activeRel = relative(canonicalDir(gitRoot), canonicalDir(activeDir)).replace(/\\/g, "/");
|
|
131
|
+
if (activeRel.length === 0 || activeRel.startsWith("..")) {
|
|
132
|
+
return sweep("active/ is outside the git worktree");
|
|
133
|
+
}
|
|
134
|
+
// Diff against the working tree, not HEAD: a locally activated brief is a
|
|
135
|
+
// pre-commit `task check` candidate too.
|
|
136
|
+
const changed = runGit(gitRoot, ["diff", "--name-only", "-z", base, "--", activeRel]);
|
|
137
|
+
if (changed.code !== 0) {
|
|
138
|
+
return sweep(`could not diff active/ against ${baseRef}`);
|
|
139
|
+
}
|
|
140
|
+
const untracked = runGit(gitRoot, [
|
|
141
|
+
"ls-files",
|
|
142
|
+
"--others",
|
|
143
|
+
"--exclude-standard",
|
|
144
|
+
"-z",
|
|
145
|
+
"--",
|
|
146
|
+
activeRel,
|
|
147
|
+
]);
|
|
148
|
+
if (untracked.code !== 0) {
|
|
149
|
+
return sweep("could not list untracked briefs under active/");
|
|
150
|
+
}
|
|
151
|
+
const paths = new Set();
|
|
152
|
+
for (const rel of [...splitNulPaths(changed.stdout), ...splitNulPaths(untracked.stdout)]) {
|
|
153
|
+
paths.add(normalizeScopePath(resolve(gitRoot, rel)));
|
|
154
|
+
}
|
|
155
|
+
return { kind: "diff", baseRef, paths };
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=candidate-scope.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RunGhFn } from "../pr-protected-issues/types.js";
|
|
2
|
+
import type { GitRunner } from "../session/git.js";
|
|
2
3
|
export type OutputStream = "stdout" | "stderr" | "none";
|
|
3
4
|
export type OrphanKind = "shipped" | "unresolved";
|
|
4
5
|
export interface OrphanActiveBrief {
|
|
@@ -22,12 +23,24 @@ export interface OrphanActiveBasis {
|
|
|
22
23
|
readonly elapsedMs: number;
|
|
23
24
|
readonly budgetMs: number;
|
|
24
25
|
}
|
|
26
|
+
/** What merge-chokepoint scoping decided for this run (#3893). */
|
|
27
|
+
export interface OrphanActiveScopeSummary {
|
|
28
|
+
readonly kind: "diff" | "sweep";
|
|
29
|
+
/** Ref the candidate diff was taken against; null on a sweep. */
|
|
30
|
+
readonly baseRef: string | null;
|
|
31
|
+
/** Why the run stayed repo-wide; null on a candidate diff. */
|
|
32
|
+
readonly reason: string | null;
|
|
33
|
+
/** Running briefs left unevaluated because this candidate does not own them. */
|
|
34
|
+
readonly skipped: number;
|
|
35
|
+
}
|
|
25
36
|
export interface EvaluateResult {
|
|
26
37
|
readonly code: 0 | 1 | 2;
|
|
27
38
|
readonly message: string;
|
|
28
39
|
readonly stream: OutputStream;
|
|
29
40
|
readonly orphans: readonly OrphanActiveBrief[];
|
|
30
41
|
readonly basis: OrphanActiveBasis;
|
|
42
|
+
/** Null unless `changedOnly` was requested. */
|
|
43
|
+
readonly scope: OrphanActiveScopeSummary | null;
|
|
31
44
|
}
|
|
32
45
|
export interface EvaluateOptions {
|
|
33
46
|
readonly quiet?: boolean;
|
|
@@ -38,6 +51,15 @@ export interface EvaluateOptions {
|
|
|
38
51
|
readonly issue?: number | null;
|
|
39
52
|
/** Monotonic clock seam so basis ages and budget checks are testable. */
|
|
40
53
|
readonly nowMs?: () => number;
|
|
54
|
+
/**
|
|
55
|
+
* Merge-chokepoint scoping (#3893): evaluate only active/running briefs the
|
|
56
|
+
* candidate's own diff touches. Ignored with `issue`, which is already one
|
|
57
|
+
* origin. Falls back to the repo-wide sweep when the base ref is unresolvable.
|
|
58
|
+
*/
|
|
59
|
+
readonly changedOnly?: boolean;
|
|
60
|
+
/** Base ref for `changedOnly`; defaults to `origin/<deliveryBranch>`. */
|
|
61
|
+
readonly baseRef?: string | null;
|
|
62
|
+
readonly runGit?: GitRunner;
|
|
41
63
|
}
|
|
42
64
|
/**
|
|
43
65
|
* Pure evaluator for orphaned active/running xBRIEF detection (#2321 / #3429).
|
|
@@ -51,6 +73,11 @@ export interface EvaluateOptions {
|
|
|
51
73
|
* one complete, fail-closed open-issue inventory and stays fail-open on
|
|
52
74
|
* unknown so offline work is not network-authorized. A cache hit is honoured
|
|
53
75
|
* only inside `ISSUE_CACHE_MAX_AGE_MS`, and every verdict reports its basis.
|
|
76
|
+
*
|
|
77
|
+
* `changedOnly` is the merge-chokepoint form (#3893): only briefs the
|
|
78
|
+
* candidate's own diff touches are evaluated, so a brief stranded by another
|
|
79
|
+
* merge cannot fail this candidate. It falls back to the repo-wide sweep at
|
|
80
|
+
* the delivery tip and whenever the base ref is unresolvable.
|
|
54
81
|
*/
|
|
55
82
|
export declare function evaluate(projectRoot: string, options?: EvaluateOptions): EvaluateResult;
|
|
56
83
|
//# sourceMappingURL=evaluate.d.ts.map
|