@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.
Files changed (92) hide show
  1. package/dist/cache/fetch.js +3 -2
  2. package/dist/cache/io.d.ts +13 -2
  3. package/dist/cache/io.js +36 -7
  4. package/dist/cache/operations.js +5 -3
  5. package/dist/doctor/index.d.ts +1 -0
  6. package/dist/doctor/index.js +1 -0
  7. package/dist/doctor/main.js +49 -0
  8. package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
  9. package/dist/doctor/openclaw-l2-adapter.js +199 -0
  10. package/dist/hooks/dispatcher.d.ts +6 -1
  11. package/dist/hooks/dispatcher.js +30 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +2 -0
  14. package/dist/init-deposit/gitignore.js +3 -0
  15. package/dist/init-deposit/hygiene.d.ts +43 -1
  16. package/dist/init-deposit/hygiene.js +134 -10
  17. package/dist/init-deposit/index.d.ts +3 -0
  18. package/dist/init-deposit/index.js +3 -0
  19. package/dist/init-deposit/init-deposit.js +10 -0
  20. package/dist/init-deposit/refresh.js +11 -0
  21. package/dist/init-deposit/scaffold.js +6 -134
  22. package/dist/init-deposit/skill-discovery-deposit.d.ts +65 -0
  23. package/dist/init-deposit/skill-discovery-deposit.js +287 -0
  24. package/dist/init-deposit/skill-discovery-hosts.d.ts +94 -0
  25. package/dist/init-deposit/skill-discovery-hosts.js +217 -0
  26. package/dist/init-deposit/slash-deposit.d.ts +46 -0
  27. package/dist/init-deposit/slash-deposit.js +174 -0
  28. package/dist/orchestration/probe-session.d.ts +5 -1
  29. package/dist/orchestration/probe-session.js +24 -13
  30. package/dist/platform/agents-md.js +1 -1
  31. package/dist/policy/deft-directive-disable.d.ts +86 -0
  32. package/dist/policy/deft-directive-disable.js +167 -0
  33. package/dist/policy/delivery-branch.d.ts +33 -0
  34. package/dist/policy/delivery-branch.js +124 -0
  35. package/dist/policy/host-slash-commands.d.ts +28 -0
  36. package/dist/policy/host-slash-commands.js +103 -0
  37. package/dist/policy/index.d.ts +3 -0
  38. package/dist/policy/index.js +61 -5
  39. package/dist/scope/brief-io.d.ts +3 -1
  40. package/dist/scope/brief-io.js +5 -3
  41. package/dist/scope/delivery-evidence.d.ts +112 -0
  42. package/dist/scope/delivery-evidence.js +419 -0
  43. package/dist/scope/index.d.ts +1 -0
  44. package/dist/scope/index.js +1 -0
  45. package/dist/scope/main.d.ts +5 -0
  46. package/dist/scope/main.js +98 -3
  47. package/dist/scope/registry-artifact-sync.js +4 -1
  48. package/dist/scope/transition.d.ts +11 -1
  49. package/dist/scope/transition.js +30 -4
  50. package/dist/session/ritual-sentinel.js +21 -12
  51. package/dist/session/session-start-hook.d.ts +3 -0
  52. package/dist/session/session-start-hook.js +21 -0
  53. package/dist/session/session-start.js +31 -0
  54. package/dist/slash/emitters.d.ts +102 -0
  55. package/dist/slash/emitters.js +148 -0
  56. package/dist/slash/generator.d.ts +98 -0
  57. package/dist/slash/generator.js +145 -0
  58. package/dist/slash/index.d.ts +16 -0
  59. package/dist/slash/index.js +16 -0
  60. package/dist/slash/openclaw-adapter.d.ts +64 -0
  61. package/dist/slash/openclaw-adapter.js +198 -0
  62. package/dist/slash/openclaw-deposit.d.ts +73 -0
  63. package/dist/slash/openclaw-deposit.js +279 -0
  64. package/dist/slash/openclaw-slugs.d.ts +52 -0
  65. package/dist/slash/openclaw-slugs.js +126 -0
  66. package/dist/slash/product-set.d.ts +50 -0
  67. package/dist/slash/product-set.js +142 -0
  68. package/dist/swarm/complete-cohort.d.ts +20 -1
  69. package/dist/swarm/complete-cohort.js +56 -9
  70. package/dist/swarm/finalize-cohort-cli.js +9 -0
  71. package/dist/swarm/finalize-cohort.d.ts +8 -0
  72. package/dist/swarm/finalize-cohort.js +217 -21
  73. package/dist/user-config/experimental-rules.d.ts +43 -0
  74. package/dist/user-config/experimental-rules.js +162 -0
  75. package/dist/user-config/index.d.ts +1 -0
  76. package/dist/user-config/index.js +1 -0
  77. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  78. package/dist/vbrief-validate/plan-hooks.js +50 -0
  79. package/dist/verify-source/contained-writes.js +1 -1
  80. package/dist/xbrief/create.d.ts +36 -0
  81. package/dist/xbrief/create.js +285 -0
  82. package/dist/xbrief/index.d.ts +14 -0
  83. package/dist/xbrief/index.js +42 -0
  84. package/dist/xbrief/paths.d.ts +37 -0
  85. package/dist/xbrief/paths.js +123 -0
  86. package/dist/xbrief/styles.d.ts +36 -0
  87. package/dist/xbrief/styles.js +235 -0
  88. package/dist/xbrief/types.d.ts +50 -0
  89. package/dist/xbrief/types.js +17 -0
  90. package/dist/xbrief/verify.d.ts +30 -0
  91. package/dist/xbrief/verify.js +251 -0
  92. 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
@@ -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";
@@ -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";
@@ -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;
@@ -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 { args: { action, file, projectRoot, force } };
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 result = runTransition(action, filePath);
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
- atomicWriteText(registryPath, formatBriefJson(registry));
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
- export declare function runTransition(action: string, filePath: string, now?: Date): TransitionResult;
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