@deftai/directive-core 0.73.0 → 0.74.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/branch/evaluate.js +2 -1
- package/dist/check/orchestrator.d.ts +3 -0
- package/dist/check/orchestrator.js +2 -1
- package/dist/codebase/map.js +2 -0
- package/dist/deposit/copy-tree.js +31 -7
- package/dist/doctor/checks.d.ts +9 -0
- package/dist/doctor/checks.js +67 -0
- package/dist/doctor/main.js +2 -1
- package/dist/fs/projection-containment.d.ts +35 -0
- package/dist/fs/projection-containment.js +118 -0
- package/dist/init-deposit/hygiene.d.ts +6 -0
- package/dist/init-deposit/hygiene.js +5 -1
- package/dist/init-deposit/refresh.d.ts +16 -2
- package/dist/init-deposit/refresh.js +144 -50
- package/dist/init-deposit/scaffold.d.ts +21 -0
- package/dist/init-deposit/scaffold.js +84 -10
- package/dist/policy/disclosure.d.ts +1 -0
- package/dist/policy/disclosure.js +1 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/policy-invocation.d.ts +5 -0
- package/dist/policy/policy-invocation.js +9 -0
- package/dist/policy/resolve.js +4 -2
- package/dist/policy/value-feedback.js +6 -3
- package/dist/pr-merge-readiness/constants.d.ts +4 -0
- package/dist/pr-merge-readiness/constants.js +4 -0
- package/dist/pr-merge-readiness/evaluate.js +3 -0
- package/dist/pr-merge-readiness/mergeability.d.ts +8 -6
- package/dist/pr-merge-readiness/mergeability.js +15 -10
- package/dist/pr-merge-readiness/output.js +12 -6
- package/dist/pr-merge-readiness/parse.d.ts +1 -0
- package/dist/pr-merge-readiness/parse.js +9 -1
- package/dist/pr-merge-readiness/types.d.ts +2 -0
- package/dist/preflight-cache/evaluate.d.ts +2 -0
- package/dist/preflight-cache/evaluate.js +14 -3
- package/dist/preflight-cache/index.d.ts +1 -1
- package/dist/preflight-cache/index.js +1 -1
- package/dist/release/constants.d.ts +2 -0
- package/dist/release/constants.js +2 -0
- package/dist/release/preflight.d.ts +2 -0
- package/dist/release/preflight.js +14 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/main.js +24 -1
- package/dist/scope/open-umbrella-warning.d.ts +12 -0
- package/dist/scope/open-umbrella-warning.js +403 -0
- package/dist/swarm/constants.d.ts +1 -1
- package/dist/swarm/constants.js +2 -1
- package/dist/swarm/subagent-backend.js +2 -1
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +69 -58
- package/dist/ts-check-lane/run-lane.d.ts +4 -0
- package/dist/ts-check-lane/run-lane.js +20 -6
- package/dist/value/feedback-file.js +3 -2
- package/dist/value/readback.js +3 -2
- package/dist/vbrief-reconcile/umbrellas.js +12 -11
- package/dist/xbrief-migrate/migrate-project.js +9 -0
- package/package.json +3 -3
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
4
|
+
import { hasArtifactSuffix } from "../layout/resolve.js";
|
|
5
|
+
import { CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE } from "../triage/queue/constants.js";
|
|
6
|
+
import { parseGithubIssueUri } from "../triage/reconcile/parse-uri.js";
|
|
7
|
+
import { collectPlanRefs, resolveVbriefRef } from "./vbrief-ref.js";
|
|
8
|
+
const OPEN_FOLDERS = ["proposed", "pending", "active"];
|
|
9
|
+
const TRACKER_LABELS = new Set(["epic", "meta", "tracker", "type:tracker", "status:tracker"]);
|
|
10
|
+
function readJson(path) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
13
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
14
|
+
? parsed
|
|
15
|
+
: null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function planOf(data) {
|
|
22
|
+
const plan = data?.plan;
|
|
23
|
+
return typeof plan === "object" && plan !== null && !Array.isArray(plan)
|
|
24
|
+
? plan
|
|
25
|
+
: null;
|
|
26
|
+
}
|
|
27
|
+
function issueRefsFromPlan(plan) {
|
|
28
|
+
const refs = plan?.references;
|
|
29
|
+
if (!Array.isArray(refs)) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const out = [];
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
for (const ref of refs) {
|
|
35
|
+
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const typed = ref;
|
|
39
|
+
if (!referenceTypeMatches(String(typed.type ?? ""), "github-issue")) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const [repo, number] = parseGithubIssueUri(typed.uri);
|
|
43
|
+
if (number === null) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const key = repo === null ? `bare:${number}` : `${repo}:${number}`;
|
|
47
|
+
if (seen.has(key)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
seen.add(key);
|
|
51
|
+
out.push({ repo, number });
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function labelsFromRaw(raw) {
|
|
56
|
+
if (!Array.isArray(raw)) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const label of raw) {
|
|
61
|
+
if (typeof label === "string") {
|
|
62
|
+
out.push(label);
|
|
63
|
+
}
|
|
64
|
+
else if (typeof label === "object" && label !== null && !Array.isArray(label)) {
|
|
65
|
+
const name = label.name;
|
|
66
|
+
if (typeof name === "string") {
|
|
67
|
+
out.push(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
function subIssueTotal(raw) {
|
|
74
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
const total = raw.total;
|
|
78
|
+
return typeof total === "number" && Number.isFinite(total) ? total : 0;
|
|
79
|
+
}
|
|
80
|
+
function readTextIfPresent(path) {
|
|
81
|
+
if (!existsSync(path)) {
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return readFileSync(path, "utf8");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function readCachedIssue(projectRoot, repo, number) {
|
|
92
|
+
const [owner, name] = repo.split("/", 2);
|
|
93
|
+
if (!owner || !name) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const issueDir = join(projectRoot, CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE, owner, name, String(number));
|
|
97
|
+
const rawPath = join(issueDir, "raw.json");
|
|
98
|
+
const raw = readJson(rawPath);
|
|
99
|
+
if (raw === null) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const body = typeof raw.body === "string" ? raw.body : "";
|
|
103
|
+
const content = readTextIfPresent(join(issueDir, "content.md"));
|
|
104
|
+
return {
|
|
105
|
+
repo,
|
|
106
|
+
number: typeof raw.number === "number" && Number.isFinite(raw.number) ? raw.number : number,
|
|
107
|
+
title: typeof raw.title === "string" ? raw.title : `#${number}`,
|
|
108
|
+
state: typeof raw.state === "string" ? raw.state.toLowerCase() : "open",
|
|
109
|
+
body: content.length > 0 ? `${body}\n${content}` : body,
|
|
110
|
+
labels: labelsFromRaw(raw.labels).map((label) => label.toLowerCase()),
|
|
111
|
+
subIssuesTotal: subIssueTotal(raw.sub_issues_summary),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function cachedState(projectRoot, ref) {
|
|
115
|
+
if (ref.repo === null) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const cached = readCachedIssue(projectRoot, ref.repo, ref.number);
|
|
119
|
+
if (cached === null) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return cached.state === "closed" ? "closed" : "open";
|
|
123
|
+
}
|
|
124
|
+
function chooseOpenIssueRef(projectRoot, refs) {
|
|
125
|
+
if (refs.length === 0) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
let firstUnknown = null;
|
|
129
|
+
for (const ref of refs) {
|
|
130
|
+
const state = cachedState(projectRoot, ref);
|
|
131
|
+
if (state === "open") {
|
|
132
|
+
return ref;
|
|
133
|
+
}
|
|
134
|
+
if (state === null && firstUnknown === null) {
|
|
135
|
+
firstUnknown = ref;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return firstUnknown;
|
|
139
|
+
}
|
|
140
|
+
function refUriMatchesPath(uri, targetPaths, vbriefRoot) {
|
|
141
|
+
if (typeof uri !== "string" || uri.length === 0) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const resolved = resolveVbriefRef(uri, vbriefRoot);
|
|
145
|
+
if (resolved === null) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return targetPaths.has(resolve(resolved));
|
|
149
|
+
}
|
|
150
|
+
function planReferenceMatchesTarget(ref, targetPaths, vbriefRoot) {
|
|
151
|
+
if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const typed = ref;
|
|
155
|
+
if (!referenceTypeMatches(String(typed.type ?? ""), "plan")) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return refUriMatchesPath(typed.uri, targetPaths, vbriefRoot);
|
|
159
|
+
}
|
|
160
|
+
function planReferencesTarget(plan, targetPaths, vbriefRoot) {
|
|
161
|
+
const refs = plan.references;
|
|
162
|
+
if (!Array.isArray(refs)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return refs.some((ref) => planReferenceMatchesTarget(ref, targetPaths, vbriefRoot));
|
|
166
|
+
}
|
|
167
|
+
function planRefsTarget(plan, targetPaths, vbriefRoot) {
|
|
168
|
+
return collectPlanRefs(plan).some((uri) => refUriMatchesPath(uri, targetPaths, vbriefRoot));
|
|
169
|
+
}
|
|
170
|
+
function relToRoot(path, root) {
|
|
171
|
+
return relative(resolve(root), resolve(path)).replace(/\\/g, "/");
|
|
172
|
+
}
|
|
173
|
+
function addReference(out, ref) {
|
|
174
|
+
const key = ref.repo !== null && ref.issueNumber !== null
|
|
175
|
+
? `${ref.repo}#${ref.issueNumber}`
|
|
176
|
+
: (ref.path ?? ref.title);
|
|
177
|
+
const existing = out.get(key);
|
|
178
|
+
if (existing !== undefined) {
|
|
179
|
+
existing.sources.add(ref.source);
|
|
180
|
+
if (existing.title.startsWith("#") && !ref.title.startsWith("#")) {
|
|
181
|
+
existing.title = ref.title;
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
out.set(key, {
|
|
186
|
+
repo: ref.repo,
|
|
187
|
+
issueNumber: ref.issueNumber,
|
|
188
|
+
title: ref.title,
|
|
189
|
+
path: ref.path,
|
|
190
|
+
sources: new Set([ref.source]),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function titleForLocalCandidate(plan, issueRef, projectRoot) {
|
|
194
|
+
if (issueRef?.repo !== null && issueRef?.repo !== undefined) {
|
|
195
|
+
const cached = readCachedIssue(projectRoot, issueRef.repo, issueRef.number);
|
|
196
|
+
if (cached !== null) {
|
|
197
|
+
return cached.title;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return typeof plan.title === "string" && plan.title.length > 0
|
|
201
|
+
? plan.title
|
|
202
|
+
: issueRef !== null
|
|
203
|
+
? `#${issueRef.number}`
|
|
204
|
+
: "open scope";
|
|
205
|
+
}
|
|
206
|
+
function findLocalReferences(projectRoot, completedScopePath, completedPlan, out) {
|
|
207
|
+
const vbriefRoot = dirname(dirname(resolve(completedScopePath)));
|
|
208
|
+
const targetScopePaths = new Set([
|
|
209
|
+
completedScopePath,
|
|
210
|
+
...OPEN_FOLDERS.map((folder) => join(vbriefRoot, folder, basename(completedScopePath))),
|
|
211
|
+
].map((path) => resolve(path)));
|
|
212
|
+
const targetParentPaths = collectPlanRefs(completedPlan)
|
|
213
|
+
.map((uri) => resolveVbriefRef(uri, vbriefRoot))
|
|
214
|
+
.filter((path) => path !== null)
|
|
215
|
+
.map((path) => resolve(path));
|
|
216
|
+
for (const folder of OPEN_FOLDERS) {
|
|
217
|
+
const folderPath = join(vbriefRoot, folder);
|
|
218
|
+
if (!existsSync(folderPath)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
for (const name of readdirSync(folderPath)
|
|
222
|
+
.filter((entry) => hasArtifactSuffix(entry))
|
|
223
|
+
.sort()) {
|
|
224
|
+
const path = join(folderPath, name);
|
|
225
|
+
const plan = planOf(readJson(path));
|
|
226
|
+
if (plan === null) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const sources = [];
|
|
230
|
+
if (planReferencesTarget(plan, targetScopePaths, vbriefRoot)) {
|
|
231
|
+
sources.push("plan.references");
|
|
232
|
+
}
|
|
233
|
+
if (planRefsTarget(plan, targetScopePaths, vbriefRoot)) {
|
|
234
|
+
sources.push("planRef");
|
|
235
|
+
}
|
|
236
|
+
if (targetParentPaths.includes(resolve(path))) {
|
|
237
|
+
sources.push("completed planRef");
|
|
238
|
+
}
|
|
239
|
+
if (sources.length === 0) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const issueRefs = issueRefsFromPlan(plan);
|
|
243
|
+
const issueRef = chooseOpenIssueRef(projectRoot, issueRefs);
|
|
244
|
+
if (issueRefs.length > 0 && issueRef === null) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const relPath = relToRoot(path, projectRoot);
|
|
248
|
+
for (const source of sources) {
|
|
249
|
+
addReference(out, {
|
|
250
|
+
repo: issueRef?.repo ?? null,
|
|
251
|
+
issueNumber: issueRef?.number ?? null,
|
|
252
|
+
title: titleForLocalCandidate(plan, issueRef, projectRoot),
|
|
253
|
+
path: relPath,
|
|
254
|
+
source,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function cachedRepoDirs(projectRoot) {
|
|
261
|
+
const base = join(projectRoot, CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE);
|
|
262
|
+
if (!existsSync(base)) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
const repos = [];
|
|
266
|
+
for (const ownerEntry of readdirSync(base, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
267
|
+
if (!ownerEntry.isDirectory()) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const owner = ownerEntry.name;
|
|
271
|
+
const ownerPath = join(base, owner);
|
|
272
|
+
for (const repoEntry of readdirSync(ownerPath, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
273
|
+
if (!repoEntry.isDirectory()) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const name = repoEntry.name;
|
|
277
|
+
repos.push(`${owner}/${name}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return repos;
|
|
281
|
+
}
|
|
282
|
+
function issueNumberToken(issueNumber) {
|
|
283
|
+
return Number.isSafeInteger(issueNumber) && issueNumber > 0 ? String(issueNumber) : null;
|
|
284
|
+
}
|
|
285
|
+
function escapeRegExp(value) {
|
|
286
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
287
|
+
}
|
|
288
|
+
function textMentionsIssue(text, ref, cachedRepo) {
|
|
289
|
+
const token = issueNumberToken(ref.number);
|
|
290
|
+
if (token === null) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const hash = new RegExp(`(^|[^A-Za-z0-9_#])#${token}(?!\\d)`);
|
|
294
|
+
if (ref.repo === cachedRepo && hash.test(text)) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
if (ref.repo === null) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const repo = escapeRegExp(ref.repo);
|
|
301
|
+
const qualifiedUrl = new RegExp(`(?:github\\.com|api\\.github\\.com/repos)/${repo}/issues/${token}(?:\\D|$)`);
|
|
302
|
+
return qualifiedUrl.test(text);
|
|
303
|
+
}
|
|
304
|
+
function looksLikeTracker(issue) {
|
|
305
|
+
if (issue.subIssuesTotal > 0) {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
if (issue.labels.some((label) => TRACKER_LABELS.has(label) || label.includes("umbrella"))) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
return /\b(epic|omnibus|tracker|umbrella)\b/i.test(issue.title);
|
|
312
|
+
}
|
|
313
|
+
function findCachedIssueBodyReferences(projectRoot, targetIssueRefs, out) {
|
|
314
|
+
const targetNumbers = new Set(targetIssueRefs.map((ref) => ref.number));
|
|
315
|
+
if (targetNumbers.size === 0) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (!targetIssueRefs.some((ref) => ref.repo !== null)) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
for (const repo of cachedRepoDirs(projectRoot)) {
|
|
322
|
+
const [owner, name] = repo.split("/", 2);
|
|
323
|
+
if (!owner || !name) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const repoDir = join(projectRoot, CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE, owner, name);
|
|
327
|
+
for (const entry of readdirSync(repoDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
328
|
+
if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const issueNumber = Number(entry.name);
|
|
332
|
+
if (targetNumbers.has(issueNumber)) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const issue = readCachedIssue(projectRoot, repo, issueNumber);
|
|
336
|
+
if (issue === null || issue.state !== "open" || !looksLikeTracker(issue)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const haystack = `${issue.title}\n${issue.body}`;
|
|
340
|
+
if (!targetIssueRefs.some((ref) => textMentionsIssue(haystack, ref, repo))) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
addReference(out, {
|
|
344
|
+
repo,
|
|
345
|
+
issueNumber: issue.number,
|
|
346
|
+
title: issue.title,
|
|
347
|
+
path: null,
|
|
348
|
+
source: "cached issue body",
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** Find open umbrella/tracker surfaces that still mention a just-completed scope. */
|
|
354
|
+
export function findOpenUmbrellaReferences(projectRoot, completedScopePath) {
|
|
355
|
+
const data = readJson(completedScopePath);
|
|
356
|
+
const completedPlan = planOf(data);
|
|
357
|
+
if (completedPlan === null) {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
const out = new Map();
|
|
361
|
+
findLocalReferences(projectRoot, completedScopePath, completedPlan, out);
|
|
362
|
+
findCachedIssueBodyReferences(projectRoot, issueRefsFromPlan(completedPlan), out);
|
|
363
|
+
return [...out.values()]
|
|
364
|
+
.map((ref) => ({
|
|
365
|
+
repo: ref.repo,
|
|
366
|
+
issueNumber: ref.issueNumber,
|
|
367
|
+
title: ref.title,
|
|
368
|
+
path: ref.path,
|
|
369
|
+
sources: [...ref.sources].sort(),
|
|
370
|
+
}))
|
|
371
|
+
.sort((a, b) => {
|
|
372
|
+
const aKey = a.issueNumber !== null
|
|
373
|
+
? a.repo === null
|
|
374
|
+
? `bare:#${a.issueNumber}`
|
|
375
|
+
: `${a.repo}#${a.issueNumber}`
|
|
376
|
+
: (a.path ?? "");
|
|
377
|
+
const bKey = b.issueNumber !== null
|
|
378
|
+
? b.repo === null
|
|
379
|
+
? `bare:#${b.issueNumber}`
|
|
380
|
+
: `${b.repo}#${b.issueNumber}`
|
|
381
|
+
: (b.path ?? "");
|
|
382
|
+
return aKey.localeCompare(bKey);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function displayReference(ref) {
|
|
386
|
+
const issue = ref.issueNumber !== null ? `#${ref.issueNumber}${ref.repo ? ` (${ref.repo})` : ""}` : null;
|
|
387
|
+
const path = ref.path !== null ? ref.path : null;
|
|
388
|
+
const name = issue ?? path ?? ref.title;
|
|
389
|
+
return `${name}: ${ref.title}`;
|
|
390
|
+
}
|
|
391
|
+
export function renderOpenUmbrellaWarning(refs) {
|
|
392
|
+
if (refs.length === 0) {
|
|
393
|
+
return "";
|
|
394
|
+
}
|
|
395
|
+
const names = refs.map(displayReference).join("; ");
|
|
396
|
+
return (`Warning: scope:complete found open umbrella/tracker reference(s) to the completed scope: ${names}. ` +
|
|
397
|
+
"Run `task vbrief:reconcile:umbrellas` to refresh current-shape comments.");
|
|
398
|
+
}
|
|
399
|
+
export function completedPathForScopeMove(filePath) {
|
|
400
|
+
const resolved = resolve(filePath);
|
|
401
|
+
return join(dirname(dirname(resolved)), "completed", basename(resolved));
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=open-umbrella-warning.js.map
|
|
@@ -7,7 +7,7 @@ export declare const EXIT_UNCLEAN = 1;
|
|
|
7
7
|
export declare const EXIT_EXTERNAL_ERROR = 2;
|
|
8
8
|
export declare const DEFAULT_BASE_BRANCH = "master";
|
|
9
9
|
export declare const LEAF_CODING_WORKER_ROLE = "leaf-implementation";
|
|
10
|
-
export declare const SUBAGENT_BACKEND_SET_CMD
|
|
10
|
+
export declare const SUBAGENT_BACKEND_SET_CMD: string;
|
|
11
11
|
export declare const GATE_ADVISE = "advise";
|
|
12
12
|
export declare const GATE_ENFORCE = "enforce";
|
|
13
13
|
export declare const READY = "ready";
|
package/dist/swarm/constants.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { policySetInvocation } from "../policy/policy-invocation.js";
|
|
1
2
|
export const EXIT_OK = 0;
|
|
2
3
|
export const EXIT_GATE_FAILED = 1;
|
|
3
4
|
export const EXIT_CONFIG_ERROR = 2;
|
|
@@ -7,7 +8,7 @@ export const EXIT_UNCLEAN = 1;
|
|
|
7
8
|
export const EXIT_EXTERNAL_ERROR = 2;
|
|
8
9
|
export const DEFAULT_BASE_BRANCH = "master";
|
|
9
10
|
export const LEAF_CODING_WORKER_ROLE = "leaf-implementation";
|
|
10
|
-
export const SUBAGENT_BACKEND_SET_CMD = "
|
|
11
|
+
export const SUBAGENT_BACKEND_SET_CMD = `${policySetInvocation("subagent-backend", " -- --set {backend_id}")}`;
|
|
11
12
|
export const GATE_ADVISE = "advise";
|
|
12
13
|
export const GATE_ENFORCE = "enforce";
|
|
13
14
|
export const READY = "ready";
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @see {@link https://github.com/deftai/directive/issues/1860} Hard deletion tracking
|
|
13
13
|
*/
|
|
14
14
|
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
15
|
+
import { policySetInvocation } from "../policy/policy-invocation.js";
|
|
15
16
|
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
16
17
|
import { LEAF_CODING_WORKER_ROLE, SUBAGENT_BACKEND_SET_CMD } from "./constants.js";
|
|
17
18
|
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
@@ -132,7 +133,7 @@ export function enforceSubagentBackendPolicy(projectRoot, environ = process.env)
|
|
|
132
133
|
error: `${detail}\n` +
|
|
133
134
|
"Select a coding sub-agent backend before headless dispatch:\n" +
|
|
134
135
|
`${listing}\n` +
|
|
135
|
-
|
|
136
|
+
`Probe harness availability: ${policySetInvocation("subagent-backends")}\n` +
|
|
136
137
|
`Persist a choice: ${SUBAGENT_BACKEND_SET_CMD.replace("{backend_id}", "<id>")}`,
|
|
137
138
|
};
|
|
138
139
|
}
|
|
@@ -8,7 +8,7 @@ export declare function gitignoreTriageCacheEntries(projectRoot: string): readon
|
|
|
8
8
|
export declare function gitattributesTriageCacheGlob(projectRoot: string): string;
|
|
9
9
|
export declare const GITATTRIBUTES_EVAL_RULE = "vbrief/.triage-cache/*.jsonl merge=union";
|
|
10
10
|
export declare const FORBIDDEN_BLANKET_EVAL_LINES: readonly string[];
|
|
11
|
-
export declare const EVAL_README_BODY = "# `vbrief/.triage-cache/`
|
|
11
|
+
export declare const EVAL_README_BODY = "# `vbrief/.triage-cache/` \u2014 triage working-set files\n\nThis directory holds JSON-lines logs and scratch files that Deft triage and\nslicing workflows emit. Deft configures your repo's `.gitignore` and\n`.gitattributes` so some files stay local while team-shared records can be\ncommitted.\n\n## What lives here\n\n| File | Committed? | Notes |\n| --- | --- | --- |\n| `slices.jsonl` | Yes | Team-shared cohort records from slicing skills. New teammates use prior cohort outputs to spot orphans and avoid re-slicing the same scope. |\n| `candidates.jsonl` | No | Your local triage accept / defer / reject stream. Re-create on a fresh clone with `deft triage:bootstrap`. |\n| `summary-history.jsonl` | No | Local history of `deft triage:summary` output; not required for day-to-day work. |\n| `scope-lifecycle.jsonl` | No | Local audit trail for scope demotions (`deft scope:demote`). Each operator's stream stays on their machine. |\n| `decompositions/` | No | Draft story-decomposition scratch. Produced child story xBRIEFs live in lifecycle folders via `deft scope:decompose`. |\n| `doctor-state.json` | No | Per-clone throttle state for `deft doctor` re-probe timing. |\n\nPaths listed as \"No\" above are added to `.gitignore` during bootstrap; anything\nnot listed remains committable by default. The selective ignore entries live in\nthe repo-root `.gitignore` (`vbrief/.triage-cache/candidates.jsonl`,\n`vbrief/.triage-cache/summary-history.jsonl`, `vbrief/.triage-cache/scope-lifecycle.jsonl`,\n`vbrief/.triage-cache/decompositions/`, and `vbrief/.triage-cache/doctor-state.json`).\n\n## Fresh clone\n\nIf `candidates.jsonl` is missing, run:\n\n```\ndeft triage:bootstrap\n```\n\nBootstrap rebuilds the local candidates log without altering committed\n`slices.jsonl`.\n\n## Merge behavior for `*.jsonl`\n\nThe repo-root `.gitattributes` may declare:\n\n```\nvbrief/.triage-cache/*.jsonl merge=union\n```\n\nThe `union` merge driver concatenates both sides' appended lines on auto-merge,\nso parallel append-only edits to the same JSON-lines file rebase without manual\nconflict surgery. It does not dedupe semantically similar records \u2014 downstream\nreaders should tolerate duplicate-looking entries.\n\n## See also\n\n- `.gitignore` \u2014 selective ignore rules for operator-private files\n- `.gitattributes` \u2014 merge driver for committed JSON-lines logs\n";
|
|
12
12
|
/** Layout-aware triage-cache README body for the active lifecycle tree (#2344 / #2349). */
|
|
13
13
|
export declare function generateTriageCacheReadmeBody(projectRoot: string): string;
|
|
14
14
|
/** Strip an inline `# ...` comment from a gitignore line. */
|