@deftai/directive-core 0.90.0 → 0.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/fetch.js +3 -2
- package/dist/cache/io.d.ts +13 -2
- package/dist/cache/io.js +36 -7
- package/dist/cache/operations.js +5 -3
- package/dist/doctor/main.js +37 -0
- package/dist/doctor/openclaw-skills.d.ts +42 -8
- package/dist/doctor/openclaw-skills.js +281 -38
- package/dist/eval/health.d.ts +10 -1
- package/dist/eval/health.js +16 -16
- package/dist/hooks/dispatcher.d.ts +6 -1
- package/dist/hooks/dispatcher.js +30 -0
- package/dist/init-deposit/gitignore.js +3 -0
- package/dist/init-deposit/hygiene.d.ts +43 -1
- package/dist/init-deposit/hygiene.js +112 -7
- package/dist/init-deposit/init-deposit.d.ts +5 -0
- package/dist/init-deposit/init-deposit.js +37 -2
- package/dist/init-deposit/scaffold.js +3 -1
- package/dist/intake/github-auth-modes.d.ts +1 -1
- package/dist/intake/github-auth-modes.js +25 -3
- package/dist/intake/issue-ingest-cli.js +13 -0
- package/dist/intake/reconcile-issues-cli.js +13 -0
- package/dist/orchestration/probe-session.d.ts +5 -1
- package/dist/orchestration/probe-session.js +24 -13
- package/dist/platform/agents-md.js +1 -1
- package/dist/policy/deft-directive-disable.d.ts +86 -0
- package/dist/policy/deft-directive-disable.js +167 -0
- package/dist/policy/delivery-branch.d.ts +33 -0
- package/dist/policy/delivery-branch.js +124 -0
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +17 -1
- package/dist/policy/plan-extensions.d.ts +10 -0
- package/dist/policy/plan-extensions.js +16 -0
- package/dist/pr-closing-keywords/detect.d.ts +13 -0
- package/dist/pr-closing-keywords/detect.js +73 -8
- package/dist/pr-closing-keywords/index.d.ts +2 -2
- package/dist/pr-closing-keywords/index.js +1 -1
- package/dist/pr-closing-keywords/main.js +119 -90
- package/dist/pr-closing-keywords/types.d.ts +10 -0
- package/dist/render/constants.d.ts +16 -0
- package/dist/render/constants.js +16 -1
- package/dist/render/export-spec.d.ts +7 -1
- package/dist/render/export-spec.js +51 -5
- package/dist/render/index.d.ts +3 -2
- package/dist/render/index.js +2 -2
- package/dist/render/roadmap-render.js +164 -60
- package/dist/render/scope-outlook.d.ts +5 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.d.ts +28 -2
- package/dist/render/spec-render.js +111 -14
- package/dist/render/text-utils.d.ts +6 -0
- package/dist/render/text-utils.js +23 -0
- package/dist/scm/binary.d.ts +3 -0
- package/dist/scm/binary.js +10 -2
- package/dist/scm/call.d.ts +5 -0
- package/dist/scm/call.js +5 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +5 -0
- package/dist/scm/main.js +27 -0
- package/dist/scm/readiness-cli.d.ts +24 -0
- package/dist/scm/readiness-cli.js +85 -0
- package/dist/scm/readiness.d.ts +104 -0
- package/dist/scm/readiness.js +421 -0
- package/dist/scope/batch-promote.d.ts +26 -0
- package/dist/scope/batch-promote.js +167 -0
- package/dist/scope/brief-io.d.ts +3 -1
- package/dist/scope/brief-io.js +5 -3
- package/dist/scope/delivery-evidence.d.ts +112 -0
- package/dist/scope/delivery-evidence.js +419 -0
- package/dist/scope/index.d.ts +2 -0
- package/dist/scope/index.js +2 -0
- package/dist/scope/main.d.ts +7 -0
- package/dist/scope/main.js +155 -9
- package/dist/scope/registry-artifact-sync.js +4 -1
- package/dist/scope/transition.d.ts +11 -1
- package/dist/scope/transition.js +30 -4
- package/dist/scope/wip-cap-check.d.ts +6 -0
- package/dist/scope/wip-cap-check.js +14 -2
- package/dist/session/ritual-sentinel.js +21 -12
- package/dist/session/session-start-hook.d.ts +3 -0
- package/dist/session/session-start-hook.js +21 -0
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +85 -0
- package/dist/swarm/complete-cohort.d.ts +20 -1
- package/dist/swarm/complete-cohort.js +56 -9
- package/dist/swarm/finalize-cohort-cli.js +9 -0
- package/dist/swarm/finalize-cohort.d.ts +8 -0
- package/dist/swarm/finalize-cohort.js +217 -21
- package/dist/triage/welcome/index.d.ts +1 -1
- package/dist/triage/welcome/index.js +1 -1
- package/dist/triage/welcome/onboard.js +7 -1
- package/dist/triage/welcome/prior-state.d.ts +7 -0
- package/dist/triage/welcome/prior-state.js +16 -2
- package/dist/triage/welcome/writers.d.ts +10 -0
- package/dist/triage/welcome/writers.js +72 -2
- package/dist/user-config/experimental-rules.d.ts +43 -0
- package/dist/user-config/experimental-rules.js +162 -0
- package/dist/user-config/index.d.ts +1 -0
- package/dist/user-config/index.js +1 -0
- package/dist/verify-source/contained-writes.js +1 -1
- package/package.json +3 -3
package/dist/scope/main.js
CHANGED
|
@@ -3,19 +3,22 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
|
|
4
4
|
import { reconcileUmbrellas, renderUmbrellasReport } from "../vbrief-reconcile/umbrellas.js";
|
|
5
5
|
import { canonicalLogPath, readAll } from "./audit-log.js";
|
|
6
|
+
import { batchPromote } from "./batch-promote.js";
|
|
6
7
|
import { TRANSITIONS } from "./constants.js";
|
|
8
|
+
import { isNonDeliveryDisposition, } from "./delivery-evidence.js";
|
|
7
9
|
import { batchDemote, DEFAULT_OLDER_THAN_DAYS, demoteOne, resolveDemoteFilePath, resolveFilePath, resolveProjectRootStrict, } from "./demote.js";
|
|
8
10
|
import { completedPathForScopeMove, findOpenUmbrellaReferences, renderOpenUmbrellaWarning, } from "./open-umbrella-warning.js";
|
|
9
11
|
import { resolveProjectRoot } from "./project-context.js";
|
|
10
12
|
import { recordWipCapOverride, runTransition } from "./transition.js";
|
|
11
13
|
import { findByDecisionId, isAlreadyUndone, REVERSIBLE_ACTIONS, undoBatch, undoOne, } from "./undo.js";
|
|
12
14
|
import { checkWipCap, formatWipCapRefusal } from "./wip-cap-check.js";
|
|
13
|
-
const LIFECYCLE_USAGE_STDERR = "usage: scope_lifecycle.py [-h] [--project-root PROJECT_ROOT] [--force]\n" +
|
|
15
|
+
const LIFECYCLE_USAGE_STDERR = "usage: scope_lifecycle.py [-h] [--project-root PROJECT_ROOT] [--force] [--batch]\n" +
|
|
14
16
|
" {activate,block,cancel,complete,fail,promote,restore,unblock}\n" +
|
|
15
|
-
" file\n" +
|
|
16
|
-
"scope_lifecycle.py: error: the following arguments are required: action, file\n"
|
|
17
|
+
" [file ...]\n" +
|
|
18
|
+
"scope_lifecycle.py: error: the following arguments are required: action, file\n" +
|
|
19
|
+
"(promote --batch may omit file and promotes all proposed/ scopes; #3011)\n";
|
|
17
20
|
function parseLifecycleArgv(argv) {
|
|
18
|
-
if (argv.length <
|
|
21
|
+
if (argv.length < 1) {
|
|
19
22
|
return { args: null, error: "usage" };
|
|
20
23
|
}
|
|
21
24
|
const action = argv[0] ?? "";
|
|
@@ -25,6 +28,15 @@ function parseLifecycleArgv(argv) {
|
|
|
25
28
|
let file = "";
|
|
26
29
|
let projectRoot;
|
|
27
30
|
let force = false;
|
|
31
|
+
let batch = false;
|
|
32
|
+
const batchFiles = [];
|
|
33
|
+
let nonDeliveryDisposition;
|
|
34
|
+
let prNumber;
|
|
35
|
+
let mergeCommit;
|
|
36
|
+
let prBase;
|
|
37
|
+
let deliveryBranch;
|
|
38
|
+
let repo;
|
|
39
|
+
let mergedAt;
|
|
28
40
|
for (let i = 1; i < argv.length; i += 1) {
|
|
29
41
|
const arg = argv[i];
|
|
30
42
|
if (arg === undefined) {
|
|
@@ -33,6 +45,9 @@ function parseLifecycleArgv(argv) {
|
|
|
33
45
|
if (arg === "--force") {
|
|
34
46
|
force = true;
|
|
35
47
|
}
|
|
48
|
+
else if (arg === "--batch") {
|
|
49
|
+
batch = true;
|
|
50
|
+
}
|
|
36
51
|
else if (arg === "--project-root") {
|
|
37
52
|
projectRoot = argv[i + 1];
|
|
38
53
|
i += 1;
|
|
@@ -40,17 +55,122 @@ function parseLifecycleArgv(argv) {
|
|
|
40
55
|
else if (arg?.startsWith("--project-root=")) {
|
|
41
56
|
projectRoot = arg.slice("--project-root=".length);
|
|
42
57
|
}
|
|
43
|
-
else if (
|
|
44
|
-
|
|
58
|
+
else if (arg === "--non-delivery" || arg === "--non-delivery-disposition") {
|
|
59
|
+
const raw = argv[i + 1];
|
|
60
|
+
i += 1;
|
|
61
|
+
if (raw === undefined || !isNonDeliveryDisposition(raw)) {
|
|
62
|
+
return { args: null, error: "usage" };
|
|
63
|
+
}
|
|
64
|
+
nonDeliveryDisposition = raw;
|
|
65
|
+
}
|
|
66
|
+
else if (arg?.startsWith("--non-delivery=")) {
|
|
67
|
+
const raw = arg.slice("--non-delivery=".length);
|
|
68
|
+
if (!isNonDeliveryDisposition(raw)) {
|
|
69
|
+
return { args: null, error: "usage" };
|
|
70
|
+
}
|
|
71
|
+
nonDeliveryDisposition = raw;
|
|
72
|
+
}
|
|
73
|
+
else if (arg === "--pr" && argv[i + 1] !== undefined) {
|
|
74
|
+
prNumber = Number.parseInt(argv[i + 1] ?? "", 10);
|
|
75
|
+
i += 1;
|
|
76
|
+
}
|
|
77
|
+
else if (arg?.startsWith("--pr=")) {
|
|
78
|
+
prNumber = Number.parseInt(arg.slice("--pr=".length), 10);
|
|
79
|
+
}
|
|
80
|
+
else if (arg === "--merge-commit" && argv[i + 1] !== undefined) {
|
|
81
|
+
mergeCommit = argv[i + 1];
|
|
82
|
+
i += 1;
|
|
83
|
+
}
|
|
84
|
+
else if (arg?.startsWith("--merge-commit=")) {
|
|
85
|
+
mergeCommit = arg.slice("--merge-commit=".length);
|
|
86
|
+
}
|
|
87
|
+
else if (arg === "--pr-base" && argv[i + 1] !== undefined) {
|
|
88
|
+
prBase = argv[i + 1];
|
|
89
|
+
i += 1;
|
|
90
|
+
}
|
|
91
|
+
else if (arg?.startsWith("--pr-base=")) {
|
|
92
|
+
prBase = arg.slice("--pr-base=".length);
|
|
93
|
+
}
|
|
94
|
+
else if (arg === "--delivery-branch" && argv[i + 1] !== undefined) {
|
|
95
|
+
deliveryBranch = argv[i + 1];
|
|
96
|
+
i += 1;
|
|
97
|
+
}
|
|
98
|
+
else if (arg?.startsWith("--delivery-branch=")) {
|
|
99
|
+
deliveryBranch = arg.slice("--delivery-branch=".length);
|
|
100
|
+
}
|
|
101
|
+
else if (arg === "--repo" && argv[i + 1] !== undefined) {
|
|
102
|
+
repo = argv[i + 1];
|
|
103
|
+
i += 1;
|
|
104
|
+
}
|
|
105
|
+
else if (arg?.startsWith("--repo=")) {
|
|
106
|
+
repo = arg.slice("--repo=".length);
|
|
107
|
+
}
|
|
108
|
+
else if (arg === "--merged-at" && argv[i + 1] !== undefined) {
|
|
109
|
+
mergedAt = argv[i + 1];
|
|
110
|
+
i += 1;
|
|
111
|
+
}
|
|
112
|
+
else if (arg?.startsWith("--merged-at=")) {
|
|
113
|
+
mergedAt = arg.slice("--merged-at=".length);
|
|
114
|
+
}
|
|
115
|
+
else if (!arg.startsWith("-")) {
|
|
116
|
+
if (batch) {
|
|
117
|
+
batchFiles.push(arg);
|
|
118
|
+
}
|
|
119
|
+
else if (file.length === 0) {
|
|
120
|
+
file = arg;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
return { args: null, error: "usage" };
|
|
124
|
+
}
|
|
45
125
|
}
|
|
46
126
|
else {
|
|
47
127
|
return { args: null, error: "usage" };
|
|
48
128
|
}
|
|
49
129
|
}
|
|
130
|
+
const deliveryEvidence = prNumber !== undefined ||
|
|
131
|
+
mergeCommit !== undefined ||
|
|
132
|
+
prBase !== undefined ||
|
|
133
|
+
deliveryBranch !== undefined ||
|
|
134
|
+
repo !== undefined ||
|
|
135
|
+
mergedAt !== undefined
|
|
136
|
+
? {
|
|
137
|
+
prNumber: prNumber !== undefined && Number.isFinite(prNumber) ? prNumber : null,
|
|
138
|
+
mergeCommit: mergeCommit ?? null,
|
|
139
|
+
prBase: prBase ?? null,
|
|
140
|
+
deliveryBranch: deliveryBranch ?? null,
|
|
141
|
+
repository: repo ?? null,
|
|
142
|
+
mergedAt: mergedAt ?? (mergeCommit !== undefined ? "supplied" : null),
|
|
143
|
+
verifier: "scope:complete",
|
|
144
|
+
}
|
|
145
|
+
: undefined;
|
|
146
|
+
if (batch) {
|
|
147
|
+
if (action !== "promote") {
|
|
148
|
+
return { args: null, error: "usage" };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
args: {
|
|
152
|
+
action,
|
|
153
|
+
file: file.length > 0 ? file : "",
|
|
154
|
+
projectRoot,
|
|
155
|
+
force,
|
|
156
|
+
batch: true,
|
|
157
|
+
batchFiles: batchFiles.length > 0 ? batchFiles : file.length > 0 ? [file] : [],
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
50
161
|
if (file.length === 0) {
|
|
51
162
|
return { args: null, error: "usage" };
|
|
52
163
|
}
|
|
53
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
args: {
|
|
166
|
+
action,
|
|
167
|
+
file,
|
|
168
|
+
projectRoot,
|
|
169
|
+
force,
|
|
170
|
+
nonDeliveryDisposition,
|
|
171
|
+
deliveryEvidence,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
54
174
|
}
|
|
55
175
|
/** Main entry for scope_lifecycle.py parity. */
|
|
56
176
|
export function lifecycleMain(argv) {
|
|
@@ -61,7 +181,28 @@ export function lifecycleMain(argv) {
|
|
|
61
181
|
}
|
|
62
182
|
return 2;
|
|
63
183
|
}
|
|
64
|
-
const { action, file, projectRoot, force } = parsed.args;
|
|
184
|
+
const { action, file, projectRoot, force, batch, batchFiles, nonDeliveryDisposition, deliveryEvidence, } = parsed.args;
|
|
185
|
+
if (batch === true && action === "promote") {
|
|
186
|
+
const result = batchPromote({
|
|
187
|
+
files: batchFiles && batchFiles.length > 0 ? batchFiles : undefined,
|
|
188
|
+
force: force === true,
|
|
189
|
+
projectRoot,
|
|
190
|
+
});
|
|
191
|
+
for (const line of result.messages) {
|
|
192
|
+
process.stdout.write(`${line}\n`);
|
|
193
|
+
}
|
|
194
|
+
for (const line of result.skipped) {
|
|
195
|
+
process.stdout.write(` skipped: ${line}\n`);
|
|
196
|
+
}
|
|
197
|
+
if (!result.ok && result.exitCode !== 0) {
|
|
198
|
+
for (const line of result.messages) {
|
|
199
|
+
if (line.startsWith("ERROR:") || line.includes("WIP cap")) {
|
|
200
|
+
process.stderr.write(`${line}\n`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return result.exitCode;
|
|
205
|
+
}
|
|
65
206
|
const [filePath, error] = resolveFilePath(file, projectRoot);
|
|
66
207
|
if (error !== null || filePath === null) {
|
|
67
208
|
process.stderr.write(`Error: ${error}\n`);
|
|
@@ -78,7 +219,12 @@ export function lifecycleMain(argv) {
|
|
|
78
219
|
}
|
|
79
220
|
}
|
|
80
221
|
}
|
|
81
|
-
const
|
|
222
|
+
const transitionOptions = {
|
|
223
|
+
nonDeliveryDisposition,
|
|
224
|
+
deliveryEvidence,
|
|
225
|
+
verifier: "scope:complete",
|
|
226
|
+
};
|
|
227
|
+
const result = runTransition(action, filePath, new Date(), transitionOptions);
|
|
82
228
|
if (result.ok) {
|
|
83
229
|
if (action === "promote" && capCheck !== null && capCheck.forceOverride) {
|
|
84
230
|
const rootForAudit = resolveProjectRoot(projectRoot);
|
|
@@ -164,7 +164,10 @@ export function syncRegistryArtifactAfterScopeMove(registryPath, scopeData, oldP
|
|
|
164
164
|
hooks.persist(registryPath, registry);
|
|
165
165
|
}
|
|
166
166
|
else {
|
|
167
|
-
|
|
167
|
+
// #3042: contain registry stay-path write against project root (parent of xbrief/).
|
|
168
|
+
atomicWriteText(registryPath, formatBriefJson(registry), {
|
|
169
|
+
projectRoot: dirname(resolve(vbriefRoot)),
|
|
170
|
+
});
|
|
168
171
|
}
|
|
169
172
|
}
|
|
170
173
|
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
import type { GitRunner } from "../session/git.js";
|
|
1
2
|
import { detectLifecycleFolder } from "./decomposed-refs.js";
|
|
3
|
+
import { type DeliveryEvidenceInput, type NonDeliveryDisposition } from "./delivery-evidence.js";
|
|
2
4
|
import type { WipCapCheck } from "./wip-cap-check.js";
|
|
3
5
|
export interface TransitionResult {
|
|
4
6
|
readonly ok: boolean;
|
|
5
7
|
readonly message: string;
|
|
6
8
|
}
|
|
7
|
-
|
|
9
|
+
/** Optional completion evidence / disposition for the delivery gate (#3041). */
|
|
10
|
+
export interface TransitionOptions {
|
|
11
|
+
readonly deliveryEvidence?: DeliveryEvidenceInput | null;
|
|
12
|
+
readonly nonDeliveryDisposition?: NonDeliveryDisposition | null;
|
|
13
|
+
readonly runGit?: GitRunner;
|
|
14
|
+
readonly verifier?: string;
|
|
15
|
+
readonly assumeEvidenceValidated?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function runTransition(action: string, filePath: string, now?: Date, options?: TransitionOptions): TransitionResult;
|
|
8
18
|
export declare function recordWipCapOverride(filePath: string, projectRoot: string, check: WipCapCheck, now?: Date): void;
|
|
9
19
|
export { detectLifecycleFolder };
|
|
10
20
|
//# sourceMappingURL=transition.d.ts.map
|
package/dist/scope/transition.js
CHANGED
|
@@ -8,6 +8,7 @@ import { atomicWriteBrief, formatBriefJson, readBriefForMutation } from "./brief
|
|
|
8
8
|
import { stampCompletionMetadata } from "./capacity-stamp.js";
|
|
9
9
|
import { LIFECYCLE_FOLDERS, MOVE_LABELS, STATUS_PRECONDITIONS, STAY_LABELS, TRANSITIONS, } from "./constants.js";
|
|
10
10
|
import { detectLifecycleFolder, updateDecomposedChildBackReferences, updateDecomposedParentBackReferences, } from "./decomposed-refs.js";
|
|
11
|
+
import { classifyStoredDeliveryDisposition, evaluateDeliveryGate, stampDeliveryProvenance, } from "./delivery-evidence.js";
|
|
11
12
|
import { syncProjectDefinitionAfterScopeMove } from "./project-definition-sync.js";
|
|
12
13
|
import { syncSpecificationAfterScopeMove } from "./specification-sync.js";
|
|
13
14
|
import { utcNowIso } from "./vbrief-json.js";
|
|
@@ -48,7 +49,7 @@ function stampEnvelopeUpdated(data, nowIso) {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
|
-
export function runTransition(action, filePath, now = new Date()) {
|
|
52
|
+
export function runTransition(action, filePath, now = new Date(), options = {}) {
|
|
52
53
|
if (!(action in TRANSITIONS)) {
|
|
53
54
|
const valid = Object.keys(TRANSITIONS).sort().join(", ");
|
|
54
55
|
return { ok: false, message: `Unknown action '${action}'. Valid actions: ${valid}` };
|
|
@@ -95,9 +96,15 @@ export function runTransition(action, filePath, now = new Date()) {
|
|
|
95
96
|
const requiredStatus = STATUS_PRECONDITIONS[act];
|
|
96
97
|
if (requiredStatus !== undefined) {
|
|
97
98
|
if (currentStatus === targetStatus) {
|
|
99
|
+
// Surface legacy delivery disposition on already-completed briefs (#3041).
|
|
100
|
+
let dispositionSuffix = "";
|
|
101
|
+
if (act === "complete" && currentFolder === "completed") {
|
|
102
|
+
const disposition = classifyStoredDeliveryDisposition(planObj);
|
|
103
|
+
dispositionSuffix = ` (deliveryDisposition=${disposition})`;
|
|
104
|
+
}
|
|
98
105
|
return {
|
|
99
106
|
ok: true,
|
|
100
|
-
message: `No-op: ${basename} is already ${targetStatus} in ${currentFolder}
|
|
107
|
+
message: `No-op: ${basename} is already ${targetStatus} in ${currentFolder}/${dispositionSuffix}`,
|
|
101
108
|
};
|
|
102
109
|
}
|
|
103
110
|
if (currentStatus !== requiredStatus) {
|
|
@@ -131,6 +138,25 @@ export function runTransition(action, filePath, now = new Date()) {
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
const nowIso = utcNowIso(now);
|
|
141
|
+
// #3041: fail closed before mutating a code-bearing complete without delivery evidence.
|
|
142
|
+
if (act === "complete") {
|
|
143
|
+
const gate = evaluateDeliveryGate({
|
|
144
|
+
projectRoot,
|
|
145
|
+
plan: planObj,
|
|
146
|
+
nowIso,
|
|
147
|
+
evidence: options.deliveryEvidence,
|
|
148
|
+
nonDeliveryDisposition: options.nonDeliveryDisposition,
|
|
149
|
+
runGit: options.runGit,
|
|
150
|
+
verifier: options.verifier ?? "scope:complete",
|
|
151
|
+
assumeEvidenceValidated: options.assumeEvidenceValidated,
|
|
152
|
+
});
|
|
153
|
+
if (!gate.ok) {
|
|
154
|
+
return { ok: false, message: gate.message };
|
|
155
|
+
}
|
|
156
|
+
if (gate.provenance !== null) {
|
|
157
|
+
stampDeliveryProvenance(planObj, gate.provenance);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
134
160
|
planObj.status = targetStatus;
|
|
135
161
|
planObj.updated = nowIso;
|
|
136
162
|
// Keep the envelope clock aligned with plan.updated on every mutating transition (#2862).
|
|
@@ -153,7 +179,7 @@ export function runTransition(action, filePath, now = new Date()) {
|
|
|
153
179
|
}
|
|
154
180
|
// #2578: stamp terminal status at the destination path in the same write as
|
|
155
181
|
// folder placement — never leave a non-terminal status under completed/.
|
|
156
|
-
const writeResult = atomicWriteBrief(destPath, data, vbriefRoot);
|
|
182
|
+
const writeResult = atomicWriteBrief(destPath, data, vbriefRoot, { projectRoot });
|
|
157
183
|
if (!writeResult.ok) {
|
|
158
184
|
return { ok: false, message: writeResult.message };
|
|
159
185
|
}
|
|
@@ -193,7 +219,7 @@ export function runTransition(action, filePath, now = new Date()) {
|
|
|
193
219
|
message: `${actionLabel} ${basename}: ${currentFolder}/ -> ${targetFolder}/ (status: ${targetStatus})`,
|
|
194
220
|
};
|
|
195
221
|
}
|
|
196
|
-
const writeResult = atomicWriteBrief(resolvedPath, data, vbriefRoot);
|
|
222
|
+
const writeResult = atomicWriteBrief(resolvedPath, data, vbriefRoot, { projectRoot });
|
|
197
223
|
if (!writeResult.ok) {
|
|
198
224
|
return { ok: false, message: writeResult.message };
|
|
199
225
|
}
|
|
@@ -7,5 +7,11 @@ export interface WipCapCheck {
|
|
|
7
7
|
}
|
|
8
8
|
/** Resolve WIP cap and decide if promotion is allowed (#1124). */
|
|
9
9
|
export declare function checkWipCap(projectRoot: string, force?: boolean): WipCapCheck;
|
|
10
|
+
/**
|
|
11
|
+
* WIP check for promoting `additional` scopes in one batch (#3011).
|
|
12
|
+
* Single promote is `additional = 1` (same as historic count >= cap refuse).
|
|
13
|
+
* Batch of N refuses when `count + N > cap` unless `--force`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkWipCapForAdditional(projectRoot: string, additional: number, force?: boolean): WipCapCheck;
|
|
10
16
|
export declare function formatWipCapRefusal(check: WipCapCheck): string;
|
|
11
17
|
//# sourceMappingURL=wip-cap-check.d.ts.map
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { countVbriefWip, resolveWipCap } from "../policy/wip.js";
|
|
2
2
|
/** Resolve WIP cap and decide if promotion is allowed (#1124). */
|
|
3
3
|
export function checkWipCap(projectRoot, force = false) {
|
|
4
|
+
return checkWipCapForAdditional(projectRoot, 1, force);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* WIP check for promoting `additional` scopes in one batch (#3011).
|
|
8
|
+
* Single promote is `additional = 1` (same as historic count >= cap refuse).
|
|
9
|
+
* Batch of N refuses when `count + N > cap` unless `--force`.
|
|
10
|
+
*/
|
|
11
|
+
export function checkWipCapForAdditional(projectRoot, additional, force = false) {
|
|
4
12
|
const capResult = resolveWipCap(projectRoot);
|
|
5
13
|
const cap = capResult.cap;
|
|
6
14
|
const count = countVbriefWip(projectRoot);
|
|
7
|
-
const
|
|
15
|
+
const add = Math.max(0, Math.floor(additional));
|
|
16
|
+
// Single-promote parity: refuse when already at/over cap before adding one.
|
|
17
|
+
// Batch: refuse when current + additional would exceed the cap.
|
|
18
|
+
const overCap = add <= 1 ? count >= cap : count + add > cap;
|
|
8
19
|
if (!overCap) {
|
|
9
20
|
return { allowed: true, cap, count, source: capResult.source, forceOverride: false };
|
|
10
21
|
}
|
|
@@ -18,6 +29,7 @@ export function formatWipCapRefusal(check) {
|
|
|
18
29
|
"Either:\n" +
|
|
19
30
|
" task scope:demote <existing> # return one to proposed/\n" +
|
|
20
31
|
" task scope:demote --batch --older-than-days 30 # bulk relief (D9 folded into D1)\n" +
|
|
21
|
-
" task scope:promote <file> --force # override (logged)"
|
|
32
|
+
" task scope:promote <file> --force # override (logged)\n" +
|
|
33
|
+
" task scope:promote --batch --force # batch override (#3011)");
|
|
22
34
|
}
|
|
23
35
|
//# sourceMappingURL=wip-cap-check.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { join, relative, resolve } from "node:path";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { containedWrite } from "../fs/contained-write.js";
|
|
4
|
+
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
4
5
|
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
|
|
5
6
|
import { stableJson } from "./json.js";
|
|
6
7
|
import { RITUAL_STATE_CONTRACT } from "./posture.js";
|
|
@@ -95,21 +96,29 @@ function validateSteps(raw, key) {
|
|
|
95
96
|
}
|
|
96
97
|
return [steps, null];
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Contained atomic JSON write for ritual/sentinel state (#3042).
|
|
101
|
+
* Containment root is projectRoot (not dirname(target)) so a force-added `.deft`
|
|
102
|
+
* directory symlink fails closed before temp+rename.
|
|
103
|
+
*/
|
|
104
|
+
function atomicWriteJson(projectRoot, targetPath, payload, prefix) {
|
|
105
|
+
const root = resolve(projectRoot);
|
|
106
|
+
const abs = resolve(targetPath);
|
|
107
|
+
// Refuse leaf/parent symlinks on the final path before temp+rename publish.
|
|
108
|
+
assertWriteTargetSafe(root, abs);
|
|
109
|
+
const dir = dirname(abs);
|
|
110
|
+
const tmpBase = `${prefix}${process.pid}.${basename(abs)}.tmp`;
|
|
102
111
|
const tmpName = join(dir, tmpBase);
|
|
103
112
|
const text = `${stableJson(payload, 2)}\n`;
|
|
104
113
|
try {
|
|
105
|
-
// #2980 wave D:
|
|
114
|
+
// #2980 wave D / #3042: product write routes through containedWrite under projectRoot.
|
|
106
115
|
containedWrite({
|
|
107
|
-
root
|
|
108
|
-
target:
|
|
116
|
+
root,
|
|
117
|
+
target: tmpName,
|
|
109
118
|
data: text,
|
|
110
119
|
mode: "create",
|
|
111
120
|
});
|
|
112
|
-
renameSync(tmpName,
|
|
121
|
+
renameSync(tmpName, abs);
|
|
113
122
|
}
|
|
114
123
|
catch (err) {
|
|
115
124
|
try {
|
|
@@ -191,7 +200,7 @@ export function readRitualState(projectRoot) {
|
|
|
191
200
|
}
|
|
192
201
|
export function writeRitualState(projectRoot, payload) {
|
|
193
202
|
const stateFile = ritualStatePath(projectRoot);
|
|
194
|
-
atomicWriteJson(stateFile, payload, ".ritual-state.");
|
|
203
|
+
atomicWriteJson(projectRoot, stateFile, payload, ".ritual-state.");
|
|
195
204
|
return stateFile;
|
|
196
205
|
}
|
|
197
206
|
/** Instant guaranteed to fail `evaluateLoadedState` age checks on any policy horizon. */
|
|
@@ -303,7 +312,7 @@ export function writeSentinel(projectRoot, input) {
|
|
|
303
312
|
.replace(`${LEGACY_ARTIFACT_DIR}/active/`, `${MIGRATED_ARTIFACT_DIR}/active/`),
|
|
304
313
|
lastBranch: input.lastBranch,
|
|
305
314
|
};
|
|
306
|
-
atomicWriteJson(sentinelFile, payload, ".last-session.");
|
|
315
|
+
atomicWriteJson(projectRoot, sentinelFile, payload, ".last-session.");
|
|
307
316
|
return sentinelFile;
|
|
308
317
|
}
|
|
309
318
|
function formatElapsed(deltaMs) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { detectDeftDirectiveDisable } from "../policy/deft-directive-disable.js";
|
|
1
2
|
import { detectNoDeftDirective } from "../policy/no-deft-directive.js";
|
|
2
3
|
import { writeSentinel } from "./ritual-sentinel.js";
|
|
3
4
|
export interface SessionStartHookOptions {
|
|
@@ -7,6 +8,8 @@ export interface SessionStartHookOptions {
|
|
|
7
8
|
readonly writeSentinelFn?: typeof writeSentinel;
|
|
8
9
|
/** Test seam for #2926 opt-out detection. */
|
|
9
10
|
readonly detectNoDeftDirectiveFn?: typeof detectNoDeftDirective;
|
|
11
|
+
/** Test seam for #3039 test kill-switch detection. */
|
|
12
|
+
readonly detectDeftDirectiveDisableFn?: typeof detectDeftDirectiveDisable;
|
|
10
13
|
}
|
|
11
14
|
/** Write ``.deft/last-session.json`` from current git state (#1269). */
|
|
12
15
|
export declare function runSessionStartHookWrite(projectRoot: string, options?: SessionStartHookOptions): {
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import { resolveVersion } from "../doctor/paths.js";
|
|
2
|
+
import { detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
|
|
2
3
|
import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, } from "../policy/no-deft-directive.js";
|
|
3
4
|
import { detectBranch } from "./git.js";
|
|
4
5
|
import { detectLatestActiveVbrief, writeSentinel } from "./ritual-sentinel.js";
|
|
5
6
|
/** Write ``.deft/last-session.json`` from current git state (#1269). */
|
|
6
7
|
export function runSessionStartHookWrite(projectRoot, options = {}) {
|
|
8
|
+
const detectKill = options.detectDeftDirectiveDisableFn ?? detectDeftDirectiveDisable;
|
|
9
|
+
// #3039: local (untracked) kill-switch — skip ritual bookkeeping (deposit OK).
|
|
10
|
+
// Tracked flags do not short-circuit (enforcement stays on).
|
|
11
|
+
const kill = detectKill(projectRoot);
|
|
12
|
+
const killActive = options.detectDeftDirectiveDisableFn !== undefined
|
|
13
|
+
? kill.active
|
|
14
|
+
: isDeftDirectiveDisableActive(projectRoot);
|
|
15
|
+
if (killActive) {
|
|
16
|
+
const detectOptOut = options.detectNoDeftDirectiveFn ?? detectNoDeftDirective;
|
|
17
|
+
const optOut = detectOptOut(projectRoot);
|
|
18
|
+
const message = formatDeftDirectiveDisableMessage({
|
|
19
|
+
permanentOptOutAlsoPresent: optOut.present,
|
|
20
|
+
trackedByGit: false,
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
code: 0,
|
|
24
|
+
stdout: `${message}\n`,
|
|
25
|
+
stderr: "",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
7
28
|
const detectOptOut = options.detectNoDeftDirectiveFn ?? detectNoDeftDirective;
|
|
8
29
|
// #2926: root opt-out wins — host SessionStart must not write ritual bookkeeping.
|
|
9
30
|
const optOut = detectOptOut(projectRoot);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type EnvironmentContext } from "../platform/shell-context.js";
|
|
2
|
+
import { type ProbeScmReadinessOptions, type ScmReadinessReport } from "../scm/readiness.js";
|
|
2
3
|
import { type ResolveUserMdResult } from "../user-config/resolve-user-md.js";
|
|
3
4
|
import type { GitRunner } from "./git.js";
|
|
4
5
|
import { type ReleaseAvailabilityProbeOptions } from "./release-availability.js";
|
|
@@ -86,6 +87,12 @@ export interface SessionStartOptions {
|
|
|
86
87
|
readonly allowOptionalNetwork?: boolean;
|
|
87
88
|
/** Process env for network opt-in resolution (tests inject). */
|
|
88
89
|
readonly env?: NodeJS.ProcessEnv;
|
|
90
|
+
/**
|
|
91
|
+
* #2275: SCM tooling + auth readiness probe for mismatched/headless envs.
|
|
92
|
+
* Defaults to probeScmReadiness; shallow on hot path, deep when optional
|
|
93
|
+
* network is enabled. Inject in tests.
|
|
94
|
+
*/
|
|
95
|
+
readonly probeScm?: (options: ProbeScmReadinessOptions) => ScmReadinessReport;
|
|
89
96
|
}
|
|
90
97
|
/** Format preferred `session:start` recovery command for cold vs re-arm (#2992). */
|
|
91
98
|
export declare function formatSessionStartRecoveryCommand(tier?: SessionCeremonyTier): string;
|