@deftai/directive-core 0.95.0 → 0.97.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/archive.d.ts +134 -0
- package/dist/cache/archive.js +636 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/cache/main.js +298 -1
- package/dist/check/gate-lists.js +8 -0
- package/dist/consumer-check-contract/evaluate.d.ts +124 -0
- package/dist/consumer-check-contract/evaluate.js +699 -0
- package/dist/consumer-check-contract/index.d.ts +5 -0
- package/dist/consumer-check-contract/index.js +5 -0
- package/dist/content-contracts/skills/helpers.d.ts +1 -1
- package/dist/content-contracts/skills/helpers.js +1 -0
- package/dist/delivery-attempt/disk-begin.d.ts +51 -0
- package/dist/delivery-attempt/disk-begin.js +68 -0
- package/dist/delivery-attempt/evaluate.d.ts +26 -0
- package/dist/delivery-attempt/evaluate.js +443 -0
- package/dist/delivery-attempt/fingerprint.d.ts +32 -0
- package/dist/delivery-attempt/fingerprint.js +100 -0
- package/dist/delivery-attempt/handoff.d.ts +25 -0
- package/dist/delivery-attempt/handoff.js +102 -0
- package/dist/delivery-attempt/index.d.ts +17 -0
- package/dist/delivery-attempt/index.js +17 -0
- package/dist/delivery-attempt/ledger.d.ts +169 -0
- package/dist/delivery-attempt/ledger.js +758 -0
- package/dist/delivery-attempt/material-delta.d.ts +38 -0
- package/dist/delivery-attempt/material-delta.js +126 -0
- package/dist/delivery-attempt/types.d.ts +210 -0
- package/dist/delivery-attempt/types.js +77 -0
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +12 -0
- package/dist/doctor/openclaw-soft-rebind.d.ts +26 -0
- package/dist/doctor/openclaw-soft-rebind.js +164 -0
- package/dist/hooks/dispatcher.d.ts +2 -1
- package/dist/hooks/dispatcher.js +63 -9
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/gitignore.js +7 -0
- package/dist/init-deposit/hygiene.d.ts +1 -1
- package/dist/init-deposit/hygiene.js +16 -4
- package/dist/init-deposit/init-deposit.js +5 -0
- package/dist/init-deposit/refresh.js +3 -0
- package/dist/init-deposit/scaffold.js +6 -5
- package/dist/parent-turn-shape/evaluate.d.ts +84 -0
- package/dist/parent-turn-shape/evaluate.js +353 -0
- package/dist/parent-turn-shape/index.d.ts +8 -0
- package/dist/parent-turn-shape/index.js +8 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +29 -1
- package/dist/pr-merge-readiness/ci-gate.js +191 -24
- package/dist/pr-merge-readiness/compute.js +10 -1
- package/dist/pr-merge-readiness/index.d.ts +2 -1
- package/dist/pr-merge-readiness/index.js +2 -1
- package/dist/pr-merge-readiness/output.js +14 -0
- package/dist/pr-merge-readiness/platform-status.d.ts +29 -0
- package/dist/pr-merge-readiness/platform-status.js +49 -0
- package/dist/pr-watch/constants.d.ts +10 -0
- package/dist/pr-watch/constants.js +12 -1
- package/dist/pr-watch/main.js +16 -1
- package/dist/pr-watch/probe.js +13 -1
- package/dist/pr-watch/types.d.ts +3 -2
- package/dist/pr-watch/watch.js +14 -7
- package/dist/review-monitor/constants.js +3 -2
- package/dist/review-monitor/tier-detection.d.ts +7 -3
- package/dist/review-monitor/tier-detection.js +18 -1
- package/dist/review-monitor/verify.js +18 -0
- package/dist/scope/index.d.ts +2 -0
- package/dist/scope/index.js +2 -0
- package/dist/scope/main.d.ts +10 -0
- package/dist/scope/main.js +109 -24
- package/dist/scope/promote-from-issue.d.ts +49 -0
- package/dist/scope/promote-from-issue.js +367 -0
- package/dist/scope/promote-path.d.ts +39 -0
- package/dist/scope/promote-path.js +105 -0
- package/dist/scope-provenance/digest.d.ts +67 -0
- package/dist/scope-provenance/digest.js +188 -0
- package/dist/scope-provenance/evaluate.d.ts +82 -0
- package/dist/scope-provenance/evaluate.js +528 -0
- package/dist/scope-provenance/index.d.ts +6 -0
- package/dist/scope-provenance/index.js +6 -0
- package/dist/session/compact-ritual.d.ts +96 -0
- package/dist/session/compact-ritual.js +237 -0
- package/dist/session/compact-ritual.spec.d.ts +2 -0
- package/dist/session/compact-ritual.spec.js +21 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/openclaw-soft-rebind-deposit.d.ts +46 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +165 -0
- package/dist/swarm/routing.d.ts +4 -2
- package/dist/swarm/routing.js +26 -4
- package/dist/test-boundary/evaluate.d.ts +54 -0
- package/dist/test-boundary/evaluate.js +368 -0
- package/dist/test-boundary/index.d.ts +6 -0
- package/dist/test-boundary/index.js +6 -0
- package/dist/test-boundary/policy.d.ts +52 -0
- package/dist/test-boundary/policy.js +182 -0
- package/dist/triage/actions/index.js +62 -2
- package/dist/triage/actions/types.d.ts +8 -1
- package/dist/triage/author-filter.d.ts +51 -0
- package/dist/triage/author-filter.js +152 -0
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +15 -1
- package/dist/triage/classify/index.d.ts +2 -2
- package/dist/triage/classify/index.js +2 -2
- package/dist/triage/classify/label-mirror.d.ts +68 -5
- package/dist/triage/classify/label-mirror.js +261 -31
- package/dist/triage/help/registry-data.d.ts +49 -38
- package/dist/triage/help/registry-data.js +115 -40
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/render.d.ts +2 -0
- package/dist/triage/queue/render.js +6 -0
- package/dist/vbrief-activate/activate.js +22 -6
- package/dist/xbrief/styles.js +33 -17
- package/package.json +11 -3
|
@@ -4,6 +4,8 @@ import { cacheGet } from "../../cache/operations.js";
|
|
|
4
4
|
import { ingestSingleForAccept as ingestSingleForAcceptTs } from "../../intake/issue-ingest.js";
|
|
5
5
|
import { call } from "../../scm/call.js";
|
|
6
6
|
import { ScmStubError } from "../../scm/errors.js";
|
|
7
|
+
import { findLifecycleArtifactsForIssue, findProposedArtifactsForIssue, promoteFromIssue, } from "../../scope/promote-from-issue.js";
|
|
8
|
+
import { promotePath } from "../../scope/promote-path.js";
|
|
7
9
|
import { createCandidatesLog, findByIssue, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
|
|
8
10
|
import { TriageError, UpstreamCloseError } from "./errors.js";
|
|
9
11
|
import { parseResumeOn } from "./resume-on.js";
|
|
@@ -62,7 +64,8 @@ function defaultIssueIngest() {
|
|
|
62
64
|
// Delegate to the native TS intake path (#2350). The legacy Python
|
|
63
65
|
// `scripts/issue_ingest.py` shell-out was orphaned when #1933 removed the
|
|
64
66
|
// Python surface, leaving `triage:accept` raising ModuleNotFoundError.
|
|
65
|
-
ingestSingleForAcceptTs(issueNumber, repo, { projectRoot });
|
|
67
|
+
const [, path] = ingestSingleForAcceptTs(issueNumber, repo, { projectRoot });
|
|
68
|
+
return path;
|
|
66
69
|
},
|
|
67
70
|
};
|
|
68
71
|
}
|
|
@@ -207,24 +210,81 @@ function ensureRejectedLabelApplied(deps, issueNumber, repo) {
|
|
|
207
210
|
writeErr(`triage_actions: reject #${issueNumber} (${repo}) closed successfully but the '${REJECTED_LABEL}' label is missing and auto-create/re-add failed: ${healExc instanceof Error ? healExc.message : String(healExc)}`);
|
|
208
211
|
}
|
|
209
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* After a successful accept (or idempotent re-accept), promote proposed → pending (#1136).
|
|
215
|
+
* Surfaces WIP-cap refusals with the same messaging as scope:promote.
|
|
216
|
+
*/
|
|
217
|
+
function runAutoPromote(issueNumber, repo, projectRoot, decisionId, ingestedPath, force) {
|
|
218
|
+
// Already pending for this issue → idempotent success (repeat accept --auto-promote).
|
|
219
|
+
const pending = findLifecycleArtifactsForIssue(projectRoot, issueNumber, {
|
|
220
|
+
folder: "pending",
|
|
221
|
+
repo,
|
|
222
|
+
});
|
|
223
|
+
if (pending.length >= 1) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
let path = typeof ingestedPath === "string" && ingestedPath.length > 0 ? ingestedPath : null;
|
|
227
|
+
if (path === null) {
|
|
228
|
+
const matches = findProposedArtifactsForIssue(projectRoot, issueNumber, repo);
|
|
229
|
+
if (matches.length === 1) {
|
|
230
|
+
path = matches[0] ?? null;
|
|
231
|
+
}
|
|
232
|
+
else if (matches.length > 1) {
|
|
233
|
+
throw new TriageError(`accept #${issueNumber} (${repo}): --auto-promote found multiple proposed artifacts:\n` +
|
|
234
|
+
matches.map((p) => ` - ${p}`).join("\n") +
|
|
235
|
+
`\nPromote one with: task scope:promote -- <path>`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (path === null) {
|
|
239
|
+
// Fall back to full from-issue resolution (may still fail on missing artifact).
|
|
240
|
+
const fromIssue = promoteFromIssue({
|
|
241
|
+
issueNumber,
|
|
242
|
+
repo,
|
|
243
|
+
projectRoot,
|
|
244
|
+
force: force === true,
|
|
245
|
+
});
|
|
246
|
+
if (!fromIssue.ok) {
|
|
247
|
+
throw new TriageError(`accept #${issueNumber} (${repo}): accept recorded but --auto-promote failed: ${fromIssue.message}`);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const result = promotePath(path, {
|
|
252
|
+
projectRoot,
|
|
253
|
+
force: force === true,
|
|
254
|
+
fromIssue: issueNumber,
|
|
255
|
+
cacheDecisionId: decisionId,
|
|
256
|
+
cacheStateAtPromote: "accept",
|
|
257
|
+
requireAudit: true,
|
|
258
|
+
});
|
|
259
|
+
if (!result.ok) {
|
|
260
|
+
throw new TriageError(`accept #${issueNumber} (${repo}): accept recorded but --auto-promote failed: ${result.message}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
210
263
|
/** Record an accept audit entry and delegate vBRIEF authoring to issue_ingest. */
|
|
211
264
|
export function accept(issueNumber, repo, deps, options = {}) {
|
|
212
265
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
213
266
|
const actor = resolveActor(options.actor);
|
|
214
267
|
const prior = isIdempotentRepeat(deps, issueNumber, repo, "accept", projectRoot);
|
|
215
268
|
if (prior !== null) {
|
|
269
|
+
if (options.autoPromote === true) {
|
|
270
|
+
runAutoPromote(issueNumber, repo, projectRoot, prior.decision_id, null, options.force);
|
|
271
|
+
}
|
|
216
272
|
return prior.decision_id;
|
|
217
273
|
}
|
|
218
274
|
const entry = buildEntry(deps, "accept", issueNumber, repo, actor);
|
|
219
275
|
const logPath = logPathFor(projectRoot);
|
|
220
276
|
const decisionId = deps.candidatesLog.append(entry, { path: logPath });
|
|
277
|
+
let ingestedPath = null;
|
|
221
278
|
try {
|
|
222
|
-
deps.issueIngest.ingestSingleForAccept(issueNumber, repo, { projectRoot });
|
|
279
|
+
ingestedPath = deps.issueIngest.ingestSingleForAccept(issueNumber, repo, { projectRoot });
|
|
223
280
|
}
|
|
224
281
|
catch (exc) {
|
|
225
282
|
rollbackAuditEntry(decisionId, projectRoot, logPath);
|
|
226
283
|
throw new TriageError(`accept #${issueNumber} (${repo}): issue:ingest delegation failed; audit entry rolled back. Cause: ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
227
284
|
}
|
|
285
|
+
if (options.autoPromote === true) {
|
|
286
|
+
runAutoPromote(issueNumber, repo, projectRoot, decisionId, typeof ingestedPath === "string" ? ingestedPath : null, options.force);
|
|
287
|
+
}
|
|
228
288
|
return decisionId;
|
|
229
289
|
}
|
|
230
290
|
/** Close upstream, best-effort label, record reject audit entry. */
|
|
@@ -21,9 +21,12 @@ export interface CandidatesLog {
|
|
|
21
21
|
newDecisionId(): string;
|
|
22
22
|
}
|
|
23
23
|
export interface IssueIngest {
|
|
24
|
+
/**
|
|
25
|
+
* Ingest issue into proposed/; return absolute path when known (#1136 auto-promote).
|
|
26
|
+
*/
|
|
24
27
|
ingestSingleForAccept(issueNumber: number, repo: string, options?: {
|
|
25
28
|
projectRoot?: string;
|
|
26
|
-
}):
|
|
29
|
+
}): string | null | undefined;
|
|
27
30
|
}
|
|
28
31
|
export interface ScmRunner {
|
|
29
32
|
call(source: string, verb: string, args: readonly string[], options?: {
|
|
@@ -44,6 +47,10 @@ export interface TriageActionsDeps {
|
|
|
44
47
|
export interface AcceptOptions {
|
|
45
48
|
actor?: string | null;
|
|
46
49
|
projectRoot?: string;
|
|
50
|
+
/** After accept+ingest, promote proposed → pending (#1136). */
|
|
51
|
+
autoPromote?: boolean;
|
|
52
|
+
/** WIP-cap override for the auto-promote leg. */
|
|
53
|
+
force?: boolean;
|
|
47
54
|
}
|
|
48
55
|
export interface RejectOptions {
|
|
49
56
|
actor?: string | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Injectable authenticated-login resolver (tests inject; production uses gh). */
|
|
2
|
+
export type ResolveAuthenticatedLogin = () => string | null;
|
|
3
|
+
export interface AuthorFilter {
|
|
4
|
+
/** Raw CLI value before resolution (e.g. `@me` or `alice,bob`). */
|
|
5
|
+
readonly raw: string;
|
|
6
|
+
/** Resolved allow-list logins (exact match; case-sensitive like bulk). */
|
|
7
|
+
readonly allowLogins: readonly string[];
|
|
8
|
+
/** True when any token was `@me` / `--author-mine`. */
|
|
9
|
+
readonly usedMe: boolean;
|
|
10
|
+
/** Header/digest display string. */
|
|
11
|
+
readonly display: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthorFilterResolveResult {
|
|
14
|
+
readonly filter?: AuthorFilter;
|
|
15
|
+
readonly error?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface AuthorPartitionResult<T> {
|
|
18
|
+
readonly matched: readonly T[];
|
|
19
|
+
readonly unknownCount: number;
|
|
20
|
+
readonly nonMatchingCount: number;
|
|
21
|
+
}
|
|
22
|
+
/** Split comma allow-list; trim; drop empties. */
|
|
23
|
+
export declare function parseAuthorTokens(raw: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Resolve `@me` (and bare tokens) into an AuthorFilter.
|
|
26
|
+
* Returns error when raw is empty/whitespace-only or `@me` cannot be resolved.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveAuthorFilter(raw: string, resolveMe?: ResolveAuthenticatedLogin | null): AuthorFilterResolveResult;
|
|
29
|
+
/**
|
|
30
|
+
* Default `@me` resolution via live `gh` (not ghx — multi-arg api --jq; #2275 / #954).
|
|
31
|
+
*/
|
|
32
|
+
export declare function defaultResolveAuthenticatedLogin(): string | null;
|
|
33
|
+
/** Login from a CachedIssue.author string (empty = unknown). */
|
|
34
|
+
export declare function normalizeAuthorLogin(login: string | null | undefined): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Login from a raw cache payload (author.login / user.login / string author).
|
|
37
|
+
* Empty / missing → null (unknown).
|
|
38
|
+
*/
|
|
39
|
+
export declare function authorLoginFromRawIssue(issue: Record<string, unknown> | null | undefined): string | null;
|
|
40
|
+
/** Exact allow-list match (bulk parity). Unknown/missing never matches. */
|
|
41
|
+
export declare function matchesAuthorFilter(login: string | null | undefined, filter: AuthorFilter): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Partition items by author filter. Unknown (missing login) counted separately
|
|
44
|
+
* and excluded from matched — callers disclose unknownCount in headers.
|
|
45
|
+
*/
|
|
46
|
+
export declare function partitionByAuthorFilter<T>(items: readonly T[], getLogin: (item: T) => string | null | undefined, filter: AuthorFilter): AuthorPartitionResult<T>;
|
|
47
|
+
/** Single-line header fragment for queue / classify digest. */
|
|
48
|
+
export declare function formatAuthorFilterLine(filter: AuthorFilter, options?: {
|
|
49
|
+
readonly unknownCount?: number;
|
|
50
|
+
}): string;
|
|
51
|
+
//# sourceMappingURL=author-filter.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `--author` filter for triage:queue and triage:classify (#3129 / #1318 Layer 1).
|
|
3
|
+
*
|
|
4
|
+
* Matches existing bulk/bootstrap semantics: exact login match on cached author.login
|
|
5
|
+
* (or user.login). `@me` resolves via authenticated `gh api user --jq .login`.
|
|
6
|
+
* Comma allow-lists are accepted. Missing author on cache rows is "unknown" — not a match,
|
|
7
|
+
* and callers should disclose the unknown count rather than silent-drop.
|
|
8
|
+
*/
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { extractAuthor } from "./scope-drift/cache-walker.js";
|
|
11
|
+
/** Split comma allow-list; trim; drop empties. */
|
|
12
|
+
export function parseAuthorTokens(raw) {
|
|
13
|
+
return raw
|
|
14
|
+
.split(",")
|
|
15
|
+
.map((t) => t.trim())
|
|
16
|
+
.filter((t) => t.length > 0);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve `@me` (and bare tokens) into an AuthorFilter.
|
|
20
|
+
* Returns error when raw is empty/whitespace-only or `@me` cannot be resolved.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveAuthorFilter(raw, resolveMe) {
|
|
23
|
+
const resolveAuthenticated = resolveMe ?? defaultResolveAuthenticatedLogin;
|
|
24
|
+
const tokens = parseAuthorTokens(raw);
|
|
25
|
+
if (tokens.length === 0) {
|
|
26
|
+
return { error: "argument --author: expected a non-empty login (or @me)" };
|
|
27
|
+
}
|
|
28
|
+
let usedMe = false;
|
|
29
|
+
let meLogin;
|
|
30
|
+
const allow = [];
|
|
31
|
+
const displayParts = [];
|
|
32
|
+
for (const token of tokens) {
|
|
33
|
+
if (token === "@me" || token.toLowerCase() === "@me") {
|
|
34
|
+
usedMe = true;
|
|
35
|
+
if (meLogin === undefined) {
|
|
36
|
+
meLogin = resolveAuthenticated();
|
|
37
|
+
}
|
|
38
|
+
if (meLogin === null || meLogin.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
error: "argument --author: @me could not be resolved (gh api user --jq .login failed; authenticate gh or pass an explicit login)",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
allow.push(meLogin);
|
|
44
|
+
displayParts.push(`@me (resolved -> ${meLogin})`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
allow.push(token);
|
|
48
|
+
displayParts.push(token);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Dedup while preserving order
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
const allowLogins = [];
|
|
54
|
+
for (const login of allow) {
|
|
55
|
+
if (!seen.has(login)) {
|
|
56
|
+
seen.add(login);
|
|
57
|
+
allowLogins.push(login);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
filter: {
|
|
62
|
+
raw,
|
|
63
|
+
allowLogins,
|
|
64
|
+
usedMe,
|
|
65
|
+
display: displayParts.join(", "),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Default `@me` resolution via live `gh` (not ghx — multi-arg api --jq; #2275 / #954).
|
|
71
|
+
*/
|
|
72
|
+
export function defaultResolveAuthenticatedLogin() {
|
|
73
|
+
try {
|
|
74
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], {
|
|
75
|
+
encoding: "utf8",
|
|
76
|
+
env: process.env,
|
|
77
|
+
windowsHide: true,
|
|
78
|
+
});
|
|
79
|
+
if (result.status !== 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const text = String(result.stdout ?? "").trim();
|
|
83
|
+
if (text.length === 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
// jq may emit a JSON string with quotes; strip surrounding quotes when present
|
|
87
|
+
if ((text.startsWith('"') && text.endsWith('"')) ||
|
|
88
|
+
(text.startsWith("'") && text.endsWith("'"))) {
|
|
89
|
+
return text.slice(1, -1);
|
|
90
|
+
}
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Login from a CachedIssue.author string (empty = unknown). */
|
|
98
|
+
export function normalizeAuthorLogin(login) {
|
|
99
|
+
if (login === null || login === undefined) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const trimmed = login.trim();
|
|
103
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Login from a raw cache payload (author.login / user.login / string author).
|
|
107
|
+
* Empty / missing → null (unknown).
|
|
108
|
+
*/
|
|
109
|
+
export function authorLoginFromRawIssue(issue) {
|
|
110
|
+
if (issue === null || issue === undefined) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return normalizeAuthorLogin(extractAuthor(issue));
|
|
114
|
+
}
|
|
115
|
+
/** Exact allow-list match (bulk parity). Unknown/missing never matches. */
|
|
116
|
+
export function matchesAuthorFilter(login, filter) {
|
|
117
|
+
const normalized = normalizeAuthorLogin(login);
|
|
118
|
+
if (normalized === null) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return filter.allowLogins.includes(normalized);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Partition items by author filter. Unknown (missing login) counted separately
|
|
125
|
+
* and excluded from matched — callers disclose unknownCount in headers.
|
|
126
|
+
*/
|
|
127
|
+
export function partitionByAuthorFilter(items, getLogin, filter) {
|
|
128
|
+
const matched = [];
|
|
129
|
+
let unknownCount = 0;
|
|
130
|
+
let nonMatchingCount = 0;
|
|
131
|
+
for (const item of items) {
|
|
132
|
+
const login = normalizeAuthorLogin(getLogin(item));
|
|
133
|
+
if (login === null) {
|
|
134
|
+
unknownCount += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (filter.allowLogins.includes(login)) {
|
|
138
|
+
matched.push(item);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
nonMatchingCount += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { matched, unknownCount, nonMatchingCount };
|
|
145
|
+
}
|
|
146
|
+
/** Single-line header fragment for queue / classify digest. */
|
|
147
|
+
export function formatAuthorFilterLine(filter, options = {}) {
|
|
148
|
+
const unknown = options.unknownCount ?? 0;
|
|
149
|
+
const unknownPart = unknown > 0 ? `; ${unknown} cached issue(s) missing author (unknown — excluded)` : "";
|
|
150
|
+
return `author filter: ${filter.display}${unknownPart}`;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=author-filter.js.map
|
|
@@ -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/` \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/`,
|
|
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| `staleness-tickler-state.json` | No | Per-clone upgrade-tickler throttle state. |\n| `release-availability-state.json` | No | Per-clone release-availability probe throttle state. |\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/`, `vbrief/.triage-cache/doctor-state.json`,\n`vbrief/.triage-cache/staleness-tickler-state.json`, and\n`vbrief/.triage-cache/release-availability-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. */
|
|
@@ -20,6 +20,9 @@ export const GITIGNORE_EVAL_ENTRIES = [
|
|
|
20
20
|
"xbrief/.triage-cache/scope-lifecycle.jsonl",
|
|
21
21
|
"xbrief/.triage-cache/decompositions/",
|
|
22
22
|
"xbrief/.triage-cache/doctor-state.json",
|
|
23
|
+
// Per-clone session state (#3146); selective only — hybrid policy preserved.
|
|
24
|
+
"xbrief/.triage-cache/staleness-tickler-state.json",
|
|
25
|
+
"xbrief/.triage-cache/release-availability-state.json",
|
|
23
26
|
];
|
|
24
27
|
/** Layout-aware gitignore lines for triage working-set files (#1703). */
|
|
25
28
|
export function gitignoreTriageCacheEntries(projectRoot) {
|
|
@@ -30,6 +33,9 @@ export function gitignoreTriageCacheEntries(projectRoot) {
|
|
|
30
33
|
triageCacheRelPath(projectRoot, "scope-lifecycle.jsonl"),
|
|
31
34
|
decomp.endsWith("/") ? decomp : `${decomp}/`,
|
|
32
35
|
triageCacheRelPath(projectRoot, "doctor-state.json"),
|
|
36
|
+
// Per-clone session state (#3146); selective only — hybrid policy preserved.
|
|
37
|
+
triageCacheRelPath(projectRoot, "staleness-tickler-state.json"),
|
|
38
|
+
triageCacheRelPath(projectRoot, "release-availability-state.json"),
|
|
33
39
|
];
|
|
34
40
|
}
|
|
35
41
|
export function gitattributesTriageCacheGlob(projectRoot) {
|
|
@@ -76,6 +82,10 @@ const EVAL_ENTRIES_RATIONALE = "\n# vbrief/.triage-cache/ tracking governance (#
|
|
|
76
82
|
"# throttle state gating the 24h/4h\n" +
|
|
77
83
|
"# re-probe window; #1308 / #1464). Local\n" +
|
|
78
84
|
"# to each clone; never committed.\n" +
|
|
85
|
+
"# - staleness-tickler-state.json -> gitignored (per-clone upgrade\n" +
|
|
86
|
+
"# tickler throttle; #2488 / #3146).\n" +
|
|
87
|
+
"# - release-availability-state.json -> gitignored (per-clone npm\n" +
|
|
88
|
+
"# release-availability throttle; #1692 / #3146).\n" +
|
|
79
89
|
"# - slices.jsonl -> TRACKED (team-shared cohort records\n" +
|
|
80
90
|
"# produced by slicing skills; see\n" +
|
|
81
91
|
"# #1132 / D13).\n" +
|
|
@@ -104,12 +114,16 @@ committed.
|
|
|
104
114
|
| \`scope-lifecycle.jsonl\` | No | Local audit trail for scope demotions (\`deft scope:demote\`). Each operator's stream stays on their machine. |
|
|
105
115
|
| \`decompositions/\` | No | Draft story-decomposition scratch. Produced child story xBRIEFs live in lifecycle folders via \`deft scope:decompose\`. |
|
|
106
116
|
| \`doctor-state.json\` | No | Per-clone throttle state for \`deft doctor\` re-probe timing. |
|
|
117
|
+
| \`staleness-tickler-state.json\` | No | Per-clone upgrade-tickler throttle state. |
|
|
118
|
+
| \`release-availability-state.json\` | No | Per-clone release-availability probe throttle state. |
|
|
107
119
|
|
|
108
120
|
Paths listed as "No" above are added to \`.gitignore\` during bootstrap; anything
|
|
109
121
|
not listed remains committable by default. The selective ignore entries live in
|
|
110
122
|
the repo-root \`.gitignore\` (\`vbrief/.triage-cache/candidates.jsonl\`,
|
|
111
123
|
\`vbrief/.triage-cache/summary-history.jsonl\`, \`vbrief/.triage-cache/scope-lifecycle.jsonl\`,
|
|
112
|
-
\`vbrief/.triage-cache/decompositions/\`,
|
|
124
|
+
\`vbrief/.triage-cache/decompositions/\`, \`vbrief/.triage-cache/doctor-state.json\`,
|
|
125
|
+
\`vbrief/.triage-cache/staleness-tickler-state.json\`, and
|
|
126
|
+
\`vbrief/.triage-cache/release-availability-state.json\`).
|
|
113
127
|
|
|
114
128
|
## Fresh clone
|
|
115
129
|
|
|
@@ -85,8 +85,8 @@ export declare function validateProject(projectRoot: string): {
|
|
|
85
85
|
};
|
|
86
86
|
/** Render --list output for a project root. */
|
|
87
87
|
export declare function listProject(projectRoot: string): string;
|
|
88
|
-
import { type ClassifyAction, DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, type LabelMirrorEngine, type LabelMirrorItem, type LabelMirrorOptions as LabelMirrorOptionsCore, type LabelMirrorOutcome, type LabelMirrorPolicy, type LabelMirrorStatus, labelMirrorOutcomeToJson, type ResolvedLabelMirrorPolicy, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlan as validateTriageLabelMirrorOnPlanFromModule } from "./label-mirror.js";
|
|
89
|
-
export { type ClassifyAction, DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, type LabelMirrorEngine, type LabelMirrorItem, type LabelMirrorOutcome, type LabelMirrorPolicy, type LabelMirrorStatus, labelMirrorOutcomeToJson, type ResolvedLabelMirrorPolicy, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlanFromModule as validateTriageLabelMirrorOnPlan, };
|
|
88
|
+
import { buildLabelMirrorDigest, type ClassifyAction, DEFAULT_APPLY_BATCH_SIZE, DEFAULT_APPLY_DELAY_MS, DEFAULT_DIGEST_SAMPLE_LIMIT, DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, type LabelMirrorDigest, type LabelMirrorEngine, type LabelMirrorFilters, type LabelMirrorItem, type LabelMirrorOptions as LabelMirrorOptionsCore, type LabelMirrorOutcome, type LabelMirrorPolicy, type LabelMirrorSleepFn, type LabelMirrorStatus, labelMirrorOutcomeToJson, type ResolvedLabelMirrorPolicy, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlan as validateTriageLabelMirrorOnPlanFromModule } from "./label-mirror.js";
|
|
89
|
+
export { buildLabelMirrorDigest, type ClassifyAction, DEFAULT_APPLY_BATCH_SIZE, DEFAULT_APPLY_DELAY_MS, DEFAULT_DIGEST_SAMPLE_LIMIT, DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, type LabelMirrorDigest, type LabelMirrorEngine, type LabelMirrorFilters, type LabelMirrorItem, type LabelMirrorOutcome, type LabelMirrorPolicy, type LabelMirrorSleepFn, type LabelMirrorStatus, labelMirrorOutcomeToJson, type ResolvedLabelMirrorPolicy, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlanFromModule as validateTriageLabelMirrorOnPlan, };
|
|
90
90
|
/** Public options: engine is optional (defaults to this module's classify API). */
|
|
91
91
|
export type LabelMirrorOptions = Omit<LabelMirrorOptionsCore, "engine"> & {
|
|
92
92
|
readonly engine?: LabelMirrorEngine;
|
|
@@ -751,8 +751,8 @@ export function listProject(projectRoot) {
|
|
|
751
751
|
// label-mirror.ts does not import this module (avoids ESM/SLizard cycle).
|
|
752
752
|
// Public mirrorLabels injects the classify engine into the pure implementation.
|
|
753
753
|
// ---------------------------------------------------------------------------
|
|
754
|
-
import { DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, labelMirrorOutcomeToJson, mirrorLabels as mirrorLabelsCore, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlan as validateTriageLabelMirrorOnPlanFromModule, } from "./label-mirror.js";
|
|
755
|
-
export { DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, labelMirrorOutcomeToJson, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlanFromModule as validateTriageLabelMirrorOnPlan, };
|
|
754
|
+
import { buildLabelMirrorDigest, DEFAULT_APPLY_BATCH_SIZE, DEFAULT_APPLY_DELAY_MS, DEFAULT_DIGEST_SAMPLE_LIMIT, DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, labelMirrorOutcomeToJson, mirrorLabels as mirrorLabelsCore, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlan as validateTriageLabelMirrorOnPlanFromModule, } from "./label-mirror.js";
|
|
755
|
+
export { buildLabelMirrorDigest, DEFAULT_APPLY_BATCH_SIZE, DEFAULT_APPLY_DELAY_MS, DEFAULT_DIGEST_SAMPLE_LIMIT, DEFAULT_IDEMPOTENCY_LABEL, defaultLabelMirrorPolicy, desiredLabelsForClassification, labelMirrorOutcomeToJson, renderLabelMirrorReport, resolveLabelMirrorPolicy, validateLabelMirrorPolicy, validateTriageLabelMirrorOnPlanFromModule as validateTriageLabelMirrorOnPlan, };
|
|
756
756
|
function defaultLabelMirrorEngine() {
|
|
757
757
|
return {
|
|
758
758
|
classifyIssue: (issue, options) => classifyIssue(issue, {
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tier-1 deterministic SCM label mirror (#1423 Wave 1).
|
|
2
|
+
* Tier-1 deterministic SCM label mirror (#1423 Wave 1 + Wave 2 bootstrap).
|
|
3
3
|
*
|
|
4
4
|
* Classifies cached issues with the existing #1129 engine, then mirrors the
|
|
5
5
|
* outcome as SCM labels (dry-run default, --apply to write). Never accepts into
|
|
6
6
|
* the xBRIEF lifecycle and never writes proposed/ scopes.
|
|
7
7
|
*
|
|
8
|
+
* Wave 2 (#3125): open-only default, operator digest (totals + by state/rule/action
|
|
9
|
+
* + samples), batched rate-limit-aware apply. Bootstrap mass-triage entrypoint is
|
|
10
|
+
* `triage:classify -- --mirror` with these filters (not triage:accept).
|
|
11
|
+
*
|
|
8
12
|
* Intentionally does NOT import from ./index.js (SLizard P1 cycle). The classify
|
|
9
13
|
* engine is injected via LabelMirrorEngine / mirrorLabels() wrapper in index.ts.
|
|
10
14
|
*/
|
|
11
15
|
import type { LabelClient } from "../../vbrief-reconcile/types.js";
|
|
16
|
+
import { type AuthorFilter } from "../author-filter.js";
|
|
12
17
|
export declare const DEFAULT_IDEMPOTENCY_LABEL = "triaged";
|
|
13
18
|
export declare const CACHE_DIR_NAME = ".deft-cache";
|
|
14
19
|
export declare const CACHE_SOURCE = "github-issue";
|
|
20
|
+
/** Default apply batch size for rate-limit awareness (#3125). */
|
|
21
|
+
export declare const DEFAULT_APPLY_BATCH_SIZE = 10;
|
|
22
|
+
/** Default delay between apply batches in ms (#3125). */
|
|
23
|
+
export declare const DEFAULT_APPLY_DELAY_MS = 1000;
|
|
24
|
+
/** Default sample count in human digest (#3125). */
|
|
25
|
+
export declare const DEFAULT_DIGEST_SAMPLE_LIMIT = 15;
|
|
15
26
|
export type ClassifyAction = "defer" | "archive" | "escalate" | "accept";
|
|
16
27
|
/** Minimal issue shape used by the mirror (matches classify GitHubIssue). */
|
|
17
28
|
export interface MirrorGitHubIssue {
|
|
@@ -73,10 +84,12 @@ export interface ResolvedLabelMirrorPolicy {
|
|
|
73
84
|
readonly alwaysLabels: readonly string[];
|
|
74
85
|
readonly actionLabels: Readonly<Partial<Record<ClassifyAction, readonly string[]>>>;
|
|
75
86
|
}
|
|
76
|
-
export type LabelMirrorStatus = "planned" | "applied" | "unchanged" | "skipped_already_triaged" | "skipped_no_match" | "skipped_unreadable" | "skipped_disabled" | "error";
|
|
87
|
+
export type LabelMirrorStatus = "planned" | "applied" | "unchanged" | "skipped_already_triaged" | "skipped_no_match" | "skipped_unreadable" | "skipped_closed" | "skipped_author" | "skipped_disabled" | "error";
|
|
77
88
|
export interface LabelMirrorItem {
|
|
78
89
|
readonly repo: string;
|
|
79
90
|
readonly issue_number: number;
|
|
91
|
+
/** Issue state from cache (open/closed/unknown). */
|
|
92
|
+
readonly state: string | null;
|
|
80
93
|
readonly action: string | null;
|
|
81
94
|
readonly reason: string | null;
|
|
82
95
|
readonly ruleKind: string | null;
|
|
@@ -86,6 +99,24 @@ export interface LabelMirrorItem {
|
|
|
86
99
|
readonly status: LabelMirrorStatus;
|
|
87
100
|
readonly message?: string;
|
|
88
101
|
}
|
|
102
|
+
/** Operator digest aggregates for bootstrap mass-triage (#3125 / #1423 Wave 2). */
|
|
103
|
+
export interface LabelMirrorDigest {
|
|
104
|
+
readonly by_state: Readonly<Record<string, number>>;
|
|
105
|
+
readonly by_rule: Readonly<Record<string, number>>;
|
|
106
|
+
readonly by_action: Readonly<Record<string, number>>;
|
|
107
|
+
readonly samples: readonly LabelMirrorItem[];
|
|
108
|
+
readonly sample_limit: number;
|
|
109
|
+
readonly sample_truncated: boolean;
|
|
110
|
+
}
|
|
111
|
+
export interface LabelMirrorFilters {
|
|
112
|
+
/** When false (default), closed issues are skipped before classify. */
|
|
113
|
+
readonly include_closed: boolean;
|
|
114
|
+
readonly repo: string | null;
|
|
115
|
+
/** Active author allow-list display (#3129); null when no author filter. */
|
|
116
|
+
readonly author: string | null;
|
|
117
|
+
/** Resolved author logins for machine consumers. */
|
|
118
|
+
readonly author_logins: readonly string[] | null;
|
|
119
|
+
}
|
|
89
120
|
export interface LabelMirrorOutcome {
|
|
90
121
|
readonly project_root: string;
|
|
91
122
|
readonly dry_run: boolean;
|
|
@@ -96,10 +127,20 @@ export interface LabelMirrorOutcome {
|
|
|
96
127
|
readonly skipped_already_triaged: number;
|
|
97
128
|
readonly skipped_no_match: number;
|
|
98
129
|
readonly skipped_unreadable: number;
|
|
130
|
+
/** Closed issues skipped by open-only default (#3125). */
|
|
131
|
+
readonly skipped_closed: number;
|
|
132
|
+
/** Issues skipped by --author filter (#3129). */
|
|
133
|
+
readonly skipped_author: number;
|
|
99
134
|
readonly errors: number;
|
|
135
|
+
readonly filters: LabelMirrorFilters;
|
|
136
|
+
readonly digest: LabelMirrorDigest;
|
|
100
137
|
readonly items: readonly LabelMirrorItem[];
|
|
101
138
|
readonly policy: ResolvedLabelMirrorPolicy;
|
|
139
|
+
/** Apply path: successful writes in this run (same as applied). */
|
|
140
|
+
readonly batch_size?: number;
|
|
141
|
+
readonly delay_ms?: number;
|
|
102
142
|
}
|
|
143
|
+
export type LabelMirrorSleepFn = (ms: number) => void;
|
|
103
144
|
export interface LabelMirrorOptions {
|
|
104
145
|
readonly dryRun?: boolean;
|
|
105
146
|
readonly repo?: string | null;
|
|
@@ -110,6 +151,24 @@ export interface LabelMirrorOptions {
|
|
|
110
151
|
/** Prefer live SCM labels when true (default: !dryRun). Cache labels used for dry-run. */
|
|
111
152
|
readonly useLiveLabels?: boolean;
|
|
112
153
|
readonly now?: Date;
|
|
154
|
+
/**
|
|
155
|
+
* Include closed issues in classify+mirror. Default false (open-only) for safe
|
|
156
|
+
* bootstrap mass-triage (#3125). Opt in with CLI `--include-closed`.
|
|
157
|
+
*/
|
|
158
|
+
readonly includeClosed?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Resolved author filter applied before plan/apply walk (#3129).
|
|
161
|
+
* Composes with open-only (AND). CLI resolves `@me` before passing this.
|
|
162
|
+
*/
|
|
163
|
+
readonly authorFilter?: AuthorFilter | null;
|
|
164
|
+
/** Max planned/applied samples in human digest (default 15). */
|
|
165
|
+
readonly sampleLimit?: number;
|
|
166
|
+
/** SCM writes per batch before delay (default 10; apply path only). */
|
|
167
|
+
readonly batchSize?: number;
|
|
168
|
+
/** Delay in ms between apply batches (default 1000; apply path only). */
|
|
169
|
+
readonly delayMs?: number;
|
|
170
|
+
/** Injectable sleep for tests (receives ms). Default busy-wait when delayMs > 0. */
|
|
171
|
+
readonly sleepMs?: LabelMirrorSleepFn;
|
|
113
172
|
/** Required: classify engine (provided by classify/index mirrorLabels wrapper). */
|
|
114
173
|
readonly engine: LabelMirrorEngine;
|
|
115
174
|
}
|
|
@@ -128,15 +187,19 @@ export declare function resolveLabelMirrorPolicy(options?: {
|
|
|
128
187
|
}): ResolvedLabelMirrorPolicy;
|
|
129
188
|
/** Labels to apply for a classified action (always + action-mapped). */
|
|
130
189
|
export declare function desiredLabelsForClassification(action: string, policy: ResolvedLabelMirrorPolicy): string[];
|
|
190
|
+
/** Build digest aggregates + samples from mirror items (#3125). */
|
|
191
|
+
export declare function buildLabelMirrorDigest(items: readonly LabelMirrorItem[], sampleLimit?: number): LabelMirrorDigest;
|
|
131
192
|
/**
|
|
132
|
-
* Run Tier-1 label mirror over the github-issue cache.
|
|
193
|
+
* Run Tier-1 label mirror over the github-issue cache (bootstrap mass-triage surface).
|
|
133
194
|
* Dry-run by default (no SCM writes). Pass dryRun: false to apply.
|
|
195
|
+
* Default state filter is open-only (#3125); pass includeClosed: true for archive stamps.
|
|
134
196
|
* Requires options.engine (classify/index wrapper injects it).
|
|
197
|
+
* Never calls triage:accept / never writes proposed/ xBRIEFs.
|
|
135
198
|
*/
|
|
136
199
|
export declare function mirrorLabels(projectRoot: string, options: LabelMirrorOptions): [number, LabelMirrorOutcome];
|
|
137
|
-
/** Human-readable digest for dry-run / apply reports. */
|
|
200
|
+
/** Human-readable digest for dry-run / apply reports (bootstrap mass-triage UX). */
|
|
138
201
|
export declare function renderLabelMirrorReport(outcome: LabelMirrorOutcome): string;
|
|
139
|
-
/** JSON-serializable outcome
|
|
202
|
+
/** JSON-serializable outcome including Wave 2 digest aggregates. */
|
|
140
203
|
export declare function labelMirrorOutcomeToJson(outcome: LabelMirrorOutcome): Record<string, unknown>;
|
|
141
204
|
/** Validate triageLabelMirror on a plan object (vbrief_validate hook). */
|
|
142
205
|
export declare function validateTriageLabelMirrorOnPlan(plan: unknown, filepath: string): string[];
|