@deftai/directive-core 0.94.0 → 0.95.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 (61) hide show
  1. package/dist/authz/classify.js +485 -0
  2. package/dist/doctor/main.js +41 -9
  3. package/dist/doctor/types.d.ts +2 -2
  4. package/dist/freshness/bind.d.ts +67 -0
  5. package/dist/freshness/bind.js +201 -0
  6. package/dist/freshness/cli.d.ts +30 -0
  7. package/dist/freshness/cli.js +180 -0
  8. package/dist/freshness/compare.d.ts +14 -0
  9. package/dist/freshness/compare.js +134 -0
  10. package/dist/freshness/generation.d.ts +44 -0
  11. package/dist/freshness/generation.js +172 -0
  12. package/dist/freshness/index.d.ts +13 -0
  13. package/dist/freshness/index.js +13 -0
  14. package/dist/freshness/report.d.ts +43 -0
  15. package/dist/freshness/report.js +139 -0
  16. package/dist/freshness/types.d.ts +70 -0
  17. package/dist/freshness/types.js +30 -0
  18. package/dist/handoff-evidence/index.d.ts +8 -0
  19. package/dist/handoff-evidence/index.js +7 -0
  20. package/dist/handoff-evidence/validate.d.ts +105 -0
  21. package/dist/handoff-evidence/validate.js +423 -0
  22. package/dist/hooks/dispatcher.d.ts +3 -0
  23. package/dist/hooks/dispatcher.js +12 -3
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +2 -0
  26. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  27. package/dist/init-deposit/agent-hooks.js +5 -2
  28. package/dist/init-deposit/init-deposit.d.ts +7 -1
  29. package/dist/init-deposit/init-deposit.js +24 -4
  30. package/dist/init-deposit/refresh.d.ts +9 -1
  31. package/dist/init-deposit/refresh.js +48 -5
  32. package/dist/policy/index.d.ts +3 -0
  33. package/dist/policy/index.js +35 -0
  34. package/dist/release-e2e/greenfield-python-free-smoke.js +7 -3
  35. package/dist/session/ritual-entrypoint.d.ts +6 -2
  36. package/dist/session/ritual-entrypoint.js +15 -2
  37. package/dist/session/session-ready.js +31 -14
  38. package/dist/session/session-start.d.ts +2 -1
  39. package/dist/session/session-start.js +66 -3
  40. package/dist/session/verify-session-ritual.d.ts +4 -2
  41. package/dist/session/verify-session-ritual.js +11 -11
  42. package/dist/slash/product-set.js +4 -1
  43. package/dist/swarm/verify-review-clean.d.ts +4 -3
  44. package/dist/swarm/verify-review-clean.js +28 -73
  45. package/dist/triage/classify/index.d.ts +9 -0
  46. package/dist/triage/classify/index.js +34 -2
  47. package/dist/triage/classify/label-mirror.d.ts +143 -0
  48. package/dist/triage/classify/label-mirror.js +684 -0
  49. package/dist/triage/help/registry-data.d.ts +7 -7
  50. package/dist/triage/help/registry-data.js +27 -7
  51. package/dist/triage/queue/show.d.ts +1 -1
  52. package/dist/triage/queue/show.js +4 -2
  53. package/dist/vbrief-validate/project-definition.js +1 -1
  54. package/dist/verify-env/agent-hook-readiness.d.ts +42 -0
  55. package/dist/verify-env/agent-hook-readiness.js +150 -0
  56. package/dist/verify-env/agent-hooks-live-probe.d.ts +16 -4
  57. package/dist/verify-env/agent-hooks-live-probe.js +120 -107
  58. package/dist/verify-env/agent-hooks.js +11 -3
  59. package/dist/verify-env/index.d.ts +1 -0
  60. package/dist/verify-env/index.js +1 -0
  61. package/package.json +7 -3
@@ -1,10 +1,9 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { hasArtifactSuffix } from "../layout/resolve.js";
4
- import { buildCiSummaryLine, evaluateCiGate } from "../pr-merge-readiness/ci-gate.js";
5
- import { defaultRunGh, fetchCheckRunsRest, fetchGreptileCommentBody, fetchPrHeadSha, } from "../pr-merge-readiness/gh.js";
6
- import { evaluateGates, parseGreptileBody } from "../pr-merge-readiness/index.js";
7
- import { evaluateSlizardGate, isSlizardCheck } from "../pr-merge-readiness/slizard-gate.js";
4
+ import { computeGateResult, isMergeReady, } from "../pr-merge-readiness/compute.js";
5
+ import { VIA_ERROR, VIA_FALLBACK2 } from "../pr-merge-readiness/constants.js";
6
+ import { defaultRunGh } from "../pr-merge-readiness/gh.js";
8
7
  import { EXIT_EXTERNAL_ERROR, EXIT_OK, EXIT_UNCLEAN } from "./constants.js";
9
8
  function extractPrFromUri(uri) {
10
9
  const pullIdx = uri.indexOf("/pull/");
@@ -104,83 +103,29 @@ export function resolveCohortFromVbriefs(vbriefGlobs) {
104
103
  return { prNumbers: seenPrs, failures };
105
104
  }
106
105
  export function evaluatePr(prNumber, repo, runGh = defaultRunGh, options = {}) {
107
- const headSha = fetchPrHeadSha(prNumber, repo, runGh);
108
- if (headSha === null) {
106
+ const result = computeGateResult(prNumber, repo, runGh, options);
107
+ if (result.via === VIA_ERROR) {
109
108
  return null;
110
109
  }
111
- const body = fetchGreptileCommentBody(prNumber, repo, runGh);
112
- if (body === null) {
113
- return null;
114
- }
115
- const verdict = parseGreptileBody(body);
116
- const failures = evaluateGates(prNumber, headSha, verdict);
117
- let ciSummary = null;
118
- let slizardSummary = null;
119
- if (failures.length === 0) {
120
- if (repo === null) {
121
- failures.push("Could not resolve repo for required CI check-run gate. " +
122
- "Use --repo OWNER/REPO or run from a checked-out repository.");
123
- ciSummary = {
124
- ready_state: "blocked",
125
- error: "repo unresolved for check-runs lookup",
126
- };
127
- slizardSummary = { ready_state: "skipped", present: false };
128
- }
129
- else if (options.skipCi === true) {
130
- const ci = evaluateCiGate([], { skipCi: true });
131
- ciSummary = {
132
- ...ci.summary,
133
- summary_line: "CI check-runs: skipped (--skip-ci)",
134
- };
135
- const slizard = evaluateSlizardGate([], options);
136
- failures.push(...slizard.failures);
137
- slizardSummary = { ...slizard.summary };
138
- }
139
- else {
140
- const checks = fetchCheckRunsRest(headSha, repo, runGh);
141
- if (checks.summary === null) {
142
- failures.push("Required CI check-runs could not be fetched; fail closed by default (#2169). " +
143
- `Root cause: ${checks.error}`);
144
- ciSummary = {
145
- ready_state: "blocked",
146
- error: checks.error,
147
- checked_count: 0,
148
- ignored_checks: [...(options.ignoreCheckNames ?? [])],
149
- failed_required: [],
150
- pending_required: [],
151
- conclusions: [],
152
- };
153
- slizardSummary = { ready_state: "skipped", present: false };
154
- }
155
- else {
156
- const slizard = evaluateSlizardGate(checks.checkRuns, options);
157
- // SLizard is scored by the structured gate; exclude it from the generic CI set.
158
- const slizardNames = checks.checkRuns
159
- .filter((r) => isSlizardCheck(r.name))
160
- .map((r) => r.name);
161
- const ci = evaluateCiGate(checks.checkRuns, {
162
- skipCi: options.skipCi,
163
- ignoreCheckNames: [...(options.ignoreCheckNames ?? []), ...slizardNames],
164
- });
165
- failures.push(...ci.failures, ...slizard.failures);
166
- ciSummary = {
167
- ...ci.summary,
168
- summary_line: buildCiSummaryLine(ci.summary),
169
- };
170
- slizardSummary = { ...slizard.summary };
171
- }
172
- }
173
- }
110
+ const ciSummary = asRecord(result.partialData.ci);
111
+ const slizardSummary = asRecord(result.partialData.slizard);
174
112
  return {
175
113
  pr_number: prNumber,
176
- head_sha: headSha,
177
- verdict: { ...verdict },
178
- failures: [...failures],
179
- clean: failures.length === 0,
114
+ head_sha: result.headSha,
115
+ verdict: { ...result.verdict },
116
+ failures: [...result.failures],
117
+ clean: result.via !== VIA_FALLBACK2 && isMergeReady(result.failures),
180
118
  ci_summary: ciSummary,
181
119
  slizard_summary: slizardSummary,
120
+ via: result.via,
121
+ partial_data: { ...result.partialData },
182
122
  };
183
123
  }
124
+ function asRecord(value) {
125
+ return value !== null && typeof value === "object" && !Array.isArray(value)
126
+ ? { ...value }
127
+ : null;
128
+ }
184
129
  export function cohortResultToDict(cohort) {
185
130
  return {
186
131
  repo: cohort.repo,
@@ -194,6 +139,8 @@ export function cohortResultToDict(cohort) {
194
139
  failures: r.failures,
195
140
  ci_summary: r.ci_summary,
196
141
  slizard_summary: r.slizard_summary,
142
+ via: r.via ?? null,
143
+ partial_data: r.partial_data ?? {},
197
144
  })),
198
145
  resolution_errors: cohort.resolution_errors,
199
146
  };
@@ -216,6 +163,7 @@ export function renderReviewCleanText(cohort) {
216
163
  lines.push("");
217
164
  lines.push(` PR #${r.pr_number} -- ${status}`);
218
165
  lines.push(` HEAD SHA: ${r.head_sha ?? "<unknown>"}`);
166
+ lines.push(` Gate via: ${r.via ?? "<unknown>"}`);
219
167
  lines.push(` Greptile reviewed: ${String(v.lastReviewedSha ?? "<not parsed>")}`);
220
168
  const conf = v.confidence;
221
169
  lines.push(` Confidence: ${conf === null || conf === undefined ? "<not parsed>" : String(conf)}/5`);
@@ -228,6 +176,13 @@ export function renderReviewCleanText(cohort) {
228
176
  if (r.slizard_summary && typeof r.slizard_summary.summary_line === "string") {
229
177
  lines.push(` ${r.slizard_summary.summary_line}`);
230
178
  }
179
+ const override = asRecord(r.partial_data?.verdict_override);
180
+ if (override !== null) {
181
+ lines.push(` Verdict override: ${String(override.reason ?? "<unknown>")}`);
182
+ if (typeof override.basis === "string") {
183
+ lines.push(` Override basis: ${override.basis}`);
184
+ }
185
+ }
231
186
  r.failures.forEach((fail, index) => {
232
187
  lines.push(` [${index + 1}] ${fail}`);
233
188
  });
@@ -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 { 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, };
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 { 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, };
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,143 @@
1
+ /**
2
+ * Tier-1 deterministic SCM label mirror (#1423 Wave 1).
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
+ * Intentionally does NOT import from ./index.js (SLizard P1 cycle). The classify
9
+ * engine is injected via LabelMirrorEngine / mirrorLabels() wrapper in index.ts.
10
+ */
11
+ import type { LabelClient } from "../../vbrief-reconcile/types.js";
12
+ export declare const DEFAULT_IDEMPOTENCY_LABEL = "triaged";
13
+ export declare const CACHE_DIR_NAME = ".deft-cache";
14
+ export declare const CACHE_SOURCE = "github-issue";
15
+ export type ClassifyAction = "defer" | "archive" | "escalate" | "accept";
16
+ /** Minimal issue shape used by the mirror (matches classify GitHubIssue). */
17
+ export interface MirrorGitHubIssue {
18
+ readonly number?: number;
19
+ readonly state?: string;
20
+ readonly body?: string | null;
21
+ readonly labels?: ReadonlyArray<{
22
+ name?: string;
23
+ } | string>;
24
+ readonly updated_at?: string;
25
+ readonly created_at?: string;
26
+ }
27
+ export interface MirrorClassificationResult {
28
+ readonly action: string;
29
+ readonly reason: string;
30
+ readonly ruleIndex: number;
31
+ readonly ruleSource: string;
32
+ readonly ruleKind: string;
33
+ readonly resumeOn: string | null;
34
+ }
35
+ /** Injected classify engine — avoids a runtime import cycle with classify/index.ts. */
36
+ export interface LabelMirrorEngine {
37
+ readonly classifyIssue: (issue: MirrorGitHubIssue, options?: {
38
+ rules?: readonly unknown[];
39
+ holdMarkers?: string[] | null;
40
+ vbriefReferenced?: ReadonlySet<number> | null;
41
+ hasTriageDecision?: boolean;
42
+ now?: Date;
43
+ }) => MirrorClassificationResult | null;
44
+ readonly resolveClassifyRules: (options?: {
45
+ projectRoot?: string;
46
+ projectDefinition?: Record<string, unknown> | null;
47
+ }) => readonly unknown[];
48
+ readonly resolveHoldMarkers: (options?: {
49
+ projectRoot?: string;
50
+ projectDefinition?: Record<string, unknown> | null;
51
+ }) => string[];
52
+ /** Optional; label-mirror prefers repo-qualified keys and only needs numbers as fallback. */
53
+ readonly extractReferencedIssues?: (projectRoot?: string) => Set<number>;
54
+ }
55
+ /**
56
+ * Repo-qualified xBRIEF github-issue keys (`owner/name\\0number`).
57
+ * Unlike number-only extractReferencedIssues, this never collides across repos.
58
+ */
59
+ export declare function extractReferencedRepoIssueKeys(projectRoot?: string, lifecycleFolders?: readonly string[]): Set<string>;
60
+ export interface LabelMirrorPolicy {
61
+ /** When false, mirror is a no-op (default true when policy object is used). */
62
+ readonly enabled?: boolean;
63
+ /** Idempotency marker; issues already carrying this label are skipped. Default: triaged. */
64
+ readonly idempotencyLabel?: string;
65
+ /** Labels always applied on a successful classification (default: [idempotencyLabel]). */
66
+ readonly alwaysLabels?: readonly string[];
67
+ /** Map classify action -> additional labels to apply. */
68
+ readonly actionLabels?: Readonly<Partial<Record<ClassifyAction, readonly string[]>>>;
69
+ }
70
+ export interface ResolvedLabelMirrorPolicy {
71
+ readonly enabled: boolean;
72
+ readonly idempotencyLabel: string;
73
+ readonly alwaysLabels: readonly string[];
74
+ readonly actionLabels: Readonly<Partial<Record<ClassifyAction, readonly string[]>>>;
75
+ }
76
+ export type LabelMirrorStatus = "planned" | "applied" | "unchanged" | "skipped_already_triaged" | "skipped_no_match" | "skipped_unreadable" | "skipped_disabled" | "error";
77
+ export interface LabelMirrorItem {
78
+ readonly repo: string;
79
+ readonly issue_number: number;
80
+ readonly action: string | null;
81
+ readonly reason: string | null;
82
+ readonly ruleKind: string | null;
83
+ readonly current: readonly string[];
84
+ readonly desired: readonly string[];
85
+ readonly add: readonly string[];
86
+ readonly status: LabelMirrorStatus;
87
+ readonly message?: string;
88
+ }
89
+ export interface LabelMirrorOutcome {
90
+ readonly project_root: string;
91
+ readonly dry_run: boolean;
92
+ readonly scanned: number;
93
+ readonly planned: number;
94
+ readonly applied: number;
95
+ readonly unchanged: number;
96
+ readonly skipped_already_triaged: number;
97
+ readonly skipped_no_match: number;
98
+ readonly skipped_unreadable: number;
99
+ readonly errors: number;
100
+ readonly items: readonly LabelMirrorItem[];
101
+ readonly policy: ResolvedLabelMirrorPolicy;
102
+ }
103
+ export interface LabelMirrorOptions {
104
+ readonly dryRun?: boolean;
105
+ readonly repo?: string | null;
106
+ readonly cacheRoot?: string;
107
+ readonly client?: LabelClient;
108
+ readonly allowCrossRepo?: boolean;
109
+ readonly repoAllowlist?: readonly string[];
110
+ /** Prefer live SCM labels when true (default: !dryRun). Cache labels used for dry-run. */
111
+ readonly useLiveLabels?: boolean;
112
+ readonly now?: Date;
113
+ /** Required: classify engine (provided by classify/index mirrorLabels wrapper). */
114
+ readonly engine: LabelMirrorEngine;
115
+ }
116
+ /** Validate plan.policy.triageLabelMirror payload. */
117
+ export declare function validateLabelMirrorPolicy(raw: unknown): {
118
+ errors: string[];
119
+ warnings: string[];
120
+ };
121
+ /** Default resolved policy when triageLabelMirror is absent. */
122
+ export declare function defaultLabelMirrorPolicy(): ResolvedLabelMirrorPolicy;
123
+ /** Resolve effective label-mirror policy from PROJECT-DEFINITION or inline object. */
124
+ export declare function resolveLabelMirrorPolicy(options?: {
125
+ projectRoot?: string;
126
+ projectDefinition?: Record<string, unknown> | null;
127
+ override?: LabelMirrorPolicy | null;
128
+ }): ResolvedLabelMirrorPolicy;
129
+ /** Labels to apply for a classified action (always + action-mapped). */
130
+ export declare function desiredLabelsForClassification(action: string, policy: ResolvedLabelMirrorPolicy): string[];
131
+ /**
132
+ * Run Tier-1 label mirror over the github-issue cache.
133
+ * Dry-run by default (no SCM writes). Pass dryRun: false to apply.
134
+ * Requires options.engine (classify/index wrapper injects it).
135
+ */
136
+ export declare function mirrorLabels(projectRoot: string, options: LabelMirrorOptions): [number, LabelMirrorOutcome];
137
+ /** Human-readable digest for dry-run / apply reports. */
138
+ export declare function renderLabelMirrorReport(outcome: LabelMirrorOutcome): string;
139
+ /** JSON-serializable outcome (stable key order not required). */
140
+ export declare function labelMirrorOutcomeToJson(outcome: LabelMirrorOutcome): Record<string, unknown>;
141
+ /** Validate triageLabelMirror on a plan object (vbrief_validate hook). */
142
+ export declare function validateTriageLabelMirrorOnPlan(plan: unknown, filepath: string): string[];
143
+ //# sourceMappingURL=label-mirror.d.ts.map