@deftai/directive-core 0.91.0 → 0.93.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/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +49 -0
- package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
- package/dist/doctor/openclaw-l2-adapter.js +199 -0
- package/dist/hooks/dispatcher.d.ts +6 -1
- package/dist/hooks/dispatcher.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/gitignore.js +3 -0
- package/dist/init-deposit/hygiene.d.ts +43 -1
- package/dist/init-deposit/hygiene.js +134 -10
- package/dist/init-deposit/index.d.ts +3 -0
- package/dist/init-deposit/index.js +3 -0
- package/dist/init-deposit/init-deposit.js +10 -0
- package/dist/init-deposit/refresh.js +11 -0
- package/dist/init-deposit/scaffold.js +6 -134
- package/dist/init-deposit/skill-discovery-deposit.d.ts +65 -0
- package/dist/init-deposit/skill-discovery-deposit.js +287 -0
- package/dist/init-deposit/skill-discovery-hosts.d.ts +94 -0
- package/dist/init-deposit/skill-discovery-hosts.js +217 -0
- package/dist/init-deposit/slash-deposit.d.ts +46 -0
- package/dist/init-deposit/slash-deposit.js +174 -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/host-slash-commands.d.ts +28 -0
- package/dist/policy/host-slash-commands.js +103 -0
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +61 -5
- 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 +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/main.d.ts +5 -0
- package/dist/scope/main.js +98 -3
- 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/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.js +31 -0
- package/dist/slash/emitters.d.ts +102 -0
- package/dist/slash/emitters.js +148 -0
- package/dist/slash/generator.d.ts +98 -0
- package/dist/slash/generator.js +145 -0
- package/dist/slash/index.d.ts +16 -0
- package/dist/slash/index.js +16 -0
- package/dist/slash/openclaw-adapter.d.ts +64 -0
- package/dist/slash/openclaw-adapter.js +198 -0
- package/dist/slash/openclaw-deposit.d.ts +73 -0
- package/dist/slash/openclaw-deposit.js +279 -0
- package/dist/slash/openclaw-slugs.d.ts +52 -0
- package/dist/slash/openclaw-slugs.js +126 -0
- package/dist/slash/product-set.d.ts +50 -0
- package/dist/slash/product-set.js +142 -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/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/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +50 -0
- package/dist/verify-source/contained-writes.js +1 -1
- package/dist/xbrief/create.d.ts +36 -0
- package/dist/xbrief/create.js +285 -0
- package/dist/xbrief/index.d.ts +14 -0
- package/dist/xbrief/index.js +42 -0
- package/dist/xbrief/paths.d.ts +37 -0
- package/dist/xbrief/paths.js +123 -0
- package/dist/xbrief/styles.d.ts +36 -0
- package/dist/xbrief/styles.js +235 -0
- package/dist/xbrief/types.d.ts +50 -0
- package/dist/xbrief/types.js +17 -0
- package/dist/xbrief/verify.d.ts +30 -0
- package/dist/xbrief/verify.js +251 -0
- package/package.json +11 -3
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery integrity for scope completion (#3041).
|
|
3
|
+
*
|
|
4
|
+
* A code-bearing scope must not acquire a delivered disposition unless the
|
|
5
|
+
* merge/delivered commit is an ancestor of the refreshed remote delivery ref,
|
|
6
|
+
* or an explicit auditable non-delivery disposition is recorded.
|
|
7
|
+
*
|
|
8
|
+
* Deploy / UAT are separate axes and MUST NOT be inferred from Git alone.
|
|
9
|
+
*/
|
|
10
|
+
import { resolveDeliveryBranch } from "../policy/delivery-branch.js";
|
|
11
|
+
import { defaultGitRunner, gitIsAncestor } from "../session/git.js";
|
|
12
|
+
/** Handoff states that Git delivery can assert (deploy/UAT never inferred). */
|
|
13
|
+
export const HANDOFF_STATES = [
|
|
14
|
+
"implemented",
|
|
15
|
+
"pr_open",
|
|
16
|
+
"merged_to_integration",
|
|
17
|
+
"delivered",
|
|
18
|
+
];
|
|
19
|
+
/** Explicit non-delivery terminal dispositions (never render as shipped). */
|
|
20
|
+
export const NON_DELIVERY_DISPOSITIONS = [
|
|
21
|
+
"cancelled",
|
|
22
|
+
"superseded",
|
|
23
|
+
"experiment_archived",
|
|
24
|
+
"accepted_not_delivered",
|
|
25
|
+
];
|
|
26
|
+
function asRecord(value) {
|
|
27
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
export function isNonDeliveryDisposition(value) {
|
|
33
|
+
return (typeof value === "string" && NON_DELIVERY_DISPOSITIONS.includes(value));
|
|
34
|
+
}
|
|
35
|
+
/** True when the brief is treated as code-bearing for the delivery gate (#3041). */
|
|
36
|
+
export function isCodeBearingScope(plan) {
|
|
37
|
+
const meta = asRecord(plan.metadata);
|
|
38
|
+
const delivery = asRecord(meta?.delivery);
|
|
39
|
+
if (delivery?.required === false) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (delivery?.required === true) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
const tags = Array.isArray(plan.tags) ? plan.tags : [];
|
|
46
|
+
for (const tag of tags) {
|
|
47
|
+
if (typeof tag !== "string")
|
|
48
|
+
continue;
|
|
49
|
+
const low = tag.trim().toLowerCase();
|
|
50
|
+
if (low === "docs-only" || low === "process-only" || low === "non-code") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const kind = typeof meta?.kind === "string" ? meta.kind.trim().toLowerCase() : "";
|
|
55
|
+
if (kind === "docs" || kind === "process" || kind === "research") {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const swarm = asRecord(meta?.swarm);
|
|
59
|
+
if (Array.isArray(swarm?.file_scope) && swarm.file_scope.length > 0) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (hasGithubIssueRef(plan)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
export function hasGithubIssueRef(plan) {
|
|
68
|
+
const refs = plan.references;
|
|
69
|
+
if (!Array.isArray(refs)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
for (const ref of refs) {
|
|
73
|
+
const rec = asRecord(ref);
|
|
74
|
+
if (rec === null)
|
|
75
|
+
continue;
|
|
76
|
+
const type = typeof rec.type === "string" ? rec.type : "";
|
|
77
|
+
const uri = typeof rec.uri === "string" ? rec.uri : "";
|
|
78
|
+
if (type.includes("github-issue") || /github\.com\/[^/]+\/[^/]+\/issues\/\d+/i.test(uri)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Classify stored completion provenance for read paths.
|
|
86
|
+
* Legacy completed records without provenance → unknown/unverified (not delivered).
|
|
87
|
+
*/
|
|
88
|
+
export function classifyStoredDeliveryDisposition(plan) {
|
|
89
|
+
const meta = asRecord(plan.metadata);
|
|
90
|
+
if (meta === null) {
|
|
91
|
+
return "unknown";
|
|
92
|
+
}
|
|
93
|
+
const prov = asRecord(meta.completionProvenance) ?? asRecord(meta.deliveryProvenance);
|
|
94
|
+
if (prov === null) {
|
|
95
|
+
// completedAt alone is not delivery proof
|
|
96
|
+
if (typeof meta.completedAt === "string" && meta.completedAt.length > 0) {
|
|
97
|
+
return "unverified";
|
|
98
|
+
}
|
|
99
|
+
return "unknown";
|
|
100
|
+
}
|
|
101
|
+
const disposition = prov.disposition;
|
|
102
|
+
if (typeof disposition === "string" && disposition.length > 0) {
|
|
103
|
+
return disposition;
|
|
104
|
+
}
|
|
105
|
+
return "unverified";
|
|
106
|
+
}
|
|
107
|
+
function remoteDeliveryRef(branch) {
|
|
108
|
+
return `origin/${branch}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Refresh the remote delivery ref. Failure blocks delivered completion (#3041).
|
|
112
|
+
*/
|
|
113
|
+
export function refreshRemoteDeliveryRef(projectRoot, deliveryBranch, runGit = defaultGitRunner) {
|
|
114
|
+
const remoteRef = remoteDeliveryRef(deliveryBranch);
|
|
115
|
+
const fetch = runGit(projectRoot, ["fetch", "origin", deliveryBranch]);
|
|
116
|
+
if (fetch.code !== 0) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
error: `git fetch origin ${deliveryBranch} failed: ` +
|
|
120
|
+
`${fetch.stderr.trim() || fetch.stdout.trim() || `exit ${fetch.code}`}`,
|
|
121
|
+
remoteRef,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const verify = runGit(projectRoot, ["rev-parse", "--verify", remoteRef]);
|
|
125
|
+
if (verify.code !== 0 || !verify.stdout.trim()) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
error: `remote delivery ref ${remoteRef} is not resolvable after fetch: ` +
|
|
129
|
+
`${verify.stderr.trim() || verify.stdout.trim() || `exit ${verify.code}`}`,
|
|
130
|
+
remoteRef,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, error: null, remoteRef };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Validate that mergeCommit is an ancestor of the refreshed remote delivery ref.
|
|
137
|
+
*/
|
|
138
|
+
export function verifyDeliveryAncestry(projectRoot, mergeCommit, deliveryBranch, runGit = defaultGitRunner) {
|
|
139
|
+
const refresh = refreshRemoteDeliveryRef(projectRoot, deliveryBranch, runGit);
|
|
140
|
+
if (!refresh.ok) {
|
|
141
|
+
return { ok: false, error: refresh.error, remoteTip: null };
|
|
142
|
+
}
|
|
143
|
+
const tip = runGit(projectRoot, ["rev-parse", "--verify", refresh.remoteRef]);
|
|
144
|
+
if (tip.code !== 0 || !tip.stdout.trim()) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
error: `could not resolve tip of ${refresh.remoteRef}`,
|
|
148
|
+
remoteTip: null,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const remoteTip = tip.stdout.trim();
|
|
152
|
+
const ancestor = gitIsAncestor(projectRoot, mergeCommit, remoteTip, runGit);
|
|
153
|
+
if (ancestor === null) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error: `could not determine whether ${mergeCommit} is an ancestor of ${refresh.remoteRef} ` +
|
|
157
|
+
`(git merge-base --is-ancestor failed)`,
|
|
158
|
+
remoteTip,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (!ancestor) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
error: `merge commit ${mergeCommit} is not an ancestor of refreshed remote delivery ref ` +
|
|
165
|
+
`${refresh.remoteRef} (${remoteTip}). Intermediate/integration-only merges are not delivery evidence (#3041).`,
|
|
166
|
+
remoteTip,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, error: null, remoteTip };
|
|
170
|
+
}
|
|
171
|
+
function buildProvenance(input) {
|
|
172
|
+
const e = input.evidence ?? {};
|
|
173
|
+
return {
|
|
174
|
+
repository: e.repository ?? null,
|
|
175
|
+
implementationCommit: e.implementationCommit ?? null,
|
|
176
|
+
prNumber: e.prNumber ?? null,
|
|
177
|
+
prBase: e.prBase ?? null,
|
|
178
|
+
mergeCommit: e.mergeCommit ?? null,
|
|
179
|
+
deliveryBranch: input.deliveryBranch,
|
|
180
|
+
deliveryCommit: input.deliveryCommit ?? e.deliveryCommit ?? null,
|
|
181
|
+
verifiedAt: input.nowIso,
|
|
182
|
+
verifier: input.verifier,
|
|
183
|
+
disposition: input.disposition,
|
|
184
|
+
handoffState: input.handoffState,
|
|
185
|
+
deployed: e.deployed ?? null,
|
|
186
|
+
uatVerified: e.uatVerified ?? null,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Gate delivered completion for a code-bearing scope (#3041).
|
|
191
|
+
*
|
|
192
|
+
* Returns ok=true with provenance when:
|
|
193
|
+
* - scope is not code-bearing (provenance may be null or non-code note), OR
|
|
194
|
+
* - explicit non-delivery disposition is provided, OR
|
|
195
|
+
* - durable delivery evidence validates (PR base == deliveryBranch, merge commit
|
|
196
|
+
* ancestor of refreshed remote delivery ref).
|
|
197
|
+
*/
|
|
198
|
+
export function evaluateDeliveryGate(options) {
|
|
199
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
200
|
+
const verifier = options.verifier ?? "scope:complete";
|
|
201
|
+
const codeBearing = isCodeBearingScope(options.plan);
|
|
202
|
+
if (!codeBearing) {
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
message: "non-code-bearing scope; delivery evidence not required",
|
|
206
|
+
provenance: null,
|
|
207
|
+
codeBearing: false,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (options.nonDeliveryDisposition !== null && options.nonDeliveryDisposition !== undefined) {
|
|
211
|
+
if (!isNonDeliveryDisposition(options.nonDeliveryDisposition)) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
message: `Invalid non-delivery disposition ${JSON.stringify(options.nonDeliveryDisposition)}. ` +
|
|
215
|
+
`Allowed: ${NON_DELIVERY_DISPOSITIONS.join(", ")}`,
|
|
216
|
+
provenance: null,
|
|
217
|
+
codeBearing: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const branchResult = resolveDeliveryBranch(options.projectRoot, runGit);
|
|
221
|
+
const provenance = buildProvenance({
|
|
222
|
+
evidence: options.evidence,
|
|
223
|
+
deliveryBranch: options.evidence?.deliveryBranch ?? branchResult.branch,
|
|
224
|
+
disposition: options.nonDeliveryDisposition,
|
|
225
|
+
handoffState: "implemented",
|
|
226
|
+
nowIso: options.nowIso,
|
|
227
|
+
verifier,
|
|
228
|
+
});
|
|
229
|
+
return {
|
|
230
|
+
ok: true,
|
|
231
|
+
message: `explicit non-delivery disposition: ${options.nonDeliveryDisposition}`,
|
|
232
|
+
provenance,
|
|
233
|
+
codeBearing: true,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const evidence = options.evidence ?? null;
|
|
237
|
+
const branchResult = resolveDeliveryBranch(options.projectRoot, runGit);
|
|
238
|
+
// Policy/git-default is SoT — evidence may not redefine deliveryBranch (#3041 Greptile P1).
|
|
239
|
+
const deliveryBranch = branchResult.branch;
|
|
240
|
+
if (evidence !== null &&
|
|
241
|
+
typeof evidence.deliveryBranch === "string" &&
|
|
242
|
+
evidence.deliveryBranch.trim().length > 0 &&
|
|
243
|
+
evidence.deliveryBranch.trim() !== deliveryBranch) {
|
|
244
|
+
return {
|
|
245
|
+
ok: false,
|
|
246
|
+
message: `Evidence deliveryBranch '${evidence.deliveryBranch.trim()}' does not match ` +
|
|
247
|
+
`configured delivery branch '${deliveryBranch}' (source: ${branchResult.source}). ` +
|
|
248
|
+
`Callers cannot redefine plan.policy.deliveryBranch via evidence (#3041).`,
|
|
249
|
+
provenance: null,
|
|
250
|
+
codeBearing: true,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (evidence === null) {
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
message: `Delivery evidence required for code-bearing scope completion (#3041). ` +
|
|
257
|
+
`Provide merge/PR delivery proof (merge commit ancestor of remote ` +
|
|
258
|
+
`'${deliveryBranch}') or an explicit --non-delivery disposition ` +
|
|
259
|
+
`(${NON_DELIVERY_DISPOSITIONS.join("|")}). ` +
|
|
260
|
+
`Merged-to-integration alone is not delivery.`,
|
|
261
|
+
provenance: null,
|
|
262
|
+
codeBearing: true,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const mergeCommit = typeof evidence.mergeCommit === "string" && evidence.mergeCommit.trim().length > 0
|
|
266
|
+
? evidence.mergeCommit.trim()
|
|
267
|
+
: null;
|
|
268
|
+
const prBase = typeof evidence.prBase === "string" && evidence.prBase.trim().length > 0
|
|
269
|
+
? evidence.prBase.trim()
|
|
270
|
+
: null;
|
|
271
|
+
const mergedAt = evidence.mergedAt;
|
|
272
|
+
if (mergedAt === null || mergedAt === undefined || String(mergedAt).length === 0) {
|
|
273
|
+
// Allow evidence without mergedAt only when assumeEvidenceValidated (unit tests)
|
|
274
|
+
// or when mergeCommit is present and will be ancestry-checked.
|
|
275
|
+
if (!options.assumeEvidenceValidated && mergeCommit === null) {
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
message: "Delivery evidence missing merged_at / merge commit; cannot prove delivery (#3041).",
|
|
279
|
+
provenance: null,
|
|
280
|
+
codeBearing: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (prBase !== null && prBase !== deliveryBranch) {
|
|
285
|
+
const provenance = buildProvenance({
|
|
286
|
+
evidence,
|
|
287
|
+
deliveryBranch,
|
|
288
|
+
disposition: "merged_to_integration",
|
|
289
|
+
handoffState: "merged_to_integration",
|
|
290
|
+
nowIso: options.nowIso,
|
|
291
|
+
verifier,
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
ok: false,
|
|
295
|
+
message: `PR base '${prBase}' is not the configured delivery branch '${deliveryBranch}'. ` +
|
|
296
|
+
`A merge into an intermediate/feature/integration branch is not delivery evidence (#3041).`,
|
|
297
|
+
provenance,
|
|
298
|
+
codeBearing: true,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (mergeCommit === null) {
|
|
302
|
+
return {
|
|
303
|
+
ok: false,
|
|
304
|
+
message: "Delivery evidence missing merge_commit_sha; cannot prove ancestry on delivery branch (#3041).",
|
|
305
|
+
provenance: null,
|
|
306
|
+
codeBearing: true,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (options.assumeEvidenceValidated) {
|
|
310
|
+
const provenance = buildProvenance({
|
|
311
|
+
evidence,
|
|
312
|
+
deliveryBranch,
|
|
313
|
+
disposition: "delivered",
|
|
314
|
+
handoffState: "delivered",
|
|
315
|
+
nowIso: options.nowIso,
|
|
316
|
+
verifier,
|
|
317
|
+
deliveryCommit: evidence.deliveryCommit ?? mergeCommit,
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
ok: true,
|
|
321
|
+
message: `delivery evidence accepted (pre-validated) on '${deliveryBranch}'`,
|
|
322
|
+
provenance,
|
|
323
|
+
codeBearing: true,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const ancestry = verifyDeliveryAncestry(options.projectRoot, mergeCommit, deliveryBranch, runGit);
|
|
327
|
+
if (!ancestry.ok) {
|
|
328
|
+
const integrationOnly = prBase !== null && prBase !== deliveryBranch;
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
message: ancestry.error ?? "delivery ancestry check failed",
|
|
332
|
+
provenance: buildProvenance({
|
|
333
|
+
evidence,
|
|
334
|
+
deliveryBranch,
|
|
335
|
+
disposition: integrationOnly ? "merged_to_integration" : "not_delivered",
|
|
336
|
+
handoffState: integrationOnly ? "merged_to_integration" : "implemented",
|
|
337
|
+
nowIso: options.nowIso,
|
|
338
|
+
verifier,
|
|
339
|
+
deliveryCommit: ancestry.remoteTip,
|
|
340
|
+
}),
|
|
341
|
+
codeBearing: true,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const provenance = buildProvenance({
|
|
345
|
+
evidence,
|
|
346
|
+
deliveryBranch,
|
|
347
|
+
disposition: "delivered",
|
|
348
|
+
handoffState: "delivered",
|
|
349
|
+
nowIso: options.nowIso,
|
|
350
|
+
verifier,
|
|
351
|
+
deliveryCommit: ancestry.remoteTip,
|
|
352
|
+
});
|
|
353
|
+
return {
|
|
354
|
+
ok: true,
|
|
355
|
+
message: `merge commit ${mergeCommit} is an ancestor of origin/${deliveryBranch}`,
|
|
356
|
+
provenance,
|
|
357
|
+
codeBearing: true,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/** Stamp completion provenance onto plan.metadata (mutates plan). */
|
|
361
|
+
export function stampDeliveryProvenance(plan, provenance) {
|
|
362
|
+
let metadata = plan.metadata;
|
|
363
|
+
if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
|
|
364
|
+
metadata = {};
|
|
365
|
+
plan.metadata = metadata;
|
|
366
|
+
}
|
|
367
|
+
const meta = metadata;
|
|
368
|
+
meta.completionProvenance = {
|
|
369
|
+
repository: provenance.repository,
|
|
370
|
+
implementationCommit: provenance.implementationCommit,
|
|
371
|
+
prNumber: provenance.prNumber,
|
|
372
|
+
prBase: provenance.prBase,
|
|
373
|
+
mergeCommit: provenance.mergeCommit,
|
|
374
|
+
deliveryBranch: provenance.deliveryBranch,
|
|
375
|
+
deliveryCommit: provenance.deliveryCommit,
|
|
376
|
+
verifiedAt: provenance.verifiedAt,
|
|
377
|
+
verifier: provenance.verifier,
|
|
378
|
+
disposition: provenance.disposition,
|
|
379
|
+
handoffState: provenance.handoffState,
|
|
380
|
+
// Deploy/UAT are explicit-only; default null so readers never infer from Git.
|
|
381
|
+
deployed: provenance.deployed,
|
|
382
|
+
uatVerified: provenance.uatVerified,
|
|
383
|
+
};
|
|
384
|
+
meta.deliveryDisposition = provenance.disposition;
|
|
385
|
+
meta.handoffState = provenance.handoffState;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Parse a PR REST payload into delivery evidence fields.
|
|
389
|
+
* Expects GitHub pulls API shape (merged_at, base.ref, merge_commit_sha, …).
|
|
390
|
+
*/
|
|
391
|
+
export function evidenceFromPrPayload(payload, prNumber, repository, deliveryBranch) {
|
|
392
|
+
const base = asRecord(payload.base);
|
|
393
|
+
const head = asRecord(payload.head);
|
|
394
|
+
const prBase = typeof base?.ref === "string" ? base.ref : null;
|
|
395
|
+
const mergeCommit = typeof payload.merge_commit_sha === "string" && payload.merge_commit_sha.length > 0
|
|
396
|
+
? payload.merge_commit_sha
|
|
397
|
+
: null;
|
|
398
|
+
const headSha = typeof head?.sha === "string" && head.sha.length > 0
|
|
399
|
+
? head.sha
|
|
400
|
+
: typeof payload.head_sha === "string"
|
|
401
|
+
? payload.head_sha
|
|
402
|
+
: null;
|
|
403
|
+
const mergedAt = payload.merged_at === null
|
|
404
|
+
? null
|
|
405
|
+
: typeof payload.merged_at === "string"
|
|
406
|
+
? payload.merged_at
|
|
407
|
+
: null;
|
|
408
|
+
return {
|
|
409
|
+
repository,
|
|
410
|
+
implementationCommit: headSha,
|
|
411
|
+
prNumber,
|
|
412
|
+
prBase,
|
|
413
|
+
mergeCommit,
|
|
414
|
+
deliveryBranch: deliveryBranch ?? null,
|
|
415
|
+
mergedAt,
|
|
416
|
+
verifier: "swarm:finalize-cohort",
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
//# sourceMappingURL=delivery-evidence.js.map
|
package/dist/scope/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./audit-log.js";
|
|
|
2
2
|
export * from "./batch-promote.js";
|
|
3
3
|
export * from "./constants.js";
|
|
4
4
|
export * from "./decomposed-refs.js";
|
|
5
|
+
export * from "./delivery-evidence.js";
|
|
5
6
|
export * from "./demote.js";
|
|
6
7
|
export * from "./main.js";
|
|
7
8
|
export * from "./open-umbrella-warning.js";
|
package/dist/scope/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./audit-log.js";
|
|
|
2
2
|
export * from "./batch-promote.js";
|
|
3
3
|
export * from "./constants.js";
|
|
4
4
|
export * from "./decomposed-refs.js";
|
|
5
|
+
export * from "./delivery-evidence.js";
|
|
5
6
|
export * from "./demote.js";
|
|
6
7
|
export * from "./main.js";
|
|
7
8
|
export * from "./open-umbrella-warning.js";
|
package/dist/scope/main.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DeliveryEvidenceInput, type NonDeliveryDisposition } from "./delivery-evidence.js";
|
|
1
2
|
export interface LifecycleArgs {
|
|
2
3
|
action: string;
|
|
3
4
|
file: string;
|
|
@@ -5,6 +6,10 @@ export interface LifecycleArgs {
|
|
|
5
6
|
force?: boolean;
|
|
6
7
|
batch?: boolean;
|
|
7
8
|
batchFiles?: string[];
|
|
9
|
+
/** Explicit non-delivery disposition for code-bearing complete (#3041). */
|
|
10
|
+
nonDeliveryDisposition?: NonDeliveryDisposition;
|
|
11
|
+
/** Optional delivery evidence flags for complete (#3041). */
|
|
12
|
+
deliveryEvidence?: DeliveryEvidenceInput;
|
|
8
13
|
}
|
|
9
14
|
export interface DemoteArgs {
|
|
10
15
|
file?: string;
|
package/dist/scope/main.js
CHANGED
|
@@ -5,6 +5,7 @@ import { reconcileUmbrellas, renderUmbrellasReport } from "../vbrief-reconcile/u
|
|
|
5
5
|
import { canonicalLogPath, readAll } from "./audit-log.js";
|
|
6
6
|
import { batchPromote } from "./batch-promote.js";
|
|
7
7
|
import { TRANSITIONS } from "./constants.js";
|
|
8
|
+
import { isNonDeliveryDisposition, } from "./delivery-evidence.js";
|
|
8
9
|
import { batchDemote, DEFAULT_OLDER_THAN_DAYS, demoteOne, resolveDemoteFilePath, resolveFilePath, resolveProjectRootStrict, } from "./demote.js";
|
|
9
10
|
import { completedPathForScopeMove, findOpenUmbrellaReferences, renderOpenUmbrellaWarning, } from "./open-umbrella-warning.js";
|
|
10
11
|
import { resolveProjectRoot } from "./project-context.js";
|
|
@@ -29,6 +30,13 @@ function parseLifecycleArgv(argv) {
|
|
|
29
30
|
let force = false;
|
|
30
31
|
let batch = false;
|
|
31
32
|
const batchFiles = [];
|
|
33
|
+
let nonDeliveryDisposition;
|
|
34
|
+
let prNumber;
|
|
35
|
+
let mergeCommit;
|
|
36
|
+
let prBase;
|
|
37
|
+
let deliveryBranch;
|
|
38
|
+
let repo;
|
|
39
|
+
let mergedAt;
|
|
32
40
|
for (let i = 1; i < argv.length; i += 1) {
|
|
33
41
|
const arg = argv[i];
|
|
34
42
|
if (arg === undefined) {
|
|
@@ -47,6 +55,63 @@ function parseLifecycleArgv(argv) {
|
|
|
47
55
|
else if (arg?.startsWith("--project-root=")) {
|
|
48
56
|
projectRoot = arg.slice("--project-root=".length);
|
|
49
57
|
}
|
|
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
|
+
}
|
|
50
115
|
else if (!arg.startsWith("-")) {
|
|
51
116
|
if (batch) {
|
|
52
117
|
batchFiles.push(arg);
|
|
@@ -62,6 +127,22 @@ function parseLifecycleArgv(argv) {
|
|
|
62
127
|
return { args: null, error: "usage" };
|
|
63
128
|
}
|
|
64
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;
|
|
65
146
|
if (batch) {
|
|
66
147
|
if (action !== "promote") {
|
|
67
148
|
return { args: null, error: "usage" };
|
|
@@ -80,7 +161,16 @@ function parseLifecycleArgv(argv) {
|
|
|
80
161
|
if (file.length === 0) {
|
|
81
162
|
return { args: null, error: "usage" };
|
|
82
163
|
}
|
|
83
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
args: {
|
|
166
|
+
action,
|
|
167
|
+
file,
|
|
168
|
+
projectRoot,
|
|
169
|
+
force,
|
|
170
|
+
nonDeliveryDisposition,
|
|
171
|
+
deliveryEvidence,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
84
174
|
}
|
|
85
175
|
/** Main entry for scope_lifecycle.py parity. */
|
|
86
176
|
export function lifecycleMain(argv) {
|
|
@@ -91,7 +181,7 @@ export function lifecycleMain(argv) {
|
|
|
91
181
|
}
|
|
92
182
|
return 2;
|
|
93
183
|
}
|
|
94
|
-
const { action, file, projectRoot, force, batch, batchFiles } = parsed.args;
|
|
184
|
+
const { action, file, projectRoot, force, batch, batchFiles, nonDeliveryDisposition, deliveryEvidence, } = parsed.args;
|
|
95
185
|
if (batch === true && action === "promote") {
|
|
96
186
|
const result = batchPromote({
|
|
97
187
|
files: batchFiles && batchFiles.length > 0 ? batchFiles : undefined,
|
|
@@ -129,7 +219,12 @@ export function lifecycleMain(argv) {
|
|
|
129
219
|
}
|
|
130
220
|
}
|
|
131
221
|
}
|
|
132
|
-
const
|
|
222
|
+
const transitionOptions = {
|
|
223
|
+
nonDeliveryDisposition,
|
|
224
|
+
deliveryEvidence,
|
|
225
|
+
verifier: "scope:complete",
|
|
226
|
+
};
|
|
227
|
+
const result = runTransition(action, filePath, new Date(), transitionOptions);
|
|
133
228
|
if (result.ok) {
|
|
134
229
|
if (action === "promote" && capCheck !== null && capCheck.forceOverride) {
|
|
135
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
|