@deftai/directive-core 0.94.0 → 0.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/authz/classify.js +485 -0
  2. package/dist/cache/archive.d.ts +134 -0
  3. package/dist/cache/archive.js +630 -0
  4. package/dist/cache/index.d.ts +1 -0
  5. package/dist/cache/index.js +1 -0
  6. package/dist/cache/main.js +298 -1
  7. package/dist/content-contracts/skills/helpers.d.ts +1 -1
  8. package/dist/content-contracts/skills/helpers.js +1 -0
  9. package/dist/doctor/main.js +41 -9
  10. package/dist/doctor/types.d.ts +2 -2
  11. package/dist/freshness/bind.d.ts +67 -0
  12. package/dist/freshness/bind.js +201 -0
  13. package/dist/freshness/cli.d.ts +30 -0
  14. package/dist/freshness/cli.js +180 -0
  15. package/dist/freshness/compare.d.ts +14 -0
  16. package/dist/freshness/compare.js +134 -0
  17. package/dist/freshness/generation.d.ts +44 -0
  18. package/dist/freshness/generation.js +172 -0
  19. package/dist/freshness/index.d.ts +13 -0
  20. package/dist/freshness/index.js +13 -0
  21. package/dist/freshness/report.d.ts +43 -0
  22. package/dist/freshness/report.js +139 -0
  23. package/dist/freshness/types.d.ts +70 -0
  24. package/dist/freshness/types.js +30 -0
  25. package/dist/handoff-evidence/index.d.ts +8 -0
  26. package/dist/handoff-evidence/index.js +7 -0
  27. package/dist/handoff-evidence/validate.d.ts +105 -0
  28. package/dist/handoff-evidence/validate.js +423 -0
  29. package/dist/hooks/dispatcher.d.ts +3 -0
  30. package/dist/hooks/dispatcher.js +12 -3
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.js +3 -0
  33. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  34. package/dist/init-deposit/agent-hooks.js +5 -2
  35. package/dist/init-deposit/hygiene.d.ts +1 -1
  36. package/dist/init-deposit/hygiene.js +16 -4
  37. package/dist/init-deposit/init-deposit.d.ts +7 -1
  38. package/dist/init-deposit/init-deposit.js +24 -4
  39. package/dist/init-deposit/refresh.d.ts +9 -1
  40. package/dist/init-deposit/refresh.js +48 -5
  41. package/dist/init-deposit/scaffold.js +6 -5
  42. package/dist/parent-turn-shape/evaluate.d.ts +84 -0
  43. package/dist/parent-turn-shape/evaluate.js +353 -0
  44. package/dist/parent-turn-shape/index.d.ts +8 -0
  45. package/dist/parent-turn-shape/index.js +8 -0
  46. package/dist/policy/index.d.ts +3 -0
  47. package/dist/policy/index.js +35 -0
  48. package/dist/release-e2e/greenfield-python-free-smoke.js +7 -3
  49. package/dist/review-monitor/constants.js +3 -2
  50. package/dist/review-monitor/tier-detection.d.ts +7 -3
  51. package/dist/review-monitor/tier-detection.js +18 -1
  52. package/dist/review-monitor/verify.js +18 -0
  53. package/dist/scope/index.d.ts +2 -0
  54. package/dist/scope/index.js +2 -0
  55. package/dist/scope/main.d.ts +10 -0
  56. package/dist/scope/main.js +109 -24
  57. package/dist/scope/promote-from-issue.d.ts +49 -0
  58. package/dist/scope/promote-from-issue.js +367 -0
  59. package/dist/scope/promote-path.d.ts +39 -0
  60. package/dist/scope/promote-path.js +105 -0
  61. package/dist/session/ritual-entrypoint.d.ts +6 -2
  62. package/dist/session/ritual-entrypoint.js +15 -2
  63. package/dist/session/session-ready.js +31 -14
  64. package/dist/session/session-start.d.ts +2 -1
  65. package/dist/session/session-start.js +66 -3
  66. package/dist/session/verify-session-ritual.d.ts +4 -2
  67. package/dist/session/verify-session-ritual.js +11 -11
  68. package/dist/slash/product-set.js +4 -1
  69. package/dist/swarm/routing.d.ts +4 -2
  70. package/dist/swarm/routing.js +26 -4
  71. package/dist/swarm/verify-review-clean.d.ts +4 -3
  72. package/dist/swarm/verify-review-clean.js +28 -73
  73. package/dist/triage/actions/index.js +62 -2
  74. package/dist/triage/actions/types.d.ts +8 -1
  75. package/dist/triage/author-filter.d.ts +51 -0
  76. package/dist/triage/author-filter.js +152 -0
  77. package/dist/triage/classify/index.d.ts +9 -0
  78. package/dist/triage/classify/index.js +34 -2
  79. package/dist/triage/classify/label-mirror.d.ts +206 -0
  80. package/dist/triage/classify/label-mirror.js +914 -0
  81. package/dist/triage/help/registry-data.d.ts +49 -38
  82. package/dist/triage/help/registry-data.js +134 -39
  83. package/dist/triage/index.d.ts +1 -0
  84. package/dist/triage/index.js +1 -0
  85. package/dist/triage/queue/index.d.ts +1 -0
  86. package/dist/triage/queue/index.js +1 -0
  87. package/dist/triage/queue/render.d.ts +2 -0
  88. package/dist/triage/queue/render.js +6 -0
  89. package/dist/triage/queue/show.d.ts +1 -1
  90. package/dist/triage/queue/show.js +4 -2
  91. package/dist/vbrief-validate/project-definition.js +1 -1
  92. package/dist/verify-env/agent-hook-readiness.d.ts +42 -0
  93. package/dist/verify-env/agent-hook-readiness.js +150 -0
  94. package/dist/verify-env/agent-hooks-live-probe.d.ts +16 -4
  95. package/dist/verify-env/agent-hooks-live-probe.js +120 -107
  96. package/dist/verify-env/agent-hooks.js +11 -3
  97. package/dist/verify-env/index.d.ts +1 -0
  98. package/dist/verify-env/index.js +1 -0
  99. package/package.json +11 -3
@@ -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
@@ -85,4 +85,13 @@ 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 { 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
+ /** Public options: engine is optional (defaults to this module's classify API). */
91
+ export type LabelMirrorOptions = Omit<LabelMirrorOptionsCore, "engine"> & {
92
+ readonly engine?: LabelMirrorEngine;
93
+ };
94
+ export { extractReferencedRepoIssueKeys } from "./label-mirror.js";
95
+ /** Tier-1 label mirror with the #1129 classify engine bound in. */
96
+ export declare function mirrorLabels(projectRoot: string, options?: LabelMirrorOptions): [number, LabelMirrorOutcome];
88
97
  //# sourceMappingURL=index.d.ts.map
@@ -721,7 +721,9 @@ export function validateProject(projectRoot) {
721
721
  const rel = projectDefinitionPath(root);
722
722
  const classifyErrs = validateTriageAutoClassifyOnPlan(plan, rel);
723
723
  const holderErrs = validateTriageHoldMarkersOnPlan(plan, rel);
724
- const errors = [...classifyErrs, ...holderErrs];
724
+ // label-mirror validates plan.policy.triageLabelMirror (#1423); imported via re-export graph.
725
+ const mirrorErrs = validateTriageLabelMirrorOnPlanFromModule(plan, rel);
726
+ const errors = [...classifyErrs, ...holderErrs, ...mirrorErrs];
725
727
  if (errors.length > 0) {
726
728
  const lines = errors.map((err) => `FAIL: ${err}`);
727
729
  lines.push("");
@@ -732,7 +734,7 @@ export function validateProject(projectRoot) {
732
734
  const markers = resolveHoldMarkers({ projectRoot: root });
733
735
  return {
734
736
  code: 0,
735
- stdout: "OK: triageAutoClassify[] + triageHoldMarkers[] valid " +
737
+ stdout: "OK: triageAutoClassify[] + triageHoldMarkers[] + triageLabelMirror valid " +
736
738
  `(${rules.length} rules, ${markers.length} hold markers).\n`,
737
739
  stderr: "",
738
740
  };
@@ -744,4 +746,34 @@ export function listProject(projectRoot) {
744
746
  const markers = resolveHoldMarkers({ projectRoot: root });
745
747
  return `${renderList(rules, { holdMarkers: markers })}\n`;
746
748
  }
749
+ // ---------------------------------------------------------------------------
750
+ // Tier-1 label mirror (#1423 Wave 1)
751
+ // label-mirror.ts does not import this module (avoids ESM/SLizard cycle).
752
+ // Public mirrorLabels injects the classify engine into the pure implementation.
753
+ // ---------------------------------------------------------------------------
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
+ function defaultLabelMirrorEngine() {
757
+ return {
758
+ classifyIssue: (issue, options) => classifyIssue(issue, {
759
+ rules: options?.rules,
760
+ holdMarkers: options?.holdMarkers,
761
+ vbriefReferenced: options?.vbriefReferenced,
762
+ hasTriageDecision: options?.hasTriageDecision,
763
+ now: options?.now,
764
+ }),
765
+ resolveClassifyRules,
766
+ resolveHoldMarkers,
767
+ extractReferencedIssues,
768
+ };
769
+ }
770
+ export { extractReferencedRepoIssueKeys } from "./label-mirror.js";
771
+ /** Tier-1 label mirror with the #1129 classify engine bound in. */
772
+ export function mirrorLabels(projectRoot, options = {}) {
773
+ const { engine, ...rest } = options;
774
+ return mirrorLabelsCore(projectRoot, {
775
+ ...rest,
776
+ engine: engine ?? defaultLabelMirrorEngine(),
777
+ });
778
+ }
747
779
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Tier-1 deterministic SCM label mirror (#1423 Wave 1 + Wave 2 bootstrap).
3
+ *
4
+ * Classifies cached issues with the existing #1129 engine, then mirrors the
5
+ * outcome as SCM labels (dry-run default, --apply to write). Never accepts into
6
+ * the xBRIEF lifecycle and never writes proposed/ scopes.
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
+ *
12
+ * Intentionally does NOT import from ./index.js (SLizard P1 cycle). The classify
13
+ * engine is injected via LabelMirrorEngine / mirrorLabels() wrapper in index.ts.
14
+ */
15
+ import type { LabelClient } from "../../vbrief-reconcile/types.js";
16
+ import { type AuthorFilter } from "../author-filter.js";
17
+ export declare const DEFAULT_IDEMPOTENCY_LABEL = "triaged";
18
+ export declare const CACHE_DIR_NAME = ".deft-cache";
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;
26
+ export type ClassifyAction = "defer" | "archive" | "escalate" | "accept";
27
+ /** Minimal issue shape used by the mirror (matches classify GitHubIssue). */
28
+ export interface MirrorGitHubIssue {
29
+ readonly number?: number;
30
+ readonly state?: string;
31
+ readonly body?: string | null;
32
+ readonly labels?: ReadonlyArray<{
33
+ name?: string;
34
+ } | string>;
35
+ readonly updated_at?: string;
36
+ readonly created_at?: string;
37
+ }
38
+ export interface MirrorClassificationResult {
39
+ readonly action: string;
40
+ readonly reason: string;
41
+ readonly ruleIndex: number;
42
+ readonly ruleSource: string;
43
+ readonly ruleKind: string;
44
+ readonly resumeOn: string | null;
45
+ }
46
+ /** Injected classify engine — avoids a runtime import cycle with classify/index.ts. */
47
+ export interface LabelMirrorEngine {
48
+ readonly classifyIssue: (issue: MirrorGitHubIssue, options?: {
49
+ rules?: readonly unknown[];
50
+ holdMarkers?: string[] | null;
51
+ vbriefReferenced?: ReadonlySet<number> | null;
52
+ hasTriageDecision?: boolean;
53
+ now?: Date;
54
+ }) => MirrorClassificationResult | null;
55
+ readonly resolveClassifyRules: (options?: {
56
+ projectRoot?: string;
57
+ projectDefinition?: Record<string, unknown> | null;
58
+ }) => readonly unknown[];
59
+ readonly resolveHoldMarkers: (options?: {
60
+ projectRoot?: string;
61
+ projectDefinition?: Record<string, unknown> | null;
62
+ }) => string[];
63
+ /** Optional; label-mirror prefers repo-qualified keys and only needs numbers as fallback. */
64
+ readonly extractReferencedIssues?: (projectRoot?: string) => Set<number>;
65
+ }
66
+ /**
67
+ * Repo-qualified xBRIEF github-issue keys (`owner/name\\0number`).
68
+ * Unlike number-only extractReferencedIssues, this never collides across repos.
69
+ */
70
+ export declare function extractReferencedRepoIssueKeys(projectRoot?: string, lifecycleFolders?: readonly string[]): Set<string>;
71
+ export interface LabelMirrorPolicy {
72
+ /** When false, mirror is a no-op (default true when policy object is used). */
73
+ readonly enabled?: boolean;
74
+ /** Idempotency marker; issues already carrying this label are skipped. Default: triaged. */
75
+ readonly idempotencyLabel?: string;
76
+ /** Labels always applied on a successful classification (default: [idempotencyLabel]). */
77
+ readonly alwaysLabels?: readonly string[];
78
+ /** Map classify action -> additional labels to apply. */
79
+ readonly actionLabels?: Readonly<Partial<Record<ClassifyAction, readonly string[]>>>;
80
+ }
81
+ export interface ResolvedLabelMirrorPolicy {
82
+ readonly enabled: boolean;
83
+ readonly idempotencyLabel: string;
84
+ readonly alwaysLabels: readonly string[];
85
+ readonly actionLabels: Readonly<Partial<Record<ClassifyAction, readonly string[]>>>;
86
+ }
87
+ export type LabelMirrorStatus = "planned" | "applied" | "unchanged" | "skipped_already_triaged" | "skipped_no_match" | "skipped_unreadable" | "skipped_closed" | "skipped_author" | "skipped_disabled" | "error";
88
+ export interface LabelMirrorItem {
89
+ readonly repo: string;
90
+ readonly issue_number: number;
91
+ /** Issue state from cache (open/closed/unknown). */
92
+ readonly state: string | null;
93
+ readonly action: string | null;
94
+ readonly reason: string | null;
95
+ readonly ruleKind: string | null;
96
+ readonly current: readonly string[];
97
+ readonly desired: readonly string[];
98
+ readonly add: readonly string[];
99
+ readonly status: LabelMirrorStatus;
100
+ readonly message?: string;
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
+ }
120
+ export interface LabelMirrorOutcome {
121
+ readonly project_root: string;
122
+ readonly dry_run: boolean;
123
+ readonly scanned: number;
124
+ readonly planned: number;
125
+ readonly applied: number;
126
+ readonly unchanged: number;
127
+ readonly skipped_already_triaged: number;
128
+ readonly skipped_no_match: number;
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;
134
+ readonly errors: number;
135
+ readonly filters: LabelMirrorFilters;
136
+ readonly digest: LabelMirrorDigest;
137
+ readonly items: readonly LabelMirrorItem[];
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;
142
+ }
143
+ export type LabelMirrorSleepFn = (ms: number) => void;
144
+ export interface LabelMirrorOptions {
145
+ readonly dryRun?: boolean;
146
+ readonly repo?: string | null;
147
+ readonly cacheRoot?: string;
148
+ readonly client?: LabelClient;
149
+ readonly allowCrossRepo?: boolean;
150
+ readonly repoAllowlist?: readonly string[];
151
+ /** Prefer live SCM labels when true (default: !dryRun). Cache labels used for dry-run. */
152
+ readonly useLiveLabels?: boolean;
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;
172
+ /** Required: classify engine (provided by classify/index mirrorLabels wrapper). */
173
+ readonly engine: LabelMirrorEngine;
174
+ }
175
+ /** Validate plan.policy.triageLabelMirror payload. */
176
+ export declare function validateLabelMirrorPolicy(raw: unknown): {
177
+ errors: string[];
178
+ warnings: string[];
179
+ };
180
+ /** Default resolved policy when triageLabelMirror is absent. */
181
+ export declare function defaultLabelMirrorPolicy(): ResolvedLabelMirrorPolicy;
182
+ /** Resolve effective label-mirror policy from PROJECT-DEFINITION or inline object. */
183
+ export declare function resolveLabelMirrorPolicy(options?: {
184
+ projectRoot?: string;
185
+ projectDefinition?: Record<string, unknown> | null;
186
+ override?: LabelMirrorPolicy | null;
187
+ }): ResolvedLabelMirrorPolicy;
188
+ /** Labels to apply for a classified action (always + action-mapped). */
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;
192
+ /**
193
+ * Run Tier-1 label mirror over the github-issue cache (bootstrap mass-triage surface).
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.
196
+ * Requires options.engine (classify/index wrapper injects it).
197
+ * Never calls triage:accept / never writes proposed/ xBRIEFs.
198
+ */
199
+ export declare function mirrorLabels(projectRoot: string, options: LabelMirrorOptions): [number, LabelMirrorOutcome];
200
+ /** Human-readable digest for dry-run / apply reports (bootstrap mass-triage UX). */
201
+ export declare function renderLabelMirrorReport(outcome: LabelMirrorOutcome): string;
202
+ /** JSON-serializable outcome including Wave 2 digest aggregates. */
203
+ export declare function labelMirrorOutcomeToJson(outcome: LabelMirrorOutcome): Record<string, unknown>;
204
+ /** Validate triageLabelMirror on a plan object (vbrief_validate hook). */
205
+ export declare function validateTriageLabelMirrorOnPlan(plan: unknown, filepath: string): string[];
206
+ //# sourceMappingURL=label-mirror.d.ts.map