@deftai/directive-core 0.104.0 → 0.106.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/authz/classify.js +1108 -67
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +34 -4
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -19
- package/dist/doctor/types.d.ts +9 -0
- package/dist/forward-coverage/diff-coverage.d.ts +39 -0
- package/dist/forward-coverage/diff-coverage.js +112 -0
- package/dist/forward-coverage/diff-lines.d.ts +19 -0
- package/dist/forward-coverage/diff-lines.js +173 -0
- package/dist/forward-coverage/evaluate.d.ts +24 -9
- package/dist/forward-coverage/evaluate.js +125 -19
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +241 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +18 -16
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +47 -1
- package/dist/intake/clause-derivation.js +238 -9
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +35 -6
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/lifecycle-visible/evaluate.d.ts +98 -0
- package/dist/lifecycle-visible/evaluate.js +710 -0
- package/dist/lifecycle-visible/index.d.ts +2 -0
- package/dist/lifecycle-visible/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +226 -77
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orchestration/judgment-policy.d.ts +13 -0
- package/dist/orchestration/judgment-policy.js +15 -0
- package/dist/orchestration/probe-session.js +13 -8
- package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
- package/dist/orchestration/verify-judgment-gates.js +43 -11
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +11 -21
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/host-hooks.d.ts +20 -0
- package/dist/policy/host-hooks.js +121 -2
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/org-force-on-migration.js +2 -1
- package/dist/policy/plan-extensions.d.ts +1 -1
- package/dist/policy/plan-extensions.js +2 -0
- package/dist/policy/product-signal.js +6 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/require-human-merge.js +8 -6
- package/dist/policy/resolve.d.ts +7 -2
- package/dist/policy/resolve.js +25 -8
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/policy/value-feedback.js +7 -8
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +4 -0
- package/dist/preflight/evaluate.js +35 -1
- package/dist/preflight/index.d.ts +4 -0
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/preflight-cache/evaluate.d.ts +12 -0
- package/dist/preflight-cache/evaluate.js +15 -1
- package/dist/preflight-cache/index.d.ts +1 -1
- package/dist/preflight-cache/index.js +1 -1
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
- package/dist/product-first-done-gate/empty-resolution.js +12 -1
- package/dist/product-first-done-gate/evaluate.d.ts +16 -0
- package/dist/product-first-done-gate/evaluate.js +298 -34
- package/dist/product-first-done-gate/index.d.ts +2 -0
- package/dist/product-first-done-gate/index.js +1 -0
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/release/closed-verb-gate.d.ts +12 -0
- package/dist/release/closed-verb-gate.js +51 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/pipeline.js +7 -0
- package/dist/release/types.d.ts +12 -0
- package/dist/render/constants.d.ts +2 -1
- package/dist/render/constants.js +2 -1
- package/dist/render/framework-commands.js +4 -0
- package/dist/render/spec-validate.js +23 -12
- package/dist/run-summary/emit.d.ts +16 -10
- package/dist/run-summary/emit.js +26 -15
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/types.d.ts +33 -4
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-evidence.d.ts +19 -7
- package/dist/scope/acceptance-evidence.js +38 -11
- package/dist/scope/delivery-evidence.d.ts +3 -3
- package/dist/scope/delivery-evidence.js +22 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/main.js +15 -5
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/promote-from-issue.js +5 -5
- package/dist/scope/transition.js +1 -1
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +67 -7
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +9 -36
- package/dist/session/ac-pass-banking.js +33 -79
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +4 -0
- package/dist/session/index.js +4 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/process-cost.d.ts +32 -10
- package/dist/session/process-cost.js +189 -1
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +700 -0
- package/dist/session/ritual-entrypoint.js +16 -1
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +13 -0
- package/dist/session/session-start.js +103 -9
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +28 -0
- package/dist/session/verify-session-ritual.js +97 -5
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +9 -3
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
- package/dist/swarm/finalize-cohort-cli.js +165 -35
- package/dist/swarm/finalize-cohort.d.ts +1 -0
- package/dist/swarm/finalize-cohort.js +47 -4
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +15 -3
- package/dist/swarm/routing-verify.d.ts +13 -1
- package/dist/swarm/routing-verify.js +60 -21
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/fake-trial.js +10 -0
- package/dist/telemetry-coverage/kinds.js +2 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/triage/bootstrap/index.js +6 -6
- package/dist/triage/help/registry-data.d.ts +1 -1
- package/dist/triage/help/registry-data.js +1 -1
- package/dist/triage/welcome/writers.d.ts +1 -1
- package/dist/triage/welcome/writers.js +10 -5
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/value/readback.d.ts +4 -1
- package/dist/value/readback.js +51 -12
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +1 -1
- package/dist/vbrief-reconcile/index.js +1 -1
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/vbrief-validate/schema.d.ts +4 -0
- package/dist/vbrief-validate/schema.js +1 -1
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
- package/dist/verify-env/agent-hook-readiness.js +12 -8
- package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
- package/dist/verify-env/agent-hooks-live-probe.js +26 -5
- package/dist/verify-env/agent-hooks.js +3 -4
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +7 -3
|
@@ -10,10 +10,51 @@
|
|
|
10
10
|
* mutations cannot ride an acceptance command (#3267 residual).
|
|
11
11
|
*/
|
|
12
12
|
import type { LiteralAcceptanceSource } from "./types.js";
|
|
13
|
+
/** One remediation when a command cannot fail (#3396). */
|
|
14
|
+
export declare const NOOP_ACCEPTANCE_REMEDIATION = "acceptance commands must be able to fail; name a command that exercises the artifact";
|
|
15
|
+
/** Capture/stamp outcome recorded on the acceptance event (#3396). */
|
|
16
|
+
export declare const REJECTED_NOOP_OUTCOME: "rejected-noop";
|
|
17
|
+
export type RejectedNoopOutcome = typeof REJECTED_NOOP_OUTCOME;
|
|
13
18
|
export interface CommandSafetyResult {
|
|
14
19
|
readonly ok: boolean;
|
|
15
20
|
readonly reason: string | null;
|
|
21
|
+
/** Set when the command is a denylisted no-op (#3396). */
|
|
22
|
+
readonly outcome?: RejectedNoopOutcome;
|
|
16
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Unconditional no-ops cannot be acceptance oracles (#3396).
|
|
26
|
+
* `test` with only constant args is a no-op; `test -f path` is an assertion.
|
|
27
|
+
*/
|
|
28
|
+
export declare function evaluateNoopDenylist(command: string): CommandSafetyResult;
|
|
29
|
+
/** True when a refusal reason is the #3396 no-op remediation. */
|
|
30
|
+
export declare function isNoopRefusalReason(reason: string | null | undefined): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Capture records verbatim statement spans as fence@ / labeled@ / prompt@ / inline@.
|
|
33
|
+
* Metadata and plan.acceptance mirrors are not statement provenance.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isVerbatimStatementSpan(sourceSpan: string | null | undefined): boolean;
|
|
36
|
+
export type StampSourceRung = "stated" | "derived" | "project_floor";
|
|
37
|
+
export interface StampAcceptanceCommand {
|
|
38
|
+
readonly command: string;
|
|
39
|
+
readonly source?: string;
|
|
40
|
+
readonly sourceSpan?: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface StampAcceptanceSafetyInput {
|
|
43
|
+
readonly commands: readonly StampAcceptanceCommand[];
|
|
44
|
+
readonly previousRung?: StampSourceRung | null;
|
|
45
|
+
}
|
|
46
|
+
export interface StampAcceptanceSafetyResult {
|
|
47
|
+
readonly ok: boolean;
|
|
48
|
+
readonly reason: string | null;
|
|
49
|
+
readonly outcome?: RejectedNoopOutcome;
|
|
50
|
+
readonly sourceRung: StampSourceRung;
|
|
51
|
+
readonly hasVerbatimStatementSpan: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Stamp-time no-op refuse + stated-only-with-span (#3396).
|
|
55
|
+
* A restamp cannot raise the rung to stated without a recorded statement span.
|
|
56
|
+
*/
|
|
57
|
+
export declare function evaluateStampAcceptanceSafety(input: StampAcceptanceSafetyInput): StampAcceptanceSafetyResult;
|
|
17
58
|
/** Whether this provenance is allowed to spawn a shell (#3267). */
|
|
18
59
|
export declare function isExecutableLiteralSource(source: LiteralAcceptanceSource | string): boolean;
|
|
19
60
|
/**
|
|
@@ -43,8 +43,6 @@ const ALLOWED_FIRST_TOKENS = new Set([
|
|
|
43
43
|
"yarn",
|
|
44
44
|
"bun",
|
|
45
45
|
"vitest",
|
|
46
|
-
"true",
|
|
47
|
-
"false",
|
|
48
46
|
]);
|
|
49
47
|
/**
|
|
50
48
|
* Exact wrapper subcommands that are verification-shaped (no scope/policy/scm/swarm
|
|
@@ -65,6 +63,128 @@ const ALLOWED_WRAPPER_SUBCOMMANDS = new Set([
|
|
|
65
63
|
* `verify:` / `verify-` cover task verify:* family; bare `verify ` is the space form.
|
|
66
64
|
*/
|
|
67
65
|
const ALLOWED_WRAPPER_PREFIXES = ["verify:", "verify-", "verify "];
|
|
66
|
+
/** One remediation when a command cannot fail (#3396). */
|
|
67
|
+
export const NOOP_ACCEPTANCE_REMEDIATION = "acceptance commands must be able to fail; name a command that exercises the artifact";
|
|
68
|
+
/** Capture/stamp outcome recorded on the acceptance event (#3396). */
|
|
69
|
+
export const REJECTED_NOOP_OUTCOME = "rejected-noop";
|
|
70
|
+
/** First tokens that always succeed or always fail without exercising an artifact. */
|
|
71
|
+
const NOOP_FIRST_TOKENS = new Set(["true", "false", ":", "echo", "printf"]);
|
|
72
|
+
/** `test` operators that inspect a path (not a constant comparison). */
|
|
73
|
+
const TEST_FILE_OPERATORS = new Set([
|
|
74
|
+
"-b",
|
|
75
|
+
"-c",
|
|
76
|
+
"-d",
|
|
77
|
+
"-e",
|
|
78
|
+
"-f",
|
|
79
|
+
"-g",
|
|
80
|
+
"-h",
|
|
81
|
+
"-k",
|
|
82
|
+
"-L",
|
|
83
|
+
"-p",
|
|
84
|
+
"-r",
|
|
85
|
+
"-S",
|
|
86
|
+
"-s",
|
|
87
|
+
"-t",
|
|
88
|
+
"-u",
|
|
89
|
+
"-w",
|
|
90
|
+
"-x",
|
|
91
|
+
]);
|
|
92
|
+
function firstTokenAndRest(trimmed) {
|
|
93
|
+
let end = 0;
|
|
94
|
+
while (end < trimmed.length && trimmed[end] !== " " && trimmed[end] !== "\t") {
|
|
95
|
+
end += 1;
|
|
96
|
+
}
|
|
97
|
+
return { first: trimmed.slice(0, end).toLowerCase(), rest: trimmed.slice(end).trim() };
|
|
98
|
+
}
|
|
99
|
+
function noopRefusal() {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
reason: NOOP_ACCEPTANCE_REMEDIATION,
|
|
103
|
+
outcome: REJECTED_NOOP_OUTCOME,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Unconditional no-ops cannot be acceptance oracles (#3396).
|
|
108
|
+
* `test` with only constant args is a no-op; `test -f path` is an assertion.
|
|
109
|
+
*/
|
|
110
|
+
export function evaluateNoopDenylist(command) {
|
|
111
|
+
if (typeof command !== "string") {
|
|
112
|
+
return { ok: true, reason: null };
|
|
113
|
+
}
|
|
114
|
+
const trimmed = command.trim();
|
|
115
|
+
if (trimmed.length === 0) {
|
|
116
|
+
return { ok: true, reason: null };
|
|
117
|
+
}
|
|
118
|
+
const { first, rest } = firstTokenAndRest(trimmed);
|
|
119
|
+
if (NOOP_FIRST_TOKENS.has(first)) {
|
|
120
|
+
return noopRefusal();
|
|
121
|
+
}
|
|
122
|
+
if (first === "exit" && rest.length === 0) {
|
|
123
|
+
return noopRefusal();
|
|
124
|
+
}
|
|
125
|
+
if (first === "test") {
|
|
126
|
+
const tokens = rest.length === 0 ? [] : rest.split(/\s+/);
|
|
127
|
+
if (!tokens.some((token) => TEST_FILE_OPERATORS.has(token))) {
|
|
128
|
+
return noopRefusal();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { ok: true, reason: null };
|
|
132
|
+
}
|
|
133
|
+
/** True when a refusal reason is the #3396 no-op remediation. */
|
|
134
|
+
export function isNoopRefusalReason(reason) {
|
|
135
|
+
return typeof reason === "string" && reason.includes("acceptance commands must be able to fail");
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Capture records verbatim statement spans as fence@ / labeled@ / prompt@ / inline@.
|
|
139
|
+
* Metadata and plan.acceptance mirrors are not statement provenance.
|
|
140
|
+
*/
|
|
141
|
+
export function isVerbatimStatementSpan(sourceSpan) {
|
|
142
|
+
if (typeof sourceSpan !== "string" || sourceSpan.trim().length === 0) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return /^(fence|labeled|prompt|inline)@/i.test(sourceSpan.trim());
|
|
146
|
+
}
|
|
147
|
+
function commandHasStatementProvenance(cmd) {
|
|
148
|
+
if (cmd.source !== undefined && cmd.source !== "task_statement") {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return isVerbatimStatementSpan(cmd.sourceSpan ?? null);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Stamp-time no-op refuse + stated-only-with-span (#3396).
|
|
155
|
+
* A restamp cannot raise the rung to stated without a recorded statement span.
|
|
156
|
+
*/
|
|
157
|
+
export function evaluateStampAcceptanceSafety(input) {
|
|
158
|
+
const hasVerbatimStatementSpan = input.commands.some(commandHasStatementProvenance);
|
|
159
|
+
let sourceRung;
|
|
160
|
+
if (input.commands.length === 0) {
|
|
161
|
+
sourceRung = input.previousRung ?? "project_floor";
|
|
162
|
+
}
|
|
163
|
+
else if (hasVerbatimStatementSpan) {
|
|
164
|
+
sourceRung = "stated";
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
sourceRung = "derived";
|
|
168
|
+
}
|
|
169
|
+
for (const cmd of input.commands) {
|
|
170
|
+
const noop = evaluateNoopDenylist(cmd.command);
|
|
171
|
+
if (!noop.ok) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
reason: noop.reason,
|
|
175
|
+
outcome: REJECTED_NOOP_OUTCOME,
|
|
176
|
+
sourceRung,
|
|
177
|
+
hasVerbatimStatementSpan,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
reason: null,
|
|
184
|
+
sourceRung,
|
|
185
|
+
hasVerbatimStatementSpan,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
68
188
|
/** Whether this provenance is allowed to spawn a shell (#3267). */
|
|
69
189
|
export function isExecutableLiteralSource(source) {
|
|
70
190
|
return EXECUTABLE_LITERAL_SOURCES.includes(source);
|
|
@@ -81,6 +201,10 @@ export function evaluateCommandSafety(command) {
|
|
|
81
201
|
if (trimmed.length > 500) {
|
|
82
202
|
return { ok: false, reason: "command exceeds 500 characters" };
|
|
83
203
|
}
|
|
204
|
+
const noop = evaluateNoopDenylist(trimmed);
|
|
205
|
+
if (!noop.ok) {
|
|
206
|
+
return noop;
|
|
207
|
+
}
|
|
84
208
|
for (let i = 0; i < trimmed.length; i += 1) {
|
|
85
209
|
const ch = trimmed[i];
|
|
86
210
|
if (SHELL_META_CHARS.has(ch)) {
|
|
@@ -63,6 +63,14 @@ export interface LiteralAcceptanceGateResult {
|
|
|
63
63
|
readonly runs: readonly LiteralAcceptanceRunResult[];
|
|
64
64
|
/** Safety-rejected shell-shaped lines (fail-loud ledger; may be empty). */
|
|
65
65
|
readonly rejected?: readonly RejectedLiteralCommand[];
|
|
66
|
+
/**
|
|
67
|
+
* Prose/fence/inline-derived rejections demoted to advisory (#3484 / #3511).
|
|
68
|
+
* Reported, never blocking — consumers MUST NOT read these back as a blocking
|
|
69
|
+
* ledger, and must not sniff the rendered message for their reasons (#3497).
|
|
70
|
+
*/
|
|
71
|
+
readonly advisoryRejected?: readonly RejectedLiteralCommand[];
|
|
72
|
+
/** Prompt lines skipped inside a transcript-shaped fence (#3511). */
|
|
73
|
+
readonly transcriptPromptSkipped?: number;
|
|
66
74
|
}
|
|
67
75
|
/** Metadata key for operator-visible rejected command ledger (#3267). */
|
|
68
76
|
export declare const LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY: "literal_acceptance_rejected";
|
|
@@ -2,6 +2,19 @@ export declare const GATE_CLASSES: Set<string>;
|
|
|
2
2
|
export declare const GATE_TIERS: Set<string>;
|
|
3
3
|
export declare const GATE_MATCH_PREDICATES: Set<string>;
|
|
4
4
|
export declare const GATE_MATCH_STATES: Set<string>;
|
|
5
|
+
/** ADR-005 / #3434 Story 1 -- author-stamped marker vocabulary. */
|
|
6
|
+
export declare const DESIGN_CRITIQUE_GATE_ID = "design-critique";
|
|
7
|
+
/** List-visible mirror; this is what judgmentGates predicates match. */
|
|
8
|
+
export declare const DESIGN_CRITIQUE_MARKER_LABEL = "design-critique:mechanism-shaped";
|
|
9
|
+
/** Authoritative body-text field in the #1423 triage write-back comment. */
|
|
10
|
+
export declare const DESIGN_CRITIQUE_MARKER_FIELD = "mechanism-shaped: true";
|
|
11
|
+
/**
|
|
12
|
+
* Clearance line shape (ADR-005). Presence and shape only -- a non-empty
|
|
13
|
+
* because-clause is required; its semantic content is never evaluated.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DESIGN_CRITIQUE_CLEARANCE_SHAPE: RegExp;
|
|
16
|
+
/** True when `line` has clearance presence+shape. Never scores the reason text. */
|
|
17
|
+
export declare function designCritiqueClearanceShapeOk(line: string): boolean;
|
|
5
18
|
export interface JudgmentGate {
|
|
6
19
|
readonly gate_id: string;
|
|
7
20
|
readonly gate_class: string;
|
|
@@ -7,6 +7,21 @@ export const GATE_CLASSES = new Set(["mechanical", "declared"]);
|
|
|
7
7
|
export const GATE_TIERS = new Set(["auto", "review", "block"]);
|
|
8
8
|
export const GATE_MATCH_PREDICATES = new Set(["labels", "body-text", "paths", "state", "age-days"]);
|
|
9
9
|
export const GATE_MATCH_STATES = new Set(["open", "closed"]);
|
|
10
|
+
/** ADR-005 / #3434 Story 1 -- author-stamped marker vocabulary. */
|
|
11
|
+
export const DESIGN_CRITIQUE_GATE_ID = "design-critique";
|
|
12
|
+
/** List-visible mirror; this is what judgmentGates predicates match. */
|
|
13
|
+
export const DESIGN_CRITIQUE_MARKER_LABEL = "design-critique:mechanism-shaped";
|
|
14
|
+
/** Authoritative body-text field in the #1423 triage write-back comment. */
|
|
15
|
+
export const DESIGN_CRITIQUE_MARKER_FIELD = "mechanism-shaped: true";
|
|
16
|
+
/**
|
|
17
|
+
* Clearance line shape (ADR-005). Presence and shape only -- a non-empty
|
|
18
|
+
* because-clause is required; its semantic content is never evaluated.
|
|
19
|
+
*/
|
|
20
|
+
export const DESIGN_CRITIQUE_CLEARANCE_SHAPE = /^design-critique:\s*(warranted|not warranted),\s*because\s+\S/i;
|
|
21
|
+
/** True when `line` has clearance presence+shape. Never scores the reason text. */
|
|
22
|
+
export function designCritiqueClearanceShapeOk(line) {
|
|
23
|
+
return DESIGN_CRITIQUE_CLEARANCE_SHAPE.test(line.trim());
|
|
24
|
+
}
|
|
10
25
|
function validateStrList(value, prefix, key) {
|
|
11
26
|
if (!Array.isArray(value) || value.length === 0) {
|
|
12
27
|
return [`${prefix}.${key} must be a non-empty list of strings`];
|
|
@@ -12,6 +12,11 @@ export const SESSION_RELPATH = [".deft", "probe-session.json"];
|
|
|
12
12
|
export const STATE_INTERROGATE = "interrogate";
|
|
13
13
|
export const STATE_COMPLETE = "complete";
|
|
14
14
|
export const VALID_DECISION_STATUSES = new Set(["locked", "deferred", "risk-accepted"]);
|
|
15
|
+
/** Live CLI teach-string (#3556). The Python helper was deleted. */
|
|
16
|
+
const PROBE_SESSION_START = "`deft probe-session start --target <scope>`";
|
|
17
|
+
const PROBE_SESSION_RECORD = "`deft probe-session record ...`";
|
|
18
|
+
const PROBE_SESSION_COMPLETE = "`deft probe-session complete`";
|
|
19
|
+
const NO_SESSION_START = `No active probe session. Start one with ${PROBE_SESSION_START}.`;
|
|
15
20
|
export class ProbeHandoffBlockedError extends Error {
|
|
16
21
|
session;
|
|
17
22
|
constructor(message, session = null) {
|
|
@@ -249,7 +254,7 @@ export function startSession(projectRoot, options) {
|
|
|
249
254
|
export function recordDecision(projectRoot, options) {
|
|
250
255
|
const session = readSession(projectRoot);
|
|
251
256
|
if (session === null) {
|
|
252
|
-
throw new ProbeHandoffBlockedError(
|
|
257
|
+
throw new ProbeHandoffBlockedError(NO_SESSION_START);
|
|
253
258
|
}
|
|
254
259
|
if (session.state !== STATE_INTERROGATE) {
|
|
255
260
|
throw new ProbeHandoffBlockedError("Probe session is already complete; decisions cannot be appended.", session);
|
|
@@ -275,7 +280,7 @@ export function recordDecision(projectRoot, options) {
|
|
|
275
280
|
export function setCurrentBranch(projectRoot, branch) {
|
|
276
281
|
const session = readSession(projectRoot);
|
|
277
282
|
if (session === null) {
|
|
278
|
-
throw new ProbeHandoffBlockedError(
|
|
283
|
+
throw new ProbeHandoffBlockedError(NO_SESSION_START);
|
|
279
284
|
}
|
|
280
285
|
if (session.state !== STATE_INTERROGATE) {
|
|
281
286
|
throw new ProbeHandoffBlockedError("Probe session is already complete; current branch cannot change.", session);
|
|
@@ -287,7 +292,7 @@ export function setCurrentBranch(projectRoot, branch) {
|
|
|
287
292
|
export function markComplete(projectRoot, now) {
|
|
288
293
|
const session = readSession(projectRoot);
|
|
289
294
|
if (session === null) {
|
|
290
|
-
throw new ProbeHandoffBlockedError(
|
|
295
|
+
throw new ProbeHandoffBlockedError(NO_SESSION_START);
|
|
291
296
|
}
|
|
292
297
|
if (session.state === STATE_COMPLETE) {
|
|
293
298
|
return session;
|
|
@@ -307,7 +312,7 @@ function pythonRepr(value) {
|
|
|
307
312
|
function blockedMessage(session, action) {
|
|
308
313
|
if (session === null) {
|
|
309
314
|
return (`Probe handoff blocked for ${action}: no active probe session. ` +
|
|
310
|
-
|
|
315
|
+
`Start interrogation with ${PROBE_SESSION_START} ` +
|
|
311
316
|
"and finish with `... complete` only after transition criteria are met.");
|
|
312
317
|
}
|
|
313
318
|
return (`Probe handoff blocked for ${action}: session state is ` +
|
|
@@ -315,9 +320,9 @@ function blockedMessage(session, action) {
|
|
|
315
320
|
`currentBranch=${pythonRepr(session.current_branch)}, ` +
|
|
316
321
|
`resolvedDecisions=${session.resolved_decisions.length}). ` +
|
|
317
322
|
"Continue interrogation until transition criteria are met, record decisions " +
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"or updating completedStrategies.probe in plan.
|
|
323
|
+
`with ${PROBE_SESSION_RECORD}, then run ` +
|
|
324
|
+
`${PROBE_SESSION_COMPLETE} before writing artifacts ` +
|
|
325
|
+
"or updating completedStrategies.probe in plan.xbrief.json.");
|
|
321
326
|
}
|
|
322
327
|
export function requireHandoffAllowed(projectRoot, action) {
|
|
323
328
|
const session = readSession(projectRoot);
|
|
@@ -330,7 +335,7 @@ export function guardProbeArtifact(projectRoot, artifactPath) {
|
|
|
330
335
|
return requireHandoffAllowed(projectRoot, `probe artifact write (${artifactPath})`);
|
|
331
336
|
}
|
|
332
337
|
export function guardPlanProbeRegistration(projectRoot) {
|
|
333
|
-
return requireHandoffAllowed(projectRoot, "completedStrategies.probe registration in plan.
|
|
338
|
+
return requireHandoffAllowed(projectRoot, "completedStrategies.probe registration in plan.xbrief.json");
|
|
334
339
|
}
|
|
335
340
|
export function sessionSummary(session) {
|
|
336
341
|
return {
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Risk-tiered judgment-gate engine (#1419 Slice 3). Port of scripts/verify_judgment_gates.py.
|
|
3
3
|
*/
|
|
4
4
|
import { type JudgmentGatesPolicy } from "./judgment-policy.js";
|
|
5
|
+
/** Thrown when a design-critique clearance reason fails presence/shape (never content). */
|
|
6
|
+
export declare class JudgmentClearanceShapeError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
5
9
|
/** Display/back-compat constant; resolution flows through resolveAuditDir (#2109). */
|
|
6
10
|
export declare const AUDIT_DIR_REL = "vbrief/.audit";
|
|
7
11
|
export declare const CLEARANCE_LOG_NAME = "judgment-gate-clearances.jsonl";
|
|
@@ -7,8 +7,15 @@ import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, statSyn
|
|
|
7
7
|
import { join, resolve } from "node:path";
|
|
8
8
|
import { assertProjectionContained, assertWriteTargetSafe, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
9
9
|
import { resolveAuditDir } from "../layout/resolve.js";
|
|
10
|
-
import { resolveJudgmentGates } from "./judgment-policy.js";
|
|
10
|
+
import { DESIGN_CRITIQUE_GATE_ID, designCritiqueClearanceShapeOk, resolveJudgmentGates, } from "./judgment-policy.js";
|
|
11
11
|
import { matchAny } from "./pathspec.js";
|
|
12
|
+
/** Thrown when a design-critique clearance reason fails presence/shape (never content). */
|
|
13
|
+
export class JudgmentClearanceShapeError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "JudgmentClearanceShapeError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
12
19
|
/** Display/back-compat constant; resolution flows through resolveAuditDir (#2109). */
|
|
13
20
|
export const AUDIT_DIR_REL = "vbrief/.audit";
|
|
14
21
|
export const CLEARANCE_LOG_NAME = "judgment-gate-clearances.jsonl";
|
|
@@ -163,6 +170,12 @@ export function readClearances(projectRoot, logPath) {
|
|
|
163
170
|
return out;
|
|
164
171
|
}
|
|
165
172
|
export function recordClearance(projectRoot, options) {
|
|
173
|
+
if (options.gate_id === DESIGN_CRITIQUE_GATE_ID &&
|
|
174
|
+
!designCritiqueClearanceShapeOk(options.reason ?? "")) {
|
|
175
|
+
throw new JudgmentClearanceShapeError("design-critique clearance reason must match shape " +
|
|
176
|
+
"'design-critique: warranted | not warranted, because ...' " +
|
|
177
|
+
"(presence and shape only; the because-clause is not scored)");
|
|
178
|
+
}
|
|
166
179
|
const path = options.log_path ?? clearanceLogPath(projectRoot);
|
|
167
180
|
const auditDir = resolveAuditDir(projectRoot);
|
|
168
181
|
assertProjectionContained(projectRoot, auditDir);
|
|
@@ -202,6 +215,15 @@ export function fingerprintScope(evidence) {
|
|
|
202
215
|
const payload = JSON.stringify(sorted);
|
|
203
216
|
return createHash("sha256").update(payload, "utf8").digest("hex");
|
|
204
217
|
}
|
|
218
|
+
function acceptClearanceShape(gateId, clearance) {
|
|
219
|
+
if (clearance === null) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (gateId !== DESIGN_CRITIQUE_GATE_ID) {
|
|
223
|
+
return clearance;
|
|
224
|
+
}
|
|
225
|
+
return designCritiqueClearanceShapeOk(String(clearance.reason ?? "")) ? clearance : null;
|
|
226
|
+
}
|
|
205
227
|
function lookupClearance(clearances, gateId, scope) {
|
|
206
228
|
let valid = null;
|
|
207
229
|
let stale = null;
|
|
@@ -383,7 +405,8 @@ export function buildReport(projectRoot, candidate, options = {}) {
|
|
|
383
405
|
continue;
|
|
384
406
|
}
|
|
385
407
|
const scope = fingerprintScope(evidence);
|
|
386
|
-
const [
|
|
408
|
+
const [validRaw, stale] = lookupClearance(records, String(gate.id), scope);
|
|
409
|
+
const valid = acceptClearanceShape(String(gate.id), validRaw);
|
|
387
410
|
outcomes.push({
|
|
388
411
|
gate_id: String(gate.id),
|
|
389
412
|
gate_class: String(gate.class),
|
|
@@ -605,15 +628,24 @@ function clearMain(argv) {
|
|
|
605
628
|
if (args.updatedAt !== null)
|
|
606
629
|
evidence["age-days"] = args.updatedAt;
|
|
607
630
|
const scope = fingerprintScope(evidence);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
631
|
+
try {
|
|
632
|
+
const entry = recordClearance(projectRoot, {
|
|
633
|
+
gate_id: args.gateId,
|
|
634
|
+
cleared_scope: scope,
|
|
635
|
+
reviewers: args.reviewers,
|
|
636
|
+
actor: args.actor,
|
|
637
|
+
reason: args.reason,
|
|
638
|
+
});
|
|
639
|
+
process.stdout.write(`recorded clearance ${String(entry.clearance_id)} for gate ${JSON.stringify(args.gateId)} (cleared_scope=${scope.slice(0, 12)}...)\n`);
|
|
640
|
+
return 0;
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
if (err instanceof JudgmentClearanceShapeError) {
|
|
644
|
+
process.stderr.write(`${err.message}\n`);
|
|
645
|
+
return 1;
|
|
646
|
+
}
|
|
647
|
+
throw err;
|
|
648
|
+
}
|
|
617
649
|
}
|
|
618
650
|
function parseEvalArgs(argv) {
|
|
619
651
|
const parsed = {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { RunGhFn } from "../pr-protected-issues/types.js";
|
|
2
2
|
export type OutputStream = "stdout" | "stderr" | "none";
|
|
3
|
+
export type OrphanKind = "shipped" | "unresolved";
|
|
3
4
|
export interface OrphanActiveBrief {
|
|
4
5
|
readonly path: string;
|
|
5
6
|
readonly reason: string;
|
|
7
|
+
readonly kind: OrphanKind;
|
|
6
8
|
}
|
|
7
9
|
export interface EvaluateResult {
|
|
8
10
|
readonly code: 0 | 1 | 2;
|
|
@@ -15,11 +17,15 @@ export interface EvaluateOptions {
|
|
|
15
17
|
readonly repo?: string | null;
|
|
16
18
|
readonly runGh?: RunGhFn;
|
|
17
19
|
readonly skipGh?: boolean;
|
|
20
|
+
/** When set, scan only active/running briefs that reference this issue (#3429). */
|
|
21
|
+
readonly issue?: number | null;
|
|
18
22
|
}
|
|
19
23
|
/**
|
|
20
|
-
* Pure evaluator for orphaned active/running xBRIEF detection (#2321).
|
|
24
|
+
* Pure evaluator for orphaned active/running xBRIEF detection (#2321 / #3429).
|
|
21
25
|
* Fails when active/ briefs with plan.status==running reference only closed
|
|
22
26
|
* issues and/or a merged PR — the stop-at:pr-open orphan signature.
|
|
27
|
+
* Pass `issue` to scan one origin after merge. Confirmed shipped prints
|
|
28
|
+
* scope:complete; unresolved lookup still exits 1 with a retry remediation.
|
|
23
29
|
*/
|
|
24
30
|
export declare function evaluate(projectRoot: string, options?: EvaluateOptions): EvaluateResult;
|
|
25
31
|
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -137,55 +137,127 @@ function fetchPrMerged(ref, runGh) {
|
|
|
137
137
|
return null;
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
const PASS = { orphaned: false, reason: null, kind: null };
|
|
141
|
+
function shipped(reason) {
|
|
142
|
+
return { orphaned: true, reason, kind: "shipped" };
|
|
143
|
+
}
|
|
144
|
+
function unresolved(reason) {
|
|
145
|
+
return { orphaned: true, reason, kind: "unresolved" };
|
|
146
|
+
}
|
|
147
|
+
function assessOrphanSignature(issueRefs, prRefs, projectRoot, runGh, skipGh, selectedIssue) {
|
|
148
|
+
if (selectedIssue !== null) {
|
|
149
|
+
// Confirmed closed origin is shipped even if a linked PR lookup fails.
|
|
150
|
+
const selectedRef = issueRefs.find((ref) => ref.number === selectedIssue);
|
|
151
|
+
const selectedState = selectedRef === undefined ? null : resolveIssueState(selectedRef, projectRoot, runGh, skipGh);
|
|
152
|
+
if (selectedState === "closed") {
|
|
153
|
+
return shipped(`issue #${selectedIssue} is closed`);
|
|
154
|
+
}
|
|
155
|
+
for (const pr of prRefs) {
|
|
156
|
+
if (skipGh) {
|
|
157
|
+
return unresolved(`linked PR #${pr.number} state could not be resolved`);
|
|
158
|
+
}
|
|
159
|
+
const merged = fetchPrMerged(pr, runGh);
|
|
160
|
+
if (merged === true) {
|
|
161
|
+
return shipped(`linked PR #${pr.number} is merged`);
|
|
162
|
+
}
|
|
163
|
+
if (merged === null) {
|
|
164
|
+
return unresolved(`linked PR #${pr.number} state could not be resolved`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// --issue N is one origin: sibling open/unknown must not mask it (#3429).
|
|
168
|
+
if (selectedRef !== undefined) {
|
|
169
|
+
if (selectedState === null) {
|
|
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`);
|
|
187
|
+
}
|
|
188
|
+
return PASS;
|
|
189
|
+
}
|
|
141
190
|
for (const pr of prRefs) {
|
|
142
191
|
if (skipGh) {
|
|
143
192
|
continue;
|
|
144
193
|
}
|
|
145
194
|
const merged = fetchPrMerged(pr, runGh);
|
|
146
195
|
if (merged === true) {
|
|
147
|
-
return
|
|
196
|
+
return shipped(`linked PR #${pr.number} is merged`);
|
|
148
197
|
}
|
|
149
198
|
}
|
|
150
199
|
if (issueRefs.length === 0) {
|
|
151
|
-
return
|
|
200
|
+
return PASS;
|
|
152
201
|
}
|
|
153
202
|
let resolved = 0;
|
|
154
203
|
for (const ref of issueRefs) {
|
|
155
204
|
const state = resolveIssueState(ref, projectRoot, runGh, skipGh);
|
|
156
205
|
if (state === null) {
|
|
157
|
-
return
|
|
206
|
+
return PASS;
|
|
158
207
|
}
|
|
159
208
|
resolved += 1;
|
|
160
209
|
if (state !== "closed") {
|
|
161
|
-
return
|
|
210
|
+
return PASS;
|
|
162
211
|
}
|
|
163
212
|
}
|
|
164
213
|
if (resolved > 0) {
|
|
165
|
-
return
|
|
214
|
+
return shipped("all referenced issues are closed");
|
|
166
215
|
}
|
|
167
|
-
return
|
|
216
|
+
return PASS;
|
|
168
217
|
}
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
"
|
|
179
|
-
|
|
218
|
+
function briefReferencesIssue(issues, issue) {
|
|
219
|
+
return issues.some((ref) => ref.number === issue);
|
|
220
|
+
}
|
|
221
|
+
function formatRefusal(orphans, projectRoot, issueFilter) {
|
|
222
|
+
const shippedOrphans = orphans.filter((orphan) => orphan.kind === "shipped");
|
|
223
|
+
const unresolvedOrphans = orphans.filter((orphan) => orphan.kind === "unresolved");
|
|
224
|
+
const noun = orphans.length === 1 ? "" : "s";
|
|
225
|
+
let headline;
|
|
226
|
+
if (unresolvedOrphans.length > 0 && shippedOrphans.length === 0) {
|
|
227
|
+
headline = `verify:orphan-active: ${unresolvedOrphans.length} active/running xBRIEF${unresolvedOrphans.length === 1 ? "" : "s"} have unresolved GitHub state (cannot confirm shipped) (project_root=${projectRoot}).`;
|
|
228
|
+
}
|
|
229
|
+
else if (shippedOrphans.length > 0 && unresolvedOrphans.length === 0) {
|
|
230
|
+
headline = `verify:orphan-active: ${shippedOrphans.length} active/running xBRIEF${shippedOrphans.length === 1 ? "" : "s"} look shipped but still consume WIP (project_root=${projectRoot}).`;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
headline = `verify:orphan-active: ${orphans.length} active/running xBRIEF${noun} failed the after-merge check (project_root=${projectRoot}).`;
|
|
234
|
+
}
|
|
235
|
+
const lines = [headline];
|
|
236
|
+
if (shippedOrphans.length > 0) {
|
|
237
|
+
lines.push(" Remediation: move each confirmed-shipped brief out of active/ with lifecycle ownership:");
|
|
238
|
+
for (const orphan of shippedOrphans) {
|
|
239
|
+
lines.push(` task scope:complete -- ${orphan.path}`);
|
|
240
|
+
}
|
|
241
|
+
lines.push(" task scope:cancel -- xbrief/active/<file>.xbrief.json # when abandoning", " For stop-at:pr-open workers the orchestrator owns post-merge complete/cancel;", " for drive-to:merge-ready workers scope:complete is part of the worker unit (#2321 / #3429).", " Or run task swarm:finalize-cohort / task swarm:complete-cohort after cohort merge.");
|
|
242
|
+
}
|
|
243
|
+
if (unresolvedOrphans.length > 0) {
|
|
244
|
+
const retry = issueFilter === null
|
|
245
|
+
? "task verify:orphan-active"
|
|
246
|
+
: `task verify:orphan-active -- --issue ${issueFilter}`;
|
|
247
|
+
lines.push(" Remediation: retry the GitHub lookup (auth, rate-limit, network, skipGh) then re-run:", ` ${retry}`, " Do not run task scope:complete until the origin is confirmed closed or the linked PR is confirmed merged.");
|
|
248
|
+
}
|
|
249
|
+
lines.push(" Offending briefs:");
|
|
180
250
|
for (const orphan of orphans) {
|
|
181
251
|
lines.push(` - ${orphan.path} (${orphan.reason})`);
|
|
182
252
|
}
|
|
183
253
|
return lines.join("\n");
|
|
184
254
|
}
|
|
185
255
|
/**
|
|
186
|
-
* Pure evaluator for orphaned active/running xBRIEF detection (#2321).
|
|
256
|
+
* Pure evaluator for orphaned active/running xBRIEF detection (#2321 / #3429).
|
|
187
257
|
* Fails when active/ briefs with plan.status==running reference only closed
|
|
188
258
|
* issues and/or a merged PR — the stop-at:pr-open orphan signature.
|
|
259
|
+
* Pass `issue` to scan one origin after merge. Confirmed shipped prints
|
|
260
|
+
* scope:complete; unresolved lookup still exits 1 with a retry remediation.
|
|
189
261
|
*/
|
|
190
262
|
export function evaluate(projectRoot, options = {}) {
|
|
191
263
|
const root = resolve(projectRoot);
|
|
@@ -238,24 +310,30 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
238
310
|
orphans: [],
|
|
239
311
|
};
|
|
240
312
|
}
|
|
313
|
+
const issueFilter = options.issue ?? null;
|
|
241
314
|
const orphans = [];
|
|
242
315
|
for (const brief of listActiveRunningBriefs(root)) {
|
|
243
316
|
const { issues, prs } = collectGithubRefs(brief.plan, defaultRepo);
|
|
317
|
+
// --issue N is one origin: briefs that name that issue. PR-only briefs stay on the unscoped scan (#3429).
|
|
318
|
+
if (issueFilter !== null && !briefReferencesIssue(issues, issueFilter)) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
244
321
|
if (issues.length === 0 && prs.length === 0) {
|
|
245
322
|
continue;
|
|
246
323
|
}
|
|
247
|
-
const assessment = assessOrphanSignature(issues, prs, root, runGh, skipGh);
|
|
248
|
-
if (assessment.orphaned && assessment.reason !== null) {
|
|
324
|
+
const assessment = assessOrphanSignature(issues, prs, root, runGh, skipGh, issueFilter);
|
|
325
|
+
if (assessment.orphaned && assessment.reason !== null && assessment.kind !== null) {
|
|
249
326
|
orphans.push({
|
|
250
327
|
path: relBriefPath(brief.path, root),
|
|
251
328
|
reason: assessment.reason,
|
|
329
|
+
kind: assessment.kind,
|
|
252
330
|
});
|
|
253
331
|
}
|
|
254
332
|
}
|
|
255
333
|
if (orphans.length > 0) {
|
|
256
334
|
return {
|
|
257
335
|
code: 1,
|
|
258
|
-
message: formatRefusal(orphans, root),
|
|
336
|
+
message: formatRefusal(orphans, root, issueFilter),
|
|
259
337
|
stream: "stderr",
|
|
260
338
|
orphans,
|
|
261
339
|
};
|
|
@@ -264,9 +342,10 @@ export function evaluate(projectRoot, options = {}) {
|
|
|
264
342
|
return { code: 0, message: "", stream: "none", orphans: [] };
|
|
265
343
|
}
|
|
266
344
|
const scanned = listActiveRunningBriefs(root).length;
|
|
345
|
+
const issueNote = issueFilter === null ? "" : ` for issue #${issueFilter}`;
|
|
267
346
|
return {
|
|
268
347
|
code: 0,
|
|
269
|
-
message: `verify:orphan-active: no orphaned active/running xBRIEFs ` +
|
|
348
|
+
message: `verify:orphan-active: no orphaned active/running xBRIEFs${issueNote} ` +
|
|
270
349
|
`(scanned ${scanned} running brief${scanned === 1 ? "" : "s"} in active/).`,
|
|
271
350
|
stream: "stdout",
|
|
272
351
|
orphans: [],
|