@deftai/directive-core 0.105.0 → 0.107.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 +591 -34
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +1 -1
- 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/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/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +17 -1
- package/dist/hooks/dispatcher.js +82 -11
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +1 -1
- package/dist/intake/clause-derivation.d.ts +9 -0
- package/dist/intake/clause-derivation.js +30 -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/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/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 +16 -0
- package/dist/literal-acceptance/capture.js +78 -10
- 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/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/policy/ceremony-dial.js +7 -7
- package/dist/policy/host-hooks.d.ts +20 -0
- package/dist/policy/host-hooks.js +121 -2
- 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/require-human-merge.js +8 -6
- package/dist/policy/resolve.d.ts +7 -2
- package/dist/policy/resolve.js +25 -8
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +7 -8
- package/dist/policy/write-fence.js +3 -0
- package/dist/preflight-cache/evaluate.d.ts +12 -0
- package/dist/preflight-cache/evaluate.js +24 -4
- package/dist/preflight-cache/index.d.ts +1 -1
- package/dist/preflight-cache/index.js +1 -1
- package/dist/product-first-done-gate/evaluate.d.ts +4 -0
- package/dist/product-first-done-gate/evaluate.js +82 -10
- 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/types.d.ts +4 -0
- package/dist/scm/design-critique-chip.d.ts +37 -0
- package/dist/scm/design-critique-chip.js +171 -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 +2 -1
- package/dist/scm/readiness.js +7 -1
- 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 +18 -6
- package/dist/scope/promote-from-issue.js +5 -5
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +8 -4
- package/dist/session/ac-pass-banking.d.ts +5 -0
- package/dist/session/ac-pass-banking.js +3 -2
- package/dist/session/git.d.ts +18 -0
- package/dist/session/git.js +81 -1
- package/dist/session/occupancy.d.ts +2 -1
- package/dist/session/occupancy.js +11 -4
- package/dist/session/process-cost.d.ts +32 -10
- package/dist/session/process-cost.js +189 -1
- package/dist/session/product-state-hash.js +41 -24
- package/dist/session/ritual-entrypoint.js +16 -1
- package/dist/session/session-start.d.ts +18 -0
- package/dist/session/session-start.js +49 -2
- package/dist/session/verify-session-ritual.d.ts +46 -4
- package/dist/session/verify-session-ritual.js +118 -10
- 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 +14 -2
- package/dist/swarm/launch.d.ts +69 -0
- package/dist/swarm/launch.js +191 -1
- package/dist/swarm/routing-set-cli.js +2 -0
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +2 -2
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/bootstrap/index.js +6 -6
- 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 +22 -8
- package/dist/triage/help/registry-data.js +54 -6
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/welcome/writers.d.ts +1 -1
- package/dist/triage/welcome/writers.js +10 -5
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/value/readback.d.ts +4 -1
- package/dist/value/readback.js +51 -12
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/schema.d.ts +4 -0
- package/dist/vbrief-validate/schema.js +1 -1
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- 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/xbrief-migrate/agents-header.js +69 -7
- package/package.json +7 -3
package/dist/scm/main.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { extractFlag } from "./argv.js";
|
|
3
3
|
import { buildCommand } from "./build-command.js";
|
|
4
4
|
import { REST_OPT_IN_VERBS } from "./constants.js";
|
|
5
|
+
import { DESIGN_CRITIQUE_CHIP_VERB, runDesignCritiqueChip } from "./design-critique-chip.js";
|
|
5
6
|
import { ScmStubError } from "./errors.js";
|
|
6
7
|
import { requireScmReady } from "./readiness.js";
|
|
7
8
|
import { runRestList, runRestView } from "./rest-dispatch.js";
|
|
@@ -30,13 +31,26 @@ function guardScmReady(options) {
|
|
|
30
31
|
export function main(argv, options = {}) {
|
|
31
32
|
if (argv.length < 2) {
|
|
32
33
|
process.stderr.write("usage: scm.py <namespace> <verb> [pass-through args...]\n" +
|
|
33
|
-
" (v1 stub: namespace=issue, verb=list|view|close|edit)\n" +
|
|
34
|
+
" (v1 stub: namespace=issue, verb=list|view|close|edit|design-critique-chip)\n" +
|
|
34
35
|
" --rest opt-in is supported on issue view/list (#976)\n");
|
|
35
36
|
return 2;
|
|
36
37
|
}
|
|
37
38
|
const namespace = argv[0] ?? "";
|
|
38
39
|
const verb = argv[1] ?? "";
|
|
39
40
|
let extra = argv.slice(2);
|
|
41
|
+
if (namespace === "issue" && verb === DESIGN_CRITIQUE_CHIP_VERB) {
|
|
42
|
+
const blocked = guardScmReady(options);
|
|
43
|
+
if (blocked !== null)
|
|
44
|
+
return blocked;
|
|
45
|
+
const result = runDesignCritiqueChip(extra, { client: options.labelClient });
|
|
46
|
+
if (result.stdout.length > 0) {
|
|
47
|
+
process.stdout.write(result.stdout);
|
|
48
|
+
}
|
|
49
|
+
if (result.stderr.length > 0) {
|
|
50
|
+
process.stderr.write(result.stderr);
|
|
51
|
+
}
|
|
52
|
+
return result.exitCode;
|
|
53
|
+
}
|
|
40
54
|
const [restMode, afterRest] = extractFlag(extra, "--rest");
|
|
41
55
|
extra = afterRest;
|
|
42
56
|
if (restMode) {
|
|
@@ -10,6 +10,8 @@ export interface ScmReadinessCliArgs {
|
|
|
10
10
|
readonly deep?: boolean;
|
|
11
11
|
readonly depth?: ScmProbeDepth;
|
|
12
12
|
readonly help?: boolean;
|
|
13
|
+
readonly repo?: string;
|
|
14
|
+
readonly expectedLogin?: string;
|
|
13
15
|
}
|
|
14
16
|
export declare function parseScmReadinessArgs(argv: readonly string[]): {
|
|
15
17
|
args: ScmReadinessCliArgs;
|
|
@@ -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";
|
|
@@ -64,6 +64,7 @@ export interface ProbeScmReadinessOptions {
|
|
|
64
64
|
readonly runtimeReport?: RuntimeCapabilityReport;
|
|
65
65
|
readonly githubAuthMode?: string | null;
|
|
66
66
|
readonly repo?: string;
|
|
67
|
+
readonly expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
|
|
67
68
|
readonly runGh?: GhRunner;
|
|
68
69
|
/** When false, skip even `gh auth status` on the shallow path (tests / pure PATH). */
|
|
69
70
|
readonly checkAuthStatus?: boolean;
|
package/dist/scm/readiness.js
CHANGED
|
@@ -105,7 +105,12 @@ function mapDeepFailureToAuthState(result) {
|
|
|
105
105
|
return "missing-token";
|
|
106
106
|
if (result.failureKind === "gh_auth_failed")
|
|
107
107
|
return "unauthenticated";
|
|
108
|
-
if (result.failureKind === "api_unreachable" ||
|
|
108
|
+
if (result.failureKind === "api_unreachable" ||
|
|
109
|
+
result.failureKind === "repo_access_denied" ||
|
|
110
|
+
result.failureKind === "missing_expected_principal" ||
|
|
111
|
+
result.failureKind === "principal_mismatch" ||
|
|
112
|
+
result.failureKind === "missing_target_repo" ||
|
|
113
|
+
result.failureKind === "installation_identity_unverifiable") {
|
|
109
114
|
return "unauthenticated";
|
|
110
115
|
}
|
|
111
116
|
return "unknown";
|
|
@@ -192,6 +197,7 @@ export function probeScmReadiness(options = {}) {
|
|
|
192
197
|
environ: env,
|
|
193
198
|
runtimeReport,
|
|
194
199
|
repo: options.repo,
|
|
200
|
+
expectedPrincipal: options.expectedPrincipal,
|
|
195
201
|
runGh: deepRunner,
|
|
196
202
|
});
|
|
197
203
|
if (deep.ok) {
|
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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
|
|
4
|
+
import { interceptHelp } from "../triage/help/index.js";
|
|
4
5
|
import { reconcileUmbrellas, renderUmbrellasReport } from "../vbrief-reconcile/umbrellas.js";
|
|
5
6
|
import { canonicalLogPath, readAll } from "./audit-log.js";
|
|
6
7
|
import { batchPromote } from "./batch-promote.js";
|
|
@@ -13,14 +14,15 @@ import { promoteFromIssue } from "./promote-from-issue.js";
|
|
|
13
14
|
import { promotePath } from "./promote-path.js";
|
|
14
15
|
import { runTransition } from "./transition.js";
|
|
15
16
|
import { findByDecisionId, isAlreadyUndone, REVERSIBLE_ACTIONS, undoBatch, undoOne, } from "./undo.js";
|
|
16
|
-
const LIFECYCLE_USAGE_STDERR = "usage:
|
|
17
|
-
" [--
|
|
17
|
+
const LIFECYCLE_USAGE_STDERR = "usage: deft scope:<action> [file] [--batch] [--from-issue N] [--repo OWNER/NAME]\n" +
|
|
18
|
+
" [--force] [--project-root PATH] [--strict] [--force-no-cache]\n" +
|
|
18
19
|
" [--path PATH]\n" +
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
20
|
+
" complete also accepts: --merge-commit SHA --pr N [--pr-base BRANCH] [--delivery-branch BRANCH]\n" +
|
|
21
|
+
" actions: activate, block, cancel, complete, fail, promote, restore, unblock\n" +
|
|
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" };
|
|
@@ -50,6 +52,8 @@ function parseLifecycleArgv(argv) {
|
|
|
50
52
|
if (arg === undefined) {
|
|
51
53
|
return { args: null, error: "usage" };
|
|
52
54
|
}
|
|
55
|
+
if (arg === "--")
|
|
56
|
+
continue;
|
|
53
57
|
if (arg === "--force") {
|
|
54
58
|
force = true;
|
|
55
59
|
}
|
|
@@ -236,6 +240,14 @@ function parseLifecycleArgv(argv) {
|
|
|
236
240
|
}
|
|
237
241
|
/** Main entry for scope_lifecycle.py parity. */
|
|
238
242
|
export function lifecycleMain(argv) {
|
|
243
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
244
|
+
const helpRc = interceptHelp("scope_lifecycle", argv);
|
|
245
|
+
if (helpRc !== null) {
|
|
246
|
+
return helpRc;
|
|
247
|
+
}
|
|
248
|
+
process.stdout.write(LIFECYCLE_USAGE_STDERR);
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
239
251
|
const parsed = parseLifecycleArgv(argv);
|
|
240
252
|
if (parsed.args === null) {
|
|
241
253
|
if (parsed.error === "usage") {
|
|
@@ -195,7 +195,7 @@ export function promoteFromIssue(options) {
|
|
|
195
195
|
return {
|
|
196
196
|
ok: false,
|
|
197
197
|
message: `No triage-cache decision for #${n} (${repoSafe}). ` +
|
|
198
|
-
`Accept first:
|
|
198
|
+
`Accept first: deft triage:accept --issue ${n} --repo ${repoSafe} ` +
|
|
199
199
|
`(or omit --strict to soft-warn and proceed; --force-no-cache skips the gate).`,
|
|
200
200
|
exitCode: 1,
|
|
201
201
|
warnings,
|
|
@@ -212,7 +212,7 @@ export function promoteFromIssue(options) {
|
|
|
212
212
|
ok: false,
|
|
213
213
|
message: `Refusing promote for #${n} (${repoSafe}): latest triage decision is '${sanitizeMsg(latest.decision)}' ` +
|
|
214
214
|
`(decision_id=${latest.decision_id}). ` +
|
|
215
|
-
`Accept first:
|
|
215
|
+
`Accept first: deft triage:accept --issue ${n} --repo ${repoSafe} ` +
|
|
216
216
|
`or override with --force-no-cache.`,
|
|
217
217
|
exitCode: 1,
|
|
218
218
|
warnings,
|
|
@@ -315,8 +315,8 @@ export function promoteFromIssue(options) {
|
|
|
315
315
|
return {
|
|
316
316
|
ok: false,
|
|
317
317
|
message: `No proposed/ scope artifact found for issue #${n} (${repoSafe}). ` +
|
|
318
|
-
`Ingest first (
|
|
319
|
-
`or pass an explicit path:
|
|
318
|
+
`Ingest first (deft triage:accept --issue ${n} --repo ${repoSafe}) ` +
|
|
319
|
+
`or pass an explicit path: deft scope:promote <path>.`,
|
|
320
320
|
exitCode: 1,
|
|
321
321
|
warnings,
|
|
322
322
|
repo,
|
|
@@ -330,7 +330,7 @@ export function promoteFromIssue(options) {
|
|
|
330
330
|
return {
|
|
331
331
|
ok: false,
|
|
332
332
|
message: `Multiple proposed/ artifacts match issue #${n}; refuse to guess.\n${list}\n` +
|
|
333
|
-
`Promote one path explicitly:
|
|
333
|
+
`Promote one path explicitly: deft scope:promote <path> ` +
|
|
334
334
|
`or re-run with --path <one-of-the-above>.`,
|
|
335
335
|
exitCode: 1,
|
|
336
336
|
warnings,
|
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))
|
|
@@ -205,11 +205,15 @@ function listActiveXbriefPaths(projectRoot) {
|
|
|
205
205
|
}
|
|
206
206
|
function remediationForExpansion() {
|
|
207
207
|
return ("Renew human approval: re-record the approved-scope digest after operator review " +
|
|
208
|
-
"(`task scope:record-approved-scope -- <xbrief-path> --actor <you
|
|
208
|
+
"(`task scope:record-approved-scope -- <xbrief-path> --actor <you> --confirm` writes " +
|
|
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).
|
|
@@ -344,7 +348,7 @@ export function evaluateOneScopeProvenance(input) {
|
|
|
344
348
|
detail: "active xBRIEF modified with a non-human (agent/missing) approved-scope stamp; " +
|
|
345
349
|
"only humanApproval stamps authorize non-empty file_scope",
|
|
346
350
|
remediation: "Record a human-origin approval via `task scope:record-approved-scope -- " +
|
|
347
|
-
"<xbrief-path> --actor <you
|
|
351
|
+
"<xbrief-path> --actor <you> --confirm` (#3145 / #3205).",
|
|
348
352
|
};
|
|
349
353
|
}
|
|
350
354
|
}
|
|
@@ -361,7 +365,7 @@ export function evaluateOneScopeProvenance(input) {
|
|
|
361
365
|
detail: "active xBRIEF modified with a non-human approved-scope stamp (scope shrink/noise path); " +
|
|
362
366
|
"only humanApproval stamps authorize non-empty file_scope",
|
|
363
367
|
remediation: "Record a human-origin approval via `task scope:record-approved-scope -- " +
|
|
364
|
-
"<xbrief-path> --actor <you
|
|
368
|
+
"<xbrief-path> --actor <you> --confirm` (#3145 / #3205).",
|
|
365
369
|
};
|
|
366
370
|
}
|
|
367
371
|
return null;
|
|
@@ -173,6 +173,11 @@ export interface MaybeBankOnAcPassInput {
|
|
|
173
173
|
readonly executableRuns: number;
|
|
174
174
|
readonly quiet?: boolean;
|
|
175
175
|
readonly productStateHash?: string | null;
|
|
176
|
+
/**
|
|
177
|
+
* Bank a verified-pass walk even when no shell runs were recorded (#3558).
|
|
178
|
+
* Soft/advisory empty passes still skip via executableRuns === 0.
|
|
179
|
+
*/
|
|
180
|
+
readonly verifiedPass?: boolean;
|
|
176
181
|
}
|
|
177
182
|
export interface MaybeBankOnAcPassResult {
|
|
178
183
|
readonly banked: boolean;
|