@deftai/directive-core 0.92.0 → 0.94.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 (111) hide show
  1. package/dist/cache/fetch.d.ts +29 -0
  2. package/dist/cache/fetch.js +131 -4
  3. package/dist/cache/operations.d.ts +8 -0
  4. package/dist/cache/operations.js +67 -4
  5. package/dist/check/cached-orchestrator.js +11 -0
  6. package/dist/check/consumer-gate-integrity.d.ts +68 -0
  7. package/dist/check/consumer-gate-integrity.js +265 -0
  8. package/dist/check/index.d.ts +1 -0
  9. package/dist/check/index.js +1 -0
  10. package/dist/check/orchestrator.js +10 -0
  11. package/dist/content-contracts/skills/greptile-detector.d.ts +9 -0
  12. package/dist/content-contracts/skills/greptile-detector.js +9 -2
  13. package/dist/doctor/index.d.ts +1 -0
  14. package/dist/doctor/index.js +1 -0
  15. package/dist/doctor/main.js +69 -0
  16. package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
  17. package/dist/doctor/openclaw-l2-adapter.js +199 -0
  18. package/dist/finish-loop/pr-finish-loop.js +1 -0
  19. package/dist/hooks/dispatcher.d.ts +24 -2
  20. package/dist/hooks/dispatcher.js +122 -11
  21. package/dist/hooks/readonly.d.ts +14 -0
  22. package/dist/hooks/readonly.js +126 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +2 -0
  25. package/dist/init-deposit/hygiene.js +22 -0
  26. package/dist/init-deposit/index.d.ts +3 -0
  27. package/dist/init-deposit/index.js +3 -0
  28. package/dist/init-deposit/init-deposit.js +10 -0
  29. package/dist/init-deposit/refresh.js +11 -0
  30. package/dist/init-deposit/scaffold.js +3 -133
  31. package/dist/init-deposit/skill-discovery-deposit.d.ts +65 -0
  32. package/dist/init-deposit/skill-discovery-deposit.js +287 -0
  33. package/dist/init-deposit/skill-discovery-hosts.d.ts +94 -0
  34. package/dist/init-deposit/skill-discovery-hosts.js +217 -0
  35. package/dist/init-deposit/slash-deposit.d.ts +46 -0
  36. package/dist/init-deposit/slash-deposit.js +174 -0
  37. package/dist/intake/issue-ingest.d.ts +20 -0
  38. package/dist/intake/issue-ingest.js +58 -0
  39. package/dist/policy/host-slash-commands.d.ts +28 -0
  40. package/dist/policy/host-slash-commands.js +103 -0
  41. package/dist/policy/index.d.ts +2 -0
  42. package/dist/policy/index.js +61 -7
  43. package/dist/policy/min-greptile-confidence.d.ts +57 -0
  44. package/dist/policy/min-greptile-confidence.js +123 -0
  45. package/dist/pr-merge-readiness/compute.d.ts +11 -1
  46. package/dist/pr-merge-readiness/compute.js +17 -4
  47. package/dist/pr-merge-readiness/evaluate.d.ts +8 -1
  48. package/dist/pr-merge-readiness/evaluate.js +7 -4
  49. package/dist/pr-merge-readiness/mergeability.d.ts +7 -1
  50. package/dist/pr-merge-readiness/mergeability.js +14 -2
  51. package/dist/pr-monitor/main.d.ts +2 -0
  52. package/dist/pr-monitor/main.js +31 -1
  53. package/dist/pr-monitor/monitor.js +5 -1
  54. package/dist/pr-monitor/readiness.js +3 -1
  55. package/dist/pr-monitor/types.d.ts +8 -0
  56. package/dist/pr-wait-mergeable/cascade.d.ts +8 -0
  57. package/dist/pr-wait-mergeable/cascade.js +28 -1
  58. package/dist/pr-wait-mergeable/main.d.ts +2 -0
  59. package/dist/pr-wait-mergeable/main.js +16 -0
  60. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  61. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  62. package/dist/pr-wait-mergeable/wrappers.js +3 -0
  63. package/dist/pr-watch/constants.d.ts +4 -1
  64. package/dist/pr-watch/constants.js +5 -2
  65. package/dist/pr-watch/main.js +1 -0
  66. package/dist/pr-watch/probe.d.ts +1 -1
  67. package/dist/pr-watch/probe.js +4 -1
  68. package/dist/pr-watch/types.d.ts +7 -1
  69. package/dist/pr-watch/watch.js +3 -2
  70. package/dist/review-monitor/index.d.ts +1 -0
  71. package/dist/review-monitor/index.js +1 -0
  72. package/dist/review-monitor/l4-owner.d.ts +46 -0
  73. package/dist/review-monitor/l4-owner.js +262 -0
  74. package/dist/slash/emitters.d.ts +102 -0
  75. package/dist/slash/emitters.js +148 -0
  76. package/dist/slash/generator.d.ts +98 -0
  77. package/dist/slash/generator.js +145 -0
  78. package/dist/slash/index.d.ts +16 -0
  79. package/dist/slash/index.js +16 -0
  80. package/dist/slash/openclaw-adapter.d.ts +64 -0
  81. package/dist/slash/openclaw-adapter.js +198 -0
  82. package/dist/slash/openclaw-deposit.d.ts +73 -0
  83. package/dist/slash/openclaw-deposit.js +279 -0
  84. package/dist/slash/openclaw-slugs.d.ts +52 -0
  85. package/dist/slash/openclaw-slugs.js +126 -0
  86. package/dist/slash/product-set.d.ts +50 -0
  87. package/dist/slash/product-set.js +142 -0
  88. package/dist/tool-events/classify.js +1 -0
  89. package/dist/triage/welcome/writers.js +16 -7
  90. package/dist/umbrella-current-shape/index.d.ts +45 -0
  91. package/dist/umbrella-current-shape/index.js +162 -8
  92. package/dist/vbrief-reconcile/index.d.ts +2 -2
  93. package/dist/vbrief-reconcile/index.js +1 -1
  94. package/dist/vbrief-reconcile/types.d.ts +14 -0
  95. package/dist/vbrief-reconcile/umbrellas.d.ts +31 -2
  96. package/dist/vbrief-reconcile/umbrellas.js +231 -19
  97. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  98. package/dist/vbrief-validate/plan-hooks.js +50 -0
  99. package/dist/xbrief/create.d.ts +36 -0
  100. package/dist/xbrief/create.js +285 -0
  101. package/dist/xbrief/index.d.ts +14 -0
  102. package/dist/xbrief/index.js +42 -0
  103. package/dist/xbrief/paths.d.ts +37 -0
  104. package/dist/xbrief/paths.js +123 -0
  105. package/dist/xbrief/styles.d.ts +36 -0
  106. package/dist/xbrief/styles.js +235 -0
  107. package/dist/xbrief/types.d.ts +50 -0
  108. package/dist/xbrief/types.js +17 -0
  109. package/dist/xbrief/verify.d.ts +30 -0
  110. package/dist/xbrief/verify.js +251 -0
  111. package/package.json +11 -3
@@ -1,15 +1,18 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import { referenceTypeMatches } from "@deftai/directive-types";
4
- import { createIssueComment, editIssueCommentBody, GitHubBodyError, } from "../intake/github-body.js";
4
+ import { createIssueComment, editIssueBody, editIssueCommentBody, fetchIssueBody, GitHubBodyError, } from "../intake/github-body.js";
5
+ import { fetchIssueStates, IssueState } from "../intake/reconcile-issues.js";
5
6
  import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
6
7
  import { call } from "../scm/call.js";
7
- import { extractIssueRef } from "../triage/reconcile/parse-uri.js";
8
+ import { extractIssueRef, parseGithubIssueUri } from "../triage/reconcile/parse-uri.js";
8
9
  import { isRepoMutationAllowed } from "./repo-guard.js";
9
10
  export const OPEN_FOLDERS = ["proposed", "pending", "active"];
10
11
  export const CLOSED_FOLDERS = ["completed", "cancelled"];
11
12
  export const LIFECYCLE_FOLDERS = [...OPEN_FOLDERS, ...CLOSED_FOLDERS];
12
13
  export const CHILD_REF_TYPE = "x-xbrief/plan";
14
+ /** Synthetic lifecycle folder for issue-only children with no local xBRIEF (#1649). */
15
+ export const UNKNOWN_FOLDER = "unknown";
13
16
  const SCM_SOURCE = "github-issue";
14
17
  const HEADER_RE = /^## Current shape \(as of pass-(\d+)\)/m;
15
18
  // ReDoS-hardened (#1782 s4 / CodeQL js/polynomial-redos): the original
@@ -23,6 +26,12 @@ const HISTORY_RE = /^Child-count history:\s*(\S.*|)$/m;
23
26
  const LAST_UPDATED_RE = /^Last updated:\s*(\S.*|)$/m;
24
27
  const LAST_PASS_TYPE_RE = /^Last pass type:\s*(\S.*|)$/m;
25
28
  const HISTORY_TOKEN_RE = /^\s*pass-(\d+):\s*(\d+)\s*$/;
29
+ /**
30
+ * Issue-body checklist line with a `#N` child ref (#1649).
31
+ * Captures leading marker, checkbox state, and the remainder after `]`.
32
+ * Linear-time: no nested quantifiers on overlapping classes.
33
+ */
34
+ const CHECKLIST_ISSUE_LINE_RE = /^([ \t]*[-*+][ \t]+)\[([ xX])\]([ \t]*#[0-9]+\b.*)$/;
26
35
  const READING_ORDER = "1. Read the umbrella issue body.\n" +
27
36
  "2. Read this current-shape comment.\n" +
28
37
  "3. Read the amendment comments in chronological order for the full audit trail.";
@@ -52,12 +61,14 @@ export function childFromData(data, folder, fallbackId) {
52
61
  : {};
53
62
  const rawDeps = swarm.depends_on;
54
63
  const dependsOn = Array.isArray(rawDeps) ? rawDeps.map((d) => String(d)) : [];
64
+ const [, issueNumber] = extractIssueRef(data);
55
65
  return {
56
66
  story_id: String(plan.id ?? fallbackId),
57
67
  title: String(plan.title ?? plan.id ?? fallbackId),
58
68
  kind: String(metadata.kind ?? "story"),
59
69
  folder,
60
70
  depends_on: dependsOn,
71
+ issue_number: issueNumber,
61
72
  };
62
73
  }
63
74
  export function buildChildIndex(vbriefDir) {
@@ -80,7 +91,22 @@ export function buildChildIndex(vbriefDir) {
80
91
  }
81
92
  return index;
82
93
  }
83
- export function computeChildren(epicData, index) {
94
+ /** Index children by forge issue number for github-issue ref resolution (#1649). */
95
+ export function indexChildrenByIssueNumber(index) {
96
+ const byIssue = new Map();
97
+ for (const child of Object.values(index)) {
98
+ if (typeof child.issue_number === "number" && !byIssue.has(child.issue_number)) {
99
+ byIssue.set(child.issue_number, child);
100
+ }
101
+ }
102
+ return byIssue;
103
+ }
104
+ /**
105
+ * Resolve epic children from plan refs AND github-issue refs (#1649 defect 2).
106
+ * The epic's own github-issue ref is skipped when `epicIssueNumber` is provided.
107
+ * Issue-only children (no local xBRIEF) are synthesized with folder `unknown`.
108
+ */
109
+ export function computeChildren(epicData, index, options = {}) {
84
110
  const plan = typeof epicData.plan === "object" && epicData.plan !== null && !Array.isArray(epicData.plan)
85
111
  ? epicData.plan
86
112
  : {};
@@ -89,21 +115,102 @@ export function computeChildren(epicData, index) {
89
115
  const seen = new Set();
90
116
  if (!Array.isArray(refs))
91
117
  return children;
118
+ const byIssue = indexChildrenByIssueNumber(index);
119
+ const epicIssue = options.epicIssueNumber ?? null;
92
120
  for (const ref of refs) {
93
121
  if (typeof ref !== "object" || ref === null || Array.isArray(ref))
94
122
  continue;
95
123
  const rec = ref;
96
- if (!referenceTypeMatches(String(rec.type ?? ""), "plan"))
97
- continue;
98
- const name = basename(String(rec.uri ?? ""));
99
- const child = index[name];
100
- if (!child || seen.has(child.story_id))
124
+ const refType = String(rec.type ?? "");
125
+ if (referenceTypeMatches(refType, "plan")) {
126
+ const name = basename(String(rec.uri ?? ""));
127
+ const child = index[name];
128
+ if (!child || seen.has(child.story_id))
129
+ continue;
130
+ seen.add(child.story_id);
131
+ children.push(child);
101
132
  continue;
102
- seen.add(child.story_id);
103
- children.push(child);
133
+ }
134
+ if (referenceTypeMatches(refType, "github-issue")) {
135
+ const [, number] = parseGithubIssueUri(rec.uri);
136
+ if (number === null)
137
+ continue;
138
+ if (epicIssue !== null && number === epicIssue)
139
+ continue;
140
+ const fromIndex = byIssue.get(number);
141
+ if (fromIndex) {
142
+ if (seen.has(fromIndex.story_id))
143
+ continue;
144
+ seen.add(fromIndex.story_id);
145
+ children.push(fromIndex);
146
+ continue;
147
+ }
148
+ // Synthetic child: hand-filed / issue:emit epic with issue-only child links.
149
+ const syntheticId = `#${number}`;
150
+ if (seen.has(syntheticId))
151
+ continue;
152
+ seen.add(syntheticId);
153
+ const title = typeof rec.title === "string" && rec.title.trim().length > 0
154
+ ? rec.title.trim()
155
+ : syntheticId;
156
+ children.push({
157
+ story_id: syntheticId,
158
+ title,
159
+ kind: "story",
160
+ folder: UNKNOWN_FOLDER,
161
+ depends_on: [],
162
+ issue_number: number,
163
+ });
164
+ }
104
165
  }
105
166
  return children;
106
167
  }
168
+ /**
169
+ * Whether a child counts as open for current-shape + checklist (#1649 defect 3).
170
+ * Prefer forge issue state when present; fall back to lifecycle folder.
171
+ * `unknown` (issue-only, no local xBRIEF) counts open without forge confirmation.
172
+ */
173
+ export function isChildOpen(child, forgeStates) {
174
+ if (typeof child.issue_number === "number" &&
175
+ forgeStates != null &&
176
+ forgeStates.has(child.issue_number)) {
177
+ return forgeStates.get(child.issue_number) === "open";
178
+ }
179
+ if (CLOSED_FOLDERS.includes(child.folder))
180
+ return false;
181
+ // proposed/pending/active/unknown (and any unexpected folder) → open
182
+ return true;
183
+ }
184
+ /**
185
+ * Reconcile umbrella issue-body checkboxes `- [ ] #N` / `- [x] #N` to child state (#1649).
186
+ * Only lines that mention a known child issue number are rewritten; other checkboxes untouched.
187
+ * Idempotent: returns `changed: false` when already correct.
188
+ */
189
+ export function reconcileBodyChecklist(body, closedIssueNumbers, knownIssueNumbers) {
190
+ if (knownIssueNumbers.size === 0)
191
+ return { body, changed: false };
192
+ const lines = body.split("\n");
193
+ let changed = false;
194
+ const out = lines.map((line) => {
195
+ const match = CHECKLIST_ISSUE_LINE_RE.exec(line);
196
+ if (!match)
197
+ return line;
198
+ const issueMatch = /#(\d+)\b/.exec(match[3] ?? "");
199
+ if (!issueMatch?.[1])
200
+ return line;
201
+ const issueNum = Number(issueMatch[1]);
202
+ if (!knownIssueNumbers.has(issueNum))
203
+ return line;
204
+ const wantChecked = closedIssueNumbers.has(issueNum);
205
+ const isChecked = (match[2] ?? " ").toLowerCase() === "x";
206
+ if (isChecked === wantChecked)
207
+ return line;
208
+ changed = true;
209
+ const marker = wantChecked ? "x" : " ";
210
+ return `${match[1]}[${marker}]${match[3]}`;
211
+ });
212
+ return { body: out.join("\n"), changed };
213
+ }
107
214
  export function computeWaves(children) {
108
215
  const ids = new Set(children.map((c) => c.story_id));
109
216
  const deps = {};
@@ -249,18 +356,92 @@ export class ScmUmbrellaClient {
249
356
  throw new UmbrellaScmError(`create comment #${issueNumber} (${repo}) failed: ${message}`);
250
357
  }
251
358
  }
359
+ fetchIssueStates(repo, issueNumbers) {
360
+ const result = new Map();
361
+ if (issueNumbers.length === 0)
362
+ return result;
363
+ const states = fetchIssueStates(repo, new Set(issueNumbers));
364
+ if (states === null) {
365
+ throw new UmbrellaScmError(`fetch issue states (${repo}) failed`);
366
+ }
367
+ for (const [num, state] of states) {
368
+ if (!(state instanceof IssueState))
369
+ continue;
370
+ const value = state.value.toUpperCase();
371
+ if (value === "OPEN")
372
+ result.set(num, "open");
373
+ else if (value === "CLOSED")
374
+ result.set(num, "closed");
375
+ // NOT_FOUND / other → omit so folder fallback applies
376
+ }
377
+ return result;
378
+ }
379
+ fetchIssueBody(repo, issueNumber) {
380
+ try {
381
+ return fetchIssueBody(repo, issueNumber);
382
+ }
383
+ catch (exc) {
384
+ const message = exc instanceof GitHubBodyError ? exc.message : String(exc);
385
+ throw new UmbrellaScmError(`fetch issue body #${issueNumber} (${repo}) failed: ${message}`);
386
+ }
387
+ }
388
+ editIssueBody(repo, issueNumber, body) {
389
+ try {
390
+ editIssueBody(repo, issueNumber, { body });
391
+ }
392
+ catch (exc) {
393
+ const message = exc instanceof GitHubBodyError ? exc.message : String(exc);
394
+ throw new UmbrellaScmError(`edit issue body #${issueNumber} (${repo}) failed: ${message}`);
395
+ }
396
+ }
252
397
  }
253
- function planShape(epicData, index) {
254
- const children = computeChildren(epicData, index);
398
+ function planShape(epicData, index, options = {}) {
399
+ const children = computeChildren(epicData, index, {
400
+ epicIssueNumber: options.epicIssueNumber,
401
+ });
255
402
  const openChildren = children
256
- .filter((c) => OPEN_FOLDERS.includes(c.folder))
403
+ .filter((c) => isChildOpen(c, options.forgeStates))
257
404
  .sort((a, b) => a.story_id.localeCompare(b.story_id));
258
405
  const closedChildren = children
259
- .filter((c) => !OPEN_FOLDERS.includes(c.folder))
406
+ .filter((c) => !isChildOpen(c, options.forgeStates))
260
407
  .sort((a, b) => a.story_id.localeCompare(b.story_id));
261
408
  const waves = computeWaves(children);
262
409
  return [openChildren, closedChildren, waves];
263
410
  }
411
+ function resolveForgeStates(client, repo, children) {
412
+ if (typeof client.fetchIssueStates !== "function")
413
+ return null;
414
+ const numbers = children
415
+ .map((c) => c.issue_number)
416
+ .filter((n) => typeof n === "number");
417
+ if (numbers.length === 0)
418
+ return null;
419
+ return client.fetchIssueStates(repo, numbers);
420
+ }
421
+ function reconcileChecklistForEpic(client, repo, issueNumber, closedChildren, allChildren, dryRun) {
422
+ if (typeof client.fetchIssueBody !== "function" || typeof client.editIssueBody !== "function") {
423
+ return "skipped";
424
+ }
425
+ const known = new Set();
426
+ for (const c of allChildren) {
427
+ if (typeof c.issue_number === "number")
428
+ known.add(c.issue_number);
429
+ }
430
+ if (known.size === 0)
431
+ return "skipped";
432
+ const closed = new Set();
433
+ for (const c of closedChildren) {
434
+ if (typeof c.issue_number === "number")
435
+ closed.add(c.issue_number);
436
+ }
437
+ const currentBody = client.fetchIssueBody(repo, issueNumber);
438
+ const { body: nextBody, changed } = reconcileBodyChecklist(currentBody, closed, known);
439
+ if (!changed)
440
+ return "unchanged";
441
+ if (!dryRun)
442
+ client.editIssueBody(repo, issueNumber, nextBody);
443
+ return "edited";
444
+ }
264
445
  function reconcileOneEpic(epicData, index, options) {
265
446
  const mutateGate = isRepoMutationAllowed(options.repo, options.projectRoot, {
266
447
  allowCrossRepo: options.allowCrossRepo,
@@ -270,10 +451,28 @@ function reconcileOneEpic(epicData, index, options) {
270
451
  if (!mutateGate.allowed) {
271
452
  throw new Error(mutateGate.reason ?? `refusing cross-repo mutation on ${options.repo}`);
272
453
  }
273
- const [openChildren, closedChildren, waves] = planShape(epicData, index);
274
- const total = openChildren.length + closedChildren.length;
454
+ // Resolve children once without forge state to know which issue numbers to fetch.
455
+ const childrenProbe = computeChildren(epicData, index, {
456
+ epicIssueNumber: options.number,
457
+ });
458
+ const forgeStates = resolveForgeStates(options.client, options.repo, childrenProbe);
459
+ const [openChildren, closedChildren, waves] = planShape(epicData, index, {
460
+ epicIssueNumber: options.number,
461
+ forgeStates,
462
+ });
463
+ const allChildren = [...openChildren, ...closedChildren];
464
+ const total = allChildren.length;
275
465
  const comments = options.client.fetchComments(options.repo, options.number);
276
466
  const existing = comments.find((c) => hasCurrentShape(c.body));
467
+ let checklistAction = "skipped";
468
+ try {
469
+ checklistAction = reconcileChecklistForEpic(options.client, options.repo, options.number, closedChildren, allChildren, options.dryRun);
470
+ }
471
+ catch (exc) {
472
+ // Checklist is best-effort relative to comment path: surface as throw so
473
+ // callers see the failure (body drift is the #1649 user-visible bug).
474
+ throw new UmbrellaScmError(exc instanceof Error ? exc.message : `checklist reconcile failed: ${String(exc)}`);
475
+ }
277
476
  if (!existing) {
278
477
  const body = renderBody({
279
478
  passN: 1,
@@ -293,6 +492,7 @@ function reconcileOneEpic(epicData, index, options) {
293
492
  action: "created",
294
493
  pass_n: 1,
295
494
  body,
495
+ checklist_action: checklistAction,
296
496
  };
297
497
  }
298
498
  const parsed = parseCurrentShape(existing.body);
@@ -308,13 +508,16 @@ function reconcileOneEpic(epicData, index, options) {
308
508
  history: parsed.history.length > 0 ? parsed.history : [[prevPass, total]],
309
509
  });
310
510
  if (candidate === existing.body) {
511
+ // Comment unchanged; still report edited when checklist flipped (#1649).
512
+ const action = checklistAction === "edited" ? "edited" : "unchanged";
311
513
  return {
312
514
  story_id: options.storyId,
313
515
  repo: options.repo,
314
516
  issue_number: options.number,
315
- action: "unchanged",
517
+ action,
316
518
  pass_n: prevPass,
317
519
  body: candidate,
520
+ checklist_action: checklistAction,
318
521
  };
319
522
  }
320
523
  const passN = prevPass + 1;
@@ -336,6 +539,7 @@ function reconcileOneEpic(epicData, index, options) {
336
539
  action: "edited",
337
540
  pass_n: passN,
338
541
  body,
542
+ checklist_action: checklistAction,
339
543
  };
340
544
  }
341
545
  export function nowIso(date = new Date()) {
@@ -441,13 +645,19 @@ export function reconcileUmbrellas(projectRoot, options = {}) {
441
645
  }
442
646
  return [outcome.errors.length > 0 ? 1 : 0, outcome];
443
647
  }
648
+ function formatChecklistSuffix(action) {
649
+ if (action === undefined || action === "skipped")
650
+ return "";
651
+ return `; checklist=${action}`;
652
+ }
444
653
  export function renderUmbrellasReport(outcome) {
445
654
  const lines = ["vBRIEF reconcile umbrellas", ""];
446
655
  const suffix = outcome.dry_run ? " (dry-run)" : "";
447
656
  lines.push(`Changed${suffix}:`);
448
657
  if (outcome.changed.length > 0) {
449
658
  for (const c of outcome.changed) {
450
- lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: ${c.action} -> pass-${c.pass_n}`);
659
+ lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: ${c.action} -> pass-${c.pass_n}` +
660
+ formatChecklistSuffix(c.checklist_action));
451
661
  }
452
662
  }
453
663
  else {
@@ -457,7 +667,8 @@ export function renderUmbrellasReport(outcome) {
457
667
  lines.push("Unchanged:");
458
668
  if (outcome.unchanged.length > 0) {
459
669
  for (const c of outcome.unchanged) {
460
- lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: pass-${c.pass_n}`);
670
+ lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: pass-${c.pass_n}` +
671
+ formatChecklistSuffix(c.checklist_action));
461
672
  }
462
673
  }
463
674
  else {
@@ -484,6 +695,7 @@ export function umbrellasOutcomeToJson(outcome) {
484
695
  issue_number: c.issue_number,
485
696
  action: c.action,
486
697
  pass_n: c.pass_n,
698
+ checklist_action: c.checklist_action ?? "skipped",
487
699
  });
488
700
  return {
489
701
  changed: outcome.changed.map(toChange),
@@ -8,6 +8,10 @@ export declare function validateTriageRankingLabelsOnPlan(plan: unknown, filepat
8
8
  export declare function validateRuntimeAuthorityOnPlan(plan: unknown, filepath: string): string[];
9
9
  /** vbrief_validate hook: validate ``plan.policy.hostHooks`` (#2752). */
10
10
  export declare function validateHostHooksOnPlan(plan: unknown, filepath: string): string[];
11
+ /** vbrief_validate hook: validate ``plan.policy.hostSlashCommands`` (#3054). */
12
+ export declare function validateHostSlashCommandsOnPlan(plan: unknown, filepath: string): string[];
13
+ /** vbrief_validate hook: validate ``plan.policy.openClawProductCommands`` (#3064). */
14
+ export declare function validateOpenClawProductCommandsOnPlan(plan: unknown, filepath: string): string[];
11
15
  /** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
12
16
  export declare function validateStalenessTicklerOnPlan(plan: unknown, filepath: string): string[];
13
17
  /** Run all PROJECT-DEFINITION policy hooks (mirrors lazy-import block in Python). */
@@ -1,7 +1,9 @@
1
1
  import { validateHostHooks } from "../policy/host-hooks.js";
2
+ import { validateHostSlashCommands } from "../policy/host-slash-commands.js";
2
3
  import { readPlanPolicy } from "../policy/plan-extensions.js";
3
4
  import { validateRuntimeAuthority } from "../policy/runtime-authority.js";
4
5
  import { validateStalenessTickler } from "../policy/staleness-tickler.js";
6
+ import { validateOpenClawProductCommands } from "../slash/openclaw-deposit.js";
5
7
  import { validateTriageAutoClassifyOnPlan, validateTriageHoldMarkersOnPlan, } from "../triage/classify/index.js";
6
8
  import { validateRankingLabels } from "../triage/queue/ranking-labels.js";
7
9
  import { pyStrRepr, pythonTypeName } from "../triage/scope/python-repr.js";
@@ -140,6 +142,42 @@ export function validateHostHooksOnPlan(plan, filepath) {
140
142
  }
141
143
  return out;
142
144
  }
145
+ /** vbrief_validate hook: validate ``plan.policy.hostSlashCommands`` (#3054). */
146
+ export function validateHostSlashCommandsOnPlan(plan, filepath) {
147
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
148
+ return [];
149
+ }
150
+ const policy = readPlanPolicy(plan);
151
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
152
+ return [];
153
+ }
154
+ if (!("hostSlashCommands" in policy)) {
155
+ return [];
156
+ }
157
+ const out = [];
158
+ for (const err of validateHostSlashCommands(policy.hostSlashCommands)) {
159
+ out.push(`${filepath}: ${err} (#3054)`);
160
+ }
161
+ return out;
162
+ }
163
+ /** vbrief_validate hook: validate ``plan.policy.openClawProductCommands`` (#3064). */
164
+ export function validateOpenClawProductCommandsOnPlan(plan, filepath) {
165
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
166
+ return [];
167
+ }
168
+ const policy = readPlanPolicy(plan);
169
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
170
+ return [];
171
+ }
172
+ if (!("openClawProductCommands" in policy)) {
173
+ return [];
174
+ }
175
+ const out = [];
176
+ for (const err of validateOpenClawProductCommands(policy.openClawProductCommands)) {
177
+ out.push(`${filepath}: ${err} (#3064)`);
178
+ }
179
+ return out;
180
+ }
143
181
  /** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
144
182
  export function validateStalenessTicklerOnPlan(plan, filepath) {
145
183
  if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
@@ -216,6 +254,18 @@ export function runProjectDefinitionHooks(plan, filepath) {
216
254
  catch {
217
255
  /* hook must not break validation */
218
256
  }
257
+ try {
258
+ errors.push(...validateHostSlashCommandsOnPlan(plan, filepath));
259
+ }
260
+ catch {
261
+ /* hook must not break validation */
262
+ }
263
+ try {
264
+ errors.push(...validateOpenClawProductCommandsOnPlan(plan, filepath));
265
+ }
266
+ catch {
267
+ /* hook must not break validation */
268
+ }
219
269
  return errors;
220
270
  }
221
271
  //# sourceMappingURL=plan-hooks.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * xbrief:create — write a dense xBRIEF artifact at --out (#3057).
3
+ *
4
+ * Required: --format (json|md|both), --out
5
+ * Optional: --style, --title, --id, --status, --description, --force, --project-root
6
+ *
7
+ * Does NOT move lifecycle folders. Not scope:promote / activate / complete.
8
+ */
9
+ import { type XbriefCliResult, type XbriefFormat, type XbriefStyle } from "./types.js";
10
+ export declare const CREATE_USAGE: string;
11
+ export interface CreateOptions {
12
+ format: XbriefFormat;
13
+ out: string;
14
+ style: XbriefStyle;
15
+ title?: string;
16
+ id?: string;
17
+ status?: string;
18
+ description?: string;
19
+ fromJson?: string;
20
+ force?: boolean;
21
+ projectRoot: string;
22
+ sizeCapBytes?: number;
23
+ cwd?: string;
24
+ home?: string;
25
+ env?: NodeJS.ProcessEnv;
26
+ now?: Date;
27
+ }
28
+ /** Parse create CLI argv into options (or error string). */
29
+ export declare function parseCreateArgv(argv: readonly string[]): CreateOptions | {
30
+ error: string;
31
+ };
32
+ /** Core create implementation (testable). */
33
+ export declare function createXbrief(options: CreateOptions): XbriefCliResult;
34
+ /** CLI entry for dispatch wrapper. */
35
+ export declare function runXbriefCreateCli(argv: string[]): XbriefCliResult;
36
+ //# sourceMappingURL=create.d.ts.map