@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.
- package/dist/cache/fetch.d.ts +29 -0
- package/dist/cache/fetch.js +131 -4
- package/dist/cache/operations.d.ts +8 -0
- package/dist/cache/operations.js +67 -4
- package/dist/check/cached-orchestrator.js +11 -0
- package/dist/check/consumer-gate-integrity.d.ts +68 -0
- package/dist/check/consumer-gate-integrity.js +265 -0
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/orchestrator.js +10 -0
- package/dist/content-contracts/skills/greptile-detector.d.ts +9 -0
- package/dist/content-contracts/skills/greptile-detector.js +9 -2
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +69 -0
- package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
- package/dist/doctor/openclaw-l2-adapter.js +199 -0
- package/dist/finish-loop/pr-finish-loop.js +1 -0
- package/dist/hooks/dispatcher.d.ts +24 -2
- package/dist/hooks/dispatcher.js +122 -11
- package/dist/hooks/readonly.d.ts +14 -0
- package/dist/hooks/readonly.js +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/hygiene.js +22 -0
- 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 +3 -133
- 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/intake/issue-ingest.d.ts +20 -0
- package/dist/intake/issue-ingest.js +58 -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 +2 -0
- package/dist/policy/index.js +61 -7
- package/dist/policy/min-greptile-confidence.d.ts +57 -0
- package/dist/policy/min-greptile-confidence.js +123 -0
- package/dist/pr-merge-readiness/compute.d.ts +11 -1
- package/dist/pr-merge-readiness/compute.js +17 -4
- package/dist/pr-merge-readiness/evaluate.d.ts +8 -1
- package/dist/pr-merge-readiness/evaluate.js +7 -4
- package/dist/pr-merge-readiness/mergeability.d.ts +7 -1
- package/dist/pr-merge-readiness/mergeability.js +14 -2
- package/dist/pr-monitor/main.d.ts +2 -0
- package/dist/pr-monitor/main.js +31 -1
- package/dist/pr-monitor/monitor.js +5 -1
- package/dist/pr-monitor/readiness.js +3 -1
- package/dist/pr-monitor/types.d.ts +8 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +8 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -1
- package/dist/pr-wait-mergeable/main.d.ts +2 -0
- package/dist/pr-wait-mergeable/main.js +16 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +3 -0
- package/dist/pr-watch/constants.d.ts +4 -1
- package/dist/pr-watch/constants.js +5 -2
- package/dist/pr-watch/main.js +1 -0
- package/dist/pr-watch/probe.d.ts +1 -1
- package/dist/pr-watch/probe.js +4 -1
- package/dist/pr-watch/types.d.ts +7 -1
- package/dist/pr-watch/watch.js +3 -2
- package/dist/review-monitor/index.d.ts +1 -0
- package/dist/review-monitor/index.js +1 -0
- package/dist/review-monitor/l4-owner.d.ts +46 -0
- package/dist/review-monitor/l4-owner.js +262 -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/tool-events/classify.js +1 -0
- package/dist/triage/welcome/writers.js +16 -7
- package/dist/umbrella-current-shape/index.d.ts +45 -0
- package/dist/umbrella-current-shape/index.js +162 -8
- package/dist/vbrief-reconcile/index.d.ts +2 -2
- package/dist/vbrief-reconcile/index.js +1 -1
- package/dist/vbrief-reconcile/types.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +31 -2
- package/dist/vbrief-reconcile/umbrellas.js +231 -19
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +50 -0
- 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
|
@@ -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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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) =>
|
|
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) => !
|
|
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
|
-
|
|
274
|
-
const
|
|
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
|
|
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
|