@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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Exit 0 when ready; exit 1 when not ready (loud diagnostic); exit 2 on bad flags.
|
|
6
6
|
*/
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { INSTALLATION_IDENTITY_ISSUE_URL, PRINCIPAL_KIND_USER, } from "../intake/github-auth-modes.js";
|
|
8
9
|
import { clearScmReadyCache, formatScmReadinessLines, probeScmReadiness, scmReadinessToDict, } from "./readiness.js";
|
|
9
10
|
export function parseScmReadinessArgs(argv) {
|
|
10
11
|
const out = {};
|
|
@@ -30,6 +31,26 @@ export function parseScmReadinessArgs(argv) {
|
|
|
30
31
|
}
|
|
31
32
|
out.depth = value;
|
|
32
33
|
}
|
|
34
|
+
else if (arg === "--repo") {
|
|
35
|
+
const value = argv[++i];
|
|
36
|
+
if (value === undefined || value.startsWith("-")) {
|
|
37
|
+
return { args: out, error: "--repo expects owner/repo" };
|
|
38
|
+
}
|
|
39
|
+
out.repo = value;
|
|
40
|
+
}
|
|
41
|
+
else if (arg === "--expected-login") {
|
|
42
|
+
const value = argv[++i];
|
|
43
|
+
if (value === undefined || value.startsWith("-")) {
|
|
44
|
+
return { args: out, error: "--expected-login expects a GitHub login" };
|
|
45
|
+
}
|
|
46
|
+
out.expectedLogin = value;
|
|
47
|
+
}
|
|
48
|
+
else if (arg === "--expected-app-slug" || arg === "--expected-installation-id") {
|
|
49
|
+
return {
|
|
50
|
+
args: out,
|
|
51
|
+
error: `${arg} is not accepted; GitHub App installation identity is deferred to ${INSTALLATION_IDENTITY_ISSUE_URL}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
33
54
|
else if (arg === "--help" || arg === "-h") {
|
|
34
55
|
out.help = true;
|
|
35
56
|
}
|
|
@@ -43,8 +64,19 @@ export function parseScmReadinessArgs(argv) {
|
|
|
43
64
|
return { args: out, error: null };
|
|
44
65
|
}
|
|
45
66
|
const USAGE = "usage: deft scm:status [--json] [--deep|--shallow|--depth shallow|deep]\n" +
|
|
67
|
+
" [--repo OWNER/REPO] [--expected-login LOGIN]\n" +
|
|
46
68
|
" Report SCM (gh/ghx) availability + auth state in this execution env (#2275).\n" +
|
|
69
|
+
" Deep probes derive the target repo (flag / GH_REPO / GITHUB_REPOSITORY / origin)\n" +
|
|
70
|
+
" and compare an expected user login when one is supplied (#3665).\n" +
|
|
71
|
+
" GitHub App installation identity is deferred to #3693.\n" +
|
|
47
72
|
" Exit 0 ready / 1 not ready / 2 config error.\n";
|
|
73
|
+
function expectedPrincipalFromArgs(args) {
|
|
74
|
+
const login = args.expectedLogin?.trim() ?? "";
|
|
75
|
+
if (login.length > 0) {
|
|
76
|
+
return { kind: PRINCIPAL_KIND_USER, login };
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
48
80
|
export function scmReadinessMain(args, options = {}) {
|
|
49
81
|
const writeOut = options.writeOut ?? ((s) => process.stdout.write(s));
|
|
50
82
|
const writeErr = options.writeErr ?? ((s) => process.stderr.write(s));
|
|
@@ -52,12 +84,21 @@ export function scmReadinessMain(args, options = {}) {
|
|
|
52
84
|
writeOut(USAGE);
|
|
53
85
|
return 0;
|
|
54
86
|
}
|
|
87
|
+
const expectedPrincipal = expectedPrincipalFromArgs(args);
|
|
88
|
+
if (expectedPrincipal !== undefined && "error" in expectedPrincipal) {
|
|
89
|
+
writeErr(`error: ${expectedPrincipal.error}\n${USAGE}`);
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
55
92
|
const depth = args.depth ?? (args.deep ? "deep" : "shallow");
|
|
56
93
|
// Explicit probe always re-evaluates (credentials may have changed since
|
|
57
94
|
// session:start). Clears the process-scoped requireScmReady cache too.
|
|
58
95
|
clearScmReadyCache();
|
|
59
96
|
const probe = options.probe ?? probeScmReadiness;
|
|
60
|
-
const report = probe({
|
|
97
|
+
const report = probe({
|
|
98
|
+
depth,
|
|
99
|
+
repo: args.repo,
|
|
100
|
+
expectedPrincipal,
|
|
101
|
+
});
|
|
61
102
|
if (args.json) {
|
|
62
103
|
writeOut(`${JSON.stringify(scmReadinessToDict(report), null, 2)}\n`);
|
|
63
104
|
}
|
package/dist/scm/readiness.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* env token presence + optional short `gh auth status`). Deep API validation
|
|
19
19
|
* is opt-in via `deep: true` (session:start --with-network).
|
|
20
20
|
*/
|
|
21
|
-
import { findInjectedToken, type GhRunner, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN } from "../intake/github-auth-modes.js";
|
|
21
|
+
import { type ExpectedGithubWorkerPrincipal, findInjectedToken, type GhRunner, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN } from "../intake/github-auth-modes.js";
|
|
22
22
|
import { type RuntimeCapabilityReport } from "../intake/platform-capabilities.js";
|
|
23
23
|
import { type WhichFn } from "./binary.js";
|
|
24
24
|
import { BINARY_PREFERENCE } from "./constants.js";
|
|
@@ -41,6 +41,8 @@ export interface ScmReadinessReport {
|
|
|
41
41
|
readonly githubAuthMode: string;
|
|
42
42
|
/** Runtime mode from the #1557a probe. */
|
|
43
43
|
readonly runtimeMode: string;
|
|
44
|
+
/** Stable id naming why the runtime was classified that way (#3859). */
|
|
45
|
+
readonly runtimeModeReason: string | null;
|
|
44
46
|
/** Whether an injected token env var is present (value never reported). */
|
|
45
47
|
readonly injectedTokenPresent: boolean;
|
|
46
48
|
/** Probe depth used for this report. */
|
|
@@ -64,6 +66,7 @@ export interface ProbeScmReadinessOptions {
|
|
|
64
66
|
readonly runtimeReport?: RuntimeCapabilityReport;
|
|
65
67
|
readonly githubAuthMode?: string | null;
|
|
66
68
|
readonly repo?: string;
|
|
69
|
+
readonly expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
|
|
67
70
|
readonly runGh?: GhRunner;
|
|
68
71
|
/** When false, skip even `gh auth status` on the shallow path (tests / pure PATH). */
|
|
69
72
|
readonly checkAuthStatus?: boolean;
|
package/dist/scm/readiness.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { spawnSync } from "node:child_process";
|
|
22
22
|
import { findInjectedToken, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN, inferGithubAuthMode, validateGithubAuthForWorker, } from "../intake/github-auth-modes.js";
|
|
23
23
|
import { getPlatformCapabilities, probeRuntimeCapabilities, } from "../intake/platform-capabilities.js";
|
|
24
|
+
import { EXPLICIT_SELECTION_REMEDIATION, GITHUB_AUTH_MODE_ENV, RUNTIME_REASON_CURSOR_MARKER_AMBIGUOUS, } from "../platform/cursor-managed-runtime.js";
|
|
24
25
|
import { defaultWhich } from "./binary.js";
|
|
25
26
|
import { BINARY_PREFERENCE } from "./constants.js";
|
|
26
27
|
import { ScmStubError } from "./errors.js";
|
|
@@ -105,7 +106,12 @@ function mapDeepFailureToAuthState(result) {
|
|
|
105
106
|
return "missing-token";
|
|
106
107
|
if (result.failureKind === "gh_auth_failed")
|
|
107
108
|
return "unauthenticated";
|
|
108
|
-
if (result.failureKind === "api_unreachable" ||
|
|
109
|
+
if (result.failureKind === "api_unreachable" ||
|
|
110
|
+
result.failureKind === "repo_access_denied" ||
|
|
111
|
+
result.failureKind === "missing_expected_principal" ||
|
|
112
|
+
result.failureKind === "principal_mismatch" ||
|
|
113
|
+
result.failureKind === "missing_target_repo" ||
|
|
114
|
+
result.failureKind === "installation_identity_unverifiable") {
|
|
109
115
|
return "unauthenticated";
|
|
110
116
|
}
|
|
111
117
|
return "unknown";
|
|
@@ -133,6 +139,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
133
139
|
authState: "binary-absent",
|
|
134
140
|
githubAuthMode,
|
|
135
141
|
runtimeMode: runtimeReport.runtimeMode,
|
|
142
|
+
runtimeModeReason: runtimeReport.runtimeModeReason ?? null,
|
|
136
143
|
injectedTokenPresent,
|
|
137
144
|
depth,
|
|
138
145
|
detail,
|
|
@@ -144,9 +151,11 @@ export function probeScmReadiness(options = {}) {
|
|
|
144
151
|
}
|
|
145
152
|
// Injected-token mode without a token is a hard not-ready even before auth status.
|
|
146
153
|
if (githubAuthMode === GITHUB_AUTH_MODE_INJECTED_TOKEN && !injectedTokenPresent) {
|
|
154
|
+
const ambiguousRuntime = runtimeReport.runtimeModeReason === RUNTIME_REASON_CURSOR_MARKER_AMBIGUOUS;
|
|
147
155
|
const detail = "injected-token mode requires GH_TOKEN, GITHUB_TOKEN, or GH_ENTERPRISE_TOKEN; " +
|
|
148
156
|
`binary=${binary} present but SCM-dependent gates skipped ` +
|
|
149
|
-
`(runtime_mode=${runtimeReport.runtimeMode}
|
|
157
|
+
`(runtime_mode=${runtimeReport.runtimeMode}` +
|
|
158
|
+
`${runtimeReport.runtimeModeReason ? `, reason=${runtimeReport.runtimeModeReason}` : ""})`;
|
|
150
159
|
return {
|
|
151
160
|
ready: false,
|
|
152
161
|
binary,
|
|
@@ -154,10 +163,15 @@ export function probeScmReadiness(options = {}) {
|
|
|
154
163
|
authState: "missing-token",
|
|
155
164
|
githubAuthMode,
|
|
156
165
|
runtimeMode: runtimeReport.runtimeMode,
|
|
166
|
+
runtimeModeReason: runtimeReport.runtimeModeReason ?? null,
|
|
157
167
|
injectedTokenPresent: false,
|
|
158
168
|
depth,
|
|
159
169
|
detail,
|
|
160
|
-
|
|
170
|
+
// An ambiguous Cursor runtime is the #3859 case: name the explicit opt-in
|
|
171
|
+
// here, because this is where the operator actually meets the block.
|
|
172
|
+
remediation: ambiguousRuntime
|
|
173
|
+
? `${EXPLICIT_SELECTION_REMEDIATION}\n\n${REMEDIATION_MISSING_TOKEN}`
|
|
174
|
+
: REMEDIATION_MISSING_TOKEN,
|
|
161
175
|
skippedGates: [...SCM_DEPENDENT_GATES],
|
|
162
176
|
login: null,
|
|
163
177
|
failureKind: "missing_injected_token",
|
|
@@ -192,6 +206,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
192
206
|
environ: env,
|
|
193
207
|
runtimeReport,
|
|
194
208
|
repo: options.repo,
|
|
209
|
+
expectedPrincipal: options.expectedPrincipal,
|
|
195
210
|
runGh: deepRunner,
|
|
196
211
|
});
|
|
197
212
|
if (deep.ok) {
|
|
@@ -203,6 +218,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
203
218
|
authState: "authenticated",
|
|
204
219
|
githubAuthMode: deep.githubAuthMode,
|
|
205
220
|
runtimeMode: runtimeReport.runtimeMode,
|
|
221
|
+
runtimeModeReason: runtimeReport.runtimeModeReason ?? null,
|
|
206
222
|
injectedTokenPresent,
|
|
207
223
|
depth,
|
|
208
224
|
detail: `SCM ready: ${binary} present, ${deep.githubAuthMode} authenticated${loginPart} (deep)`,
|
|
@@ -220,6 +236,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
220
236
|
authState,
|
|
221
237
|
githubAuthMode: deep.githubAuthMode,
|
|
222
238
|
runtimeMode: runtimeReport.runtimeMode,
|
|
239
|
+
runtimeModeReason: runtimeReport.runtimeModeReason ?? null,
|
|
223
240
|
injectedTokenPresent,
|
|
224
241
|
depth,
|
|
225
242
|
detail: `SCM not ready: ${deep.detail}`,
|
|
@@ -241,6 +258,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
241
258
|
authState: "unknown",
|
|
242
259
|
githubAuthMode,
|
|
243
260
|
runtimeMode: runtimeReport.runtimeMode,
|
|
261
|
+
runtimeModeReason: runtimeReport.runtimeModeReason ?? null,
|
|
244
262
|
injectedTokenPresent,
|
|
245
263
|
depth,
|
|
246
264
|
detail,
|
|
@@ -262,6 +280,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
262
280
|
authState: "unauthenticated",
|
|
263
281
|
githubAuthMode,
|
|
264
282
|
runtimeMode: runtimeReport.runtimeMode,
|
|
283
|
+
runtimeModeReason: runtimeReport.runtimeModeReason ?? null,
|
|
265
284
|
injectedTokenPresent,
|
|
266
285
|
depth,
|
|
267
286
|
detail,
|
|
@@ -278,6 +297,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
278
297
|
authState: "authenticated",
|
|
279
298
|
githubAuthMode,
|
|
280
299
|
runtimeMode: runtimeReport.runtimeMode,
|
|
300
|
+
runtimeModeReason: runtimeReport.runtimeModeReason ?? null,
|
|
281
301
|
injectedTokenPresent,
|
|
282
302
|
depth,
|
|
283
303
|
detail: `SCM ready: ${binary} present, ${githubAuthMode} authenticated (shallow)`,
|
|
@@ -296,6 +316,7 @@ export function scmReadinessToDict(report) {
|
|
|
296
316
|
auth_state: report.authState,
|
|
297
317
|
github_auth_mode: report.githubAuthMode,
|
|
298
318
|
runtime_mode: report.runtimeMode,
|
|
319
|
+
runtime_mode_reason: report.runtimeModeReason,
|
|
299
320
|
injected_token_present: report.injectedTokenPresent,
|
|
300
321
|
depth: report.depth,
|
|
301
322
|
detail: report.detail,
|
|
@@ -318,7 +339,14 @@ export function formatScmReadinessLines(report) {
|
|
|
318
339
|
}
|
|
319
340
|
lines.push(`[deft scm] ${report.detail}`);
|
|
320
341
|
if (report.skippedGates.length > 0) {
|
|
321
|
-
|
|
342
|
+
// Name the classification reason next to the skip: a silent skip is what
|
|
343
|
+
// makes this failure costly, because work-selection surfaces go dark (#3859).
|
|
344
|
+
const reason = report.runtimeModeReason ? ` (reason: ${report.runtimeModeReason})` : "";
|
|
345
|
+
lines.push(`[deft scm] skipped gates: ${report.skippedGates.join(", ")}${reason}`);
|
|
346
|
+
if (report.runtimeModeReason === RUNTIME_REASON_CURSOR_MARKER_AMBIGUOUS) {
|
|
347
|
+
lines.push(`[deft scm] runtime is ambiguous; set ${GITHUB_AUTH_MODE_ENV}=host-gh to authorize ` +
|
|
348
|
+
"the host gh credential store on a machine you control (#3859)");
|
|
349
|
+
}
|
|
322
350
|
}
|
|
323
351
|
lines.push("[deft scm] run SCM-dependent gates only after auth is ready, or from a matched env; " +
|
|
324
352
|
"see content/scm/github.md § Mismatched/headless SCM readiness (#2275)");
|
|
@@ -381,13 +409,15 @@ export function requireScmReady(options = {}) {
|
|
|
381
409
|
if (hermeticAuthSkip) {
|
|
382
410
|
// Production path uses assertScmBinaryPresent (not test-only).
|
|
383
411
|
const binary = assertScmBinaryPresent(options.whichFn);
|
|
412
|
+
const hermeticRuntime = options.runtimeReport ?? getPlatformCapabilities();
|
|
384
413
|
const report = {
|
|
385
414
|
ready: true,
|
|
386
415
|
binary,
|
|
387
416
|
binaryPath: options.whichFn?.(binary) ?? null,
|
|
388
417
|
authState: "unknown",
|
|
389
418
|
githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
|
|
390
|
-
runtimeMode:
|
|
419
|
+
runtimeMode: hermeticRuntime.runtimeMode,
|
|
420
|
+
runtimeModeReason: hermeticRuntime.runtimeModeReason ?? null,
|
|
391
421
|
injectedTokenPresent: findInjectedToken(options.env ?? process.env) !== null,
|
|
392
422
|
depth: "shallow",
|
|
393
423
|
detail: `SCM binary present (${binary}); auth status not checked (hermetic)`,
|
package/dist/scope/decompose.js
CHANGED
|
@@ -17,6 +17,7 @@ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveL
|
|
|
17
17
|
import { resolveRepo } from "../triage/queue/repo.js";
|
|
18
18
|
import { referenceWithDefaultTrust, slugify } from "../vbrief-build/build.js";
|
|
19
19
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
20
|
+
import { READY_REQUIRES_PARALLEL_SAFE } from "../vbrief-validation/story-quality.js";
|
|
20
21
|
import { formatCoverageReportLine, validateCoverageMap } from "./coverage-map.js";
|
|
21
22
|
import { buildParentLineageArtifact } from "./parent-lineage.js";
|
|
22
23
|
import { formatBriefJson } from "./vbrief-json.js";
|
|
@@ -407,7 +408,7 @@ export function storyQualityIssues(opts) {
|
|
|
407
408
|
issues.push(...fileScopeIssues(swarm));
|
|
408
409
|
issues.push(...verifyCommandIssues(swarm));
|
|
409
410
|
if (swarm.parallel_safe === false) {
|
|
410
|
-
issues.push(
|
|
411
|
+
issues.push(READY_REQUIRES_PARALLEL_SAFE);
|
|
411
412
|
}
|
|
412
413
|
if (swarm.file_scope_confidence === "low") {
|
|
413
414
|
issues.push("readiness=ready requires file_scope_confidence above low");
|
|
@@ -16,6 +16,29 @@ export declare const NON_DELIVERY_DISPOSITIONS: readonly ["cancelled", "supersed
|
|
|
16
16
|
export type NonDeliveryDisposition = (typeof NON_DELIVERY_DISPOSITIONS)[number];
|
|
17
17
|
/** Delivery verification outcome for completion provenance. */
|
|
18
18
|
export type DeliveryDisposition = "delivered" | "merged_to_integration" | "not_delivered" | NonDeliveryDisposition | "unknown" | "unverified";
|
|
19
|
+
/**
|
|
20
|
+
* This is a historical audit snapshot stamped onto
|
|
21
|
+
* `plan.metadata.completionProvenance` at `scope:complete` (#3041 / #3690).
|
|
22
|
+
*
|
|
23
|
+
* Completed xBRIEFs have full standing as a record of *what is* and zero
|
|
24
|
+
* authority over *what to build next* (#3383). Fields stay because they
|
|
25
|
+
* answer a later reconstruction question after branches are deleted and
|
|
26
|
+
* refs move. A field does not need a current production-code reader.
|
|
27
|
+
*
|
|
28
|
+
* Reconstruction groups:
|
|
29
|
+
* - Identity and location: `repository`, `implementationCommit`, `prNumber`,
|
|
30
|
+
* `prBase`, `deliveryBranch`
|
|
31
|
+
* - Delivery evidence: `mergeCommit`, `deliveryCommit`, `disposition`,
|
|
32
|
+
* `handoffState`
|
|
33
|
+
* - Verification attribution: `verifiedAt`, `verifier`
|
|
34
|
+
* - Explicit operator-supplied facts: `deployed`, `uatVerified` (never
|
|
35
|
+
* inferred from Git)
|
|
36
|
+
*
|
|
37
|
+
* `completedSessionId` is duplicated. This nested copy is stamped so the
|
|
38
|
+
* snapshot is self-contained. Consumers (`verify:ac` /
|
|
39
|
+
* `session-completed-ac`) read the sibling
|
|
40
|
+
* `plan.metadata.completedSessionId`, not this field.
|
|
41
|
+
*/
|
|
19
42
|
export interface CompletionProvenance {
|
|
20
43
|
readonly repository: string | null;
|
|
21
44
|
readonly implementationCommit: string | null;
|
|
@@ -32,7 +55,10 @@ export interface CompletionProvenance {
|
|
|
32
55
|
readonly deployed: boolean | null;
|
|
33
56
|
/** Always null unless explicitly supplied — never inferred from Git (#3041). */
|
|
34
57
|
readonly uatVerified: boolean | null;
|
|
35
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Nested session correlation for a self-contained snapshot.
|
|
60
|
+
* Consumers read sibling `plan.metadata.completedSessionId`, not this field.
|
|
61
|
+
*/
|
|
36
62
|
readonly completedSessionId?: string | null;
|
|
37
63
|
}
|
|
38
64
|
export interface DeliveryEvidenceInput {
|
package/dist/scope/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./decomposed-refs.js";
|
|
|
8
8
|
export * from "./delivery-evidence.js";
|
|
9
9
|
export * from "./demote.js";
|
|
10
10
|
export * from "./effort-activate-gate.js";
|
|
11
|
+
export * from "./lifecycle-write.js";
|
|
11
12
|
export * from "./main.js";
|
|
12
13
|
export * from "./open-umbrella-warning.js";
|
|
13
14
|
export * from "./parent-lineage.js";
|
package/dist/scope/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./decomposed-refs.js";
|
|
|
8
8
|
export * from "./delivery-evidence.js";
|
|
9
9
|
export * from "./demote.js";
|
|
10
10
|
export * from "./effort-activate-gate.js";
|
|
11
|
+
export * from "./lifecycle-write.js";
|
|
11
12
|
export * from "./main.js";
|
|
12
13
|
export * from "./open-umbrella-warning.js";
|
|
13
14
|
export * from "./parent-lineage.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition-write fingerprint for completed/ artifacts (#3679).
|
|
3
|
+
*
|
|
4
|
+
* Distinct from completionProvenance (#3686): this only records that
|
|
5
|
+
* runTransition wrote the blob. Non-code-bearing completes and scope:fail
|
|
6
|
+
* stamp this and still omit delivery provenance.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LIFECYCLE_WRITE_KEY: "lifecycleWrite";
|
|
9
|
+
export type LifecycleWriteAction = "complete" | "fail";
|
|
10
|
+
export interface LifecycleWriteStamp {
|
|
11
|
+
readonly action: LifecycleWriteAction;
|
|
12
|
+
readonly writtenAt: string;
|
|
13
|
+
}
|
|
14
|
+
/** Worker-facing leftover-land path after a scope-provenance strip (#3476 / #3679). */
|
|
15
|
+
export declare const LEFTOVER_LAND_PR_REMEDIATION: string;
|
|
16
|
+
/** Stamp that runTransition wrote this plan into completed/. */
|
|
17
|
+
export declare function stampLifecycleWrite(plan: Record<string, unknown>, action: LifecycleWriteAction, writtenAt: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* True when a completed/ blob shows verb evidence.
|
|
20
|
+
* Accepts the new stamp, legacy completedAt (pre-#3679 complete), or failed status
|
|
21
|
+
* (scope:fail never stamped provenance and must keep passing).
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasTransitionWrite(plan: Record<string, unknown>): boolean;
|
|
24
|
+
//# sourceMappingURL=lifecycle-write.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition-write fingerprint for completed/ artifacts (#3679).
|
|
3
|
+
*
|
|
4
|
+
* Distinct from completionProvenance (#3686): this only records that
|
|
5
|
+
* runTransition wrote the blob. Non-code-bearing completes and scope:fail
|
|
6
|
+
* stamp this and still omit delivery provenance.
|
|
7
|
+
*/
|
|
8
|
+
export const LIFECYCLE_WRITE_KEY = "lifecycleWrite";
|
|
9
|
+
/** Worker-facing leftover-land path after a scope-provenance strip (#3476 / #3679). */
|
|
10
|
+
export const LEFTOVER_LAND_PR_REMEDIATION = "After a provenance-gated product PR strips active/ from the change set, " +
|
|
11
|
+
"the designed remainder is a leftover land PR (#3476): run `task scope:complete` " +
|
|
12
|
+
"after merge (it can stamp a brief already in completed/), then land that artifact. " +
|
|
13
|
+
"Do not git-add a completed/ husk to skip the verb.";
|
|
14
|
+
function asRecord(value) {
|
|
15
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function metadataRecord(plan) {
|
|
21
|
+
const existing = asRecord(plan.metadata);
|
|
22
|
+
if (existing !== null) {
|
|
23
|
+
return existing;
|
|
24
|
+
}
|
|
25
|
+
const created = {};
|
|
26
|
+
plan.metadata = created;
|
|
27
|
+
return created;
|
|
28
|
+
}
|
|
29
|
+
/** Stamp that runTransition wrote this plan into completed/. */
|
|
30
|
+
export function stampLifecycleWrite(plan, action, writtenAt) {
|
|
31
|
+
const meta = metadataRecord(plan);
|
|
32
|
+
meta[LIFECYCLE_WRITE_KEY] = { action, writtenAt };
|
|
33
|
+
}
|
|
34
|
+
function hasLifecycleWriteStamp(plan) {
|
|
35
|
+
const meta = asRecord(plan.metadata);
|
|
36
|
+
if (meta === null) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const stamp = asRecord(meta[LIFECYCLE_WRITE_KEY]);
|
|
40
|
+
if (stamp === null) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const action = stamp.action;
|
|
44
|
+
const writtenAt = stamp.writtenAt;
|
|
45
|
+
return ((action === "complete" || action === "fail") &&
|
|
46
|
+
typeof writtenAt === "string" &&
|
|
47
|
+
writtenAt.trim().length > 0);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* True when a completed/ blob shows verb evidence.
|
|
51
|
+
* Accepts the new stamp, legacy completedAt (pre-#3679 complete), or failed status
|
|
52
|
+
* (scope:fail never stamped provenance and must keep passing).
|
|
53
|
+
*/
|
|
54
|
+
export function hasTransitionWrite(plan) {
|
|
55
|
+
if (hasLifecycleWriteStamp(plan)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const meta = asRecord(plan.metadata);
|
|
59
|
+
if (meta !== null && typeof meta.completedAt === "string" && meta.completedAt.trim().length > 0) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return String(plan.status ?? "") === "failed";
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=lifecycle-write.js.map
|
package/dist/scope/main.js
CHANGED
|
@@ -17,10 +17,12 @@ import { findByDecisionId, isAlreadyUndone, REVERSIBLE_ACTIONS, undoBatch, undoO
|
|
|
17
17
|
const LIFECYCLE_USAGE_STDERR = "usage: deft scope:<action> [file] [--batch] [--from-issue N] [--repo OWNER/NAME]\n" +
|
|
18
18
|
" [--force] [--project-root PATH] [--strict] [--force-no-cache]\n" +
|
|
19
19
|
" [--path PATH]\n" +
|
|
20
|
+
" complete also accepts: --merge-commit SHA --pr N [--pr-base BRANCH] [--delivery-branch BRANCH]\n" +
|
|
20
21
|
" actions: activate, block, cancel, complete, fail, promote, restore, unblock\n" +
|
|
21
22
|
"The verb already encodes the action (e.g. deft scope:promote <file>). Do not pass the action again.\n" +
|
|
22
23
|
"(promote --batch may omit file and promotes all proposed/ scopes; #3011)\n" +
|
|
23
|
-
"(promote --from-issue=N may omit file; #1136)\n"
|
|
24
|
+
"(promote --from-issue=N may omit file; #1136)\n" +
|
|
25
|
+
"(complete --merge-commit / --pr are required for code-bearing delivery evidence; #3041 / #3721)\n";
|
|
24
26
|
function parseLifecycleArgv(argv) {
|
|
25
27
|
if (argv.length < 1) {
|
|
26
28
|
return { args: null, error: "usage" };
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { dirname, resolve } from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import { atomicWriteProjectDefinition, loadProjectDefinitionForMutation, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
2
|
+
import { withProjectDefinitionMutation } from "../vbrief-build/project-definition-mutation.js";
|
|
4
3
|
import { syncRegistryArtifactAfterScopeMove } from "./registry-artifact-sync.js";
|
|
5
4
|
/** Fail-closed sync of PROJECT-DEFINITION after a lifecycle move (#1527 / #2131). */
|
|
6
5
|
export function syncProjectDefinitionAfterScopeMove(scopeData, oldPath, newPath, vbriefRoot, targetStatus) {
|
|
7
6
|
const projectRoot = dirname(resolve(vbriefRoot));
|
|
8
7
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
withProjectDefinitionMutation(projectRoot, (mutation) => {
|
|
9
|
+
syncRegistryArtifactAfterScopeMove(
|
|
10
|
+
// The artifact the lock captured, not an independent re-resolution (#3796).
|
|
11
|
+
mutation.artifactPath, scopeData, oldPath, newPath, vbriefRoot, targetStatus, {
|
|
12
|
+
loadForMutation: () => [mutation.load(), mutation.artifactPath],
|
|
13
|
+
persist: (path, data) => {
|
|
14
|
+
// The registry sync echoes back the path it was handed, so a
|
|
15
|
+
// mismatch means something re-resolved the artifact mid-section.
|
|
16
|
+
if (resolve(path) !== resolve(mutation.artifactPath)) {
|
|
17
|
+
throw new Error("refusing to persist PROJECT-DEFINITION to a path other than the locked artifact");
|
|
18
|
+
}
|
|
19
|
+
mutation.persist(data);
|
|
20
|
+
},
|
|
14
21
|
});
|
|
15
22
|
});
|
|
16
23
|
return null;
|
package/dist/scope/transition.js
CHANGED
|
@@ -16,6 +16,7 @@ import { LIFECYCLE_FOLDERS, MOVE_LABELS, STATUS_PRECONDITIONS, STAY_LABELS, TRAN
|
|
|
16
16
|
import { detectLifecycleFolder, updateDecomposedChildBackReferences, updateDecomposedParentBackReferences, } from "./decomposed-refs.js";
|
|
17
17
|
import { classifyStoredDeliveryDisposition, evaluateDeliveryGate, resolveCompletionSessionId, stampDeliveryProvenance, } from "./delivery-evidence.js";
|
|
18
18
|
import { evaluateEffortActivateGate } from "./effort-activate-gate.js";
|
|
19
|
+
import { stampLifecycleWrite } from "./lifecycle-write.js";
|
|
19
20
|
import { syncProjectDefinitionAfterScopeMove } from "./project-definition-sync.js";
|
|
20
21
|
import { syncSpecificationAfterScopeMove } from "./specification-sync.js";
|
|
21
22
|
import { utcNowIso } from "./vbrief-json.js";
|
|
@@ -81,7 +82,9 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
84
|
const { allowedSources, targetFolder, targetStatus } = TRANSITIONS[act];
|
|
84
|
-
|
|
85
|
+
const restampCompleted = act === "complete" && currentFolder === "completed";
|
|
86
|
+
if (!restampCompleted &&
|
|
87
|
+
!allowedSources.includes(currentFolder)) {
|
|
85
88
|
const allowedStr = allowedSources.map((s) => `${s}/`).join(", ");
|
|
86
89
|
return {
|
|
87
90
|
ok: false,
|
|
@@ -103,15 +106,9 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
103
106
|
const requiredStatus = STATUS_PRECONDITIONS[act];
|
|
104
107
|
if (requiredStatus !== undefined) {
|
|
105
108
|
if (currentStatus === targetStatus) {
|
|
106
|
-
// Surface legacy delivery disposition on already-completed briefs (#3041).
|
|
107
|
-
let dispositionSuffix = "";
|
|
108
|
-
if (act === "complete" && currentFolder === "completed") {
|
|
109
|
-
const disposition = classifyStoredDeliveryDisposition(planObj);
|
|
110
|
-
dispositionSuffix = ` (deliveryDisposition=${disposition})`;
|
|
111
|
-
}
|
|
112
109
|
return {
|
|
113
110
|
ok: true,
|
|
114
|
-
message: `No-op: ${basename} is already ${targetStatus} in ${currentFolder}
|
|
111
|
+
message: `No-op: ${basename} is already ${targetStatus} in ${currentFolder}/`,
|
|
115
112
|
};
|
|
116
113
|
}
|
|
117
114
|
if (currentStatus !== requiredStatus) {
|
|
@@ -122,14 +119,30 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
122
119
|
};
|
|
123
120
|
}
|
|
124
121
|
}
|
|
122
|
+
const nowIso = utcNowIso(now);
|
|
123
|
+
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
124
|
+
const projectRoot = dirname(vbriefRoot);
|
|
125
|
+
// #3679: lift #3041 already-completed handling out of STATUS_PRECONDITIONS
|
|
126
|
+
// so complete can stamp a brief already in completed/ without a folder trip.
|
|
127
|
+
if (restampCompleted) {
|
|
128
|
+
return restampCompletedBrief({
|
|
129
|
+
resolvedPath,
|
|
130
|
+
basename,
|
|
131
|
+
data,
|
|
132
|
+
planObj,
|
|
133
|
+
projectRoot,
|
|
134
|
+
vbriefRoot,
|
|
135
|
+
now,
|
|
136
|
+
nowIso,
|
|
137
|
+
options,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
125
140
|
if (targetFolder !== null && targetFolder === currentFolder) {
|
|
126
141
|
return {
|
|
127
142
|
ok: true,
|
|
128
143
|
message: `No-op: ${basename} is already in ${currentFolder}/ (status: ${currentStatus})`,
|
|
129
144
|
};
|
|
130
145
|
}
|
|
131
|
-
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
132
|
-
const projectRoot = dirname(vbriefRoot);
|
|
133
146
|
if (targetFolder !== null) {
|
|
134
147
|
const destDir = join(vbriefRoot, targetFolder);
|
|
135
148
|
try {
|
|
@@ -144,7 +157,6 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
144
157
|
throw err;
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
|
-
const nowIso = utcNowIso(now);
|
|
148
160
|
// #3360: hand-authored briefs run #3323 clause derivation on activate/promote.
|
|
149
161
|
// #3355: stamp is state-observed (before vs after), not derivation-notice-bound.
|
|
150
162
|
const previousAcceptance = planObj.acceptance;
|
|
@@ -190,6 +202,7 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
190
202
|
? { ...gate.provenance, completedSessionId: sessionId }
|
|
191
203
|
: gate.provenance);
|
|
192
204
|
}
|
|
205
|
+
stampLifecycleWrite(planObj, "complete", nowIso);
|
|
193
206
|
}
|
|
194
207
|
// #3240: per-criterion typed evidence or human-origin disposition before auto-advance.
|
|
195
208
|
let acceptanceReports;
|
|
@@ -266,6 +279,9 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
266
279
|
};
|
|
267
280
|
}
|
|
268
281
|
}
|
|
282
|
+
if (act === "fail") {
|
|
283
|
+
stampLifecycleWrite(planObj, "fail", nowIso);
|
|
284
|
+
}
|
|
269
285
|
const formatted = formatBriefJson(data);
|
|
270
286
|
const crud = new InstrumentedVbriefCrud({ now: () => now });
|
|
271
287
|
if (targetFolder !== null) {
|
|
@@ -370,6 +386,69 @@ export function runTransition(action, filePath, now = new Date(), options = {})
|
|
|
370
386
|
acceptanceReports,
|
|
371
387
|
};
|
|
372
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Stamp a brief already in completed/ (#3679). Runs the #3041 delivery gate
|
|
391
|
+
* and writes in place so recovery does not need a folder round-trip.
|
|
392
|
+
* The #3041 deliveryDisposition suffix now actually executes for complete.
|
|
393
|
+
*/
|
|
394
|
+
function restampCompletedBrief(args) {
|
|
395
|
+
const { resolvedPath, basename, data, planObj, projectRoot, vbriefRoot, now, nowIso, options } = args;
|
|
396
|
+
const gate = evaluateDeliveryGate({
|
|
397
|
+
projectRoot,
|
|
398
|
+
plan: planObj,
|
|
399
|
+
nowIso,
|
|
400
|
+
evidence: options.deliveryEvidence,
|
|
401
|
+
nonDeliveryDisposition: options.nonDeliveryDisposition,
|
|
402
|
+
runGit: options.runGit,
|
|
403
|
+
verifier: options.verifier ?? "scope:complete",
|
|
404
|
+
assumeEvidenceValidated: options.assumeEvidenceValidated,
|
|
405
|
+
});
|
|
406
|
+
if (!gate.ok) {
|
|
407
|
+
return { ok: false, message: gate.message };
|
|
408
|
+
}
|
|
409
|
+
if (gate.provenance !== null) {
|
|
410
|
+
const sessionId = resolveCompletionSessionId(projectRoot);
|
|
411
|
+
stampDeliveryProvenance(planObj, sessionId !== null ? { ...gate.provenance, completedSessionId: sessionId } : gate.provenance);
|
|
412
|
+
}
|
|
413
|
+
planObj.status = "completed";
|
|
414
|
+
planObj.updated = nowIso;
|
|
415
|
+
stampEnvelopeUpdated(data, nowIso);
|
|
416
|
+
stampCompletionMetadata(planObj, projectRoot, nowIso, {
|
|
417
|
+
completedSessionId: resolveCompletionSessionId(projectRoot),
|
|
418
|
+
});
|
|
419
|
+
stampLifecycleWrite(planObj, "complete", nowIso);
|
|
420
|
+
const writeResult = atomicWriteBrief(resolvedPath, data, vbriefRoot, { projectRoot });
|
|
421
|
+
if (!writeResult.ok) {
|
|
422
|
+
return { ok: false, message: writeResult.message };
|
|
423
|
+
}
|
|
424
|
+
const crud = new InstrumentedVbriefCrud({ now: () => now });
|
|
425
|
+
crud.recordTrustedUpdate(resolvedPath, formatBriefJson(data));
|
|
426
|
+
try {
|
|
427
|
+
persistCrudMetrics(projectRoot, crud.getMetrics());
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
/* best-effort telemetry persistence */
|
|
431
|
+
}
|
|
432
|
+
const sessionId = resolveCompletionSessionId(projectRoot);
|
|
433
|
+
if (sessionId !== null) {
|
|
434
|
+
try {
|
|
435
|
+
writeSessionCompletedMarker(projectRoot, {
|
|
436
|
+
path: resolvedPath,
|
|
437
|
+
sessionId,
|
|
438
|
+
completedAt: nowIso,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return { ok: false, message: SESSION_COMPLETED_AC_REMEDIATION };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const disposition = classifyStoredDeliveryDisposition(planObj);
|
|
446
|
+
return {
|
|
447
|
+
ok: true,
|
|
448
|
+
message: `Restamped ${basename} in completed/ (status: completed, ` +
|
|
449
|
+
`deliveryDisposition=${disposition})`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
373
452
|
export function recordWipCapOverride(filePath, projectRoot, check, now = new Date()) {
|
|
374
453
|
try {
|
|
375
454
|
const rel = resolve(filePath).startsWith(resolve(projectRoot))
|
|
@@ -209,7 +209,11 @@ function remediationForExpansion() {
|
|
|
209
209
|
"`.deft/approved-scope/<plan-id>.json` with a humanApproval stamp). Commit that " +
|
|
210
210
|
"approval on the merge base (or a prior PR) before expanding or activating the " +
|
|
211
211
|
"scoped xBRIEF in the implementation change set. Editing the active xBRIEF alone " +
|
|
212
|
-
"does not authorize new paths (#3145 / #3205). See content/docs/scope-provenance.md."
|
|
212
|
+
"does not authorize new paths (#3145 / #3205). See content/docs/scope-provenance.md. " +
|
|
213
|
+
"If you cannot mint a digest (human TTY required), drop active/ from this product " +
|
|
214
|
+
"change set and land the completed artifact in a leftover land PR after merge (#3476). " +
|
|
215
|
+
"Run `task scope:complete` on the brief (it can stamp a file already in completed/). " +
|
|
216
|
+
"Do not git-add a completed/ husk to skip that PR (#3679).");
|
|
213
217
|
}
|
|
214
218
|
/**
|
|
215
219
|
* Parse + lightly validate an approved-scope JSON blob (base-ref `git show` or disk).
|