@deftai/directive-core 0.71.0 → 0.72.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.
@@ -0,0 +1,35 @@
1
+ /** Record-shape version for attribution ledger entries (#2376). Bump on shape changes. */
2
+ export declare const ATTRIBUTION_SCHEMA_VERSION: 1;
3
+ /** Project-local, gitignored install-identity file. */
4
+ export declare const INSTALL_ID_REL: string;
5
+ /** Identity + provenance fields stamped onto every attribution record (#2376). */
6
+ export interface AttributionEnrichment {
7
+ /** Normalized `owner/name` of the origin remote, or null when unavailable. */
8
+ readonly repo: string | null;
9
+ /** directive engine version at emit time. */
10
+ readonly directive_version: string;
11
+ /** Stable per-checkout uuid, or null when it cannot be read/created. */
12
+ readonly install_id: string | null;
13
+ /** Attribution record-shape version. */
14
+ readonly schema_version: number;
15
+ }
16
+ /**
17
+ * Read-or-create a stable per-checkout install id in `.deft-cache/install-id`.
18
+ * Best-effort: returns null (never throws) if the file cannot be read or written.
19
+ */
20
+ export declare function resolveInstallId(projectRoot: string): string | null;
21
+ export interface BuildAttributionEnrichmentOptions {
22
+ /** Test seam for origin-repo resolution; defaults to `inferRepoFromGit`. */
23
+ readonly repoResolver?: (projectRoot: string) => string | null;
24
+ }
25
+ /**
26
+ * Build the identity/provenance enrichment stamped on attribution records (#2376).
27
+ *
28
+ * Memoized per `projectRoot` (identity/version/repo are stable within a session)
29
+ * so a caller that emits many signals in one run does not spawn a git process and
30
+ * open the install-id file per event -- mirrors `detectOriginOrg`'s cache to keep
31
+ * this off the hot emit path (#2377 review). A custom `repoResolver` bypasses the
32
+ * cache so tests stay deterministic. Every field degrades gracefully; never throws.
33
+ */
34
+ export declare function buildAttributionEnrichment(projectRoot: string, options?: BuildAttributionEnrichmentOptions): AttributionEnrichment;
35
+ //# sourceMappingURL=attribution-enrichment.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { readCorePackageVersion } from "../engine-version.js";
5
+ import { inferRepoFromGit } from "../triage/queue/repo.js";
6
+ /** Record-shape version for attribution ledger entries (#2376). Bump on shape changes. */
7
+ export const ATTRIBUTION_SCHEMA_VERSION = 1;
8
+ /** Project-local, gitignored install-identity file. */
9
+ export const INSTALL_ID_REL = join(".deft-cache", "install-id");
10
+ /**
11
+ * Read-or-create a stable per-checkout install id in `.deft-cache/install-id`.
12
+ * Best-effort: returns null (never throws) if the file cannot be read or written.
13
+ */
14
+ export function resolveInstallId(projectRoot) {
15
+ const path = resolve(projectRoot, INSTALL_ID_REL);
16
+ try {
17
+ if (existsSync(path)) {
18
+ const existing = readFileSync(path, "utf8").trim();
19
+ if (existing.length > 0) {
20
+ return existing;
21
+ }
22
+ }
23
+ }
24
+ catch {
25
+ // fall through and try to (re)create
26
+ }
27
+ try {
28
+ const id = randomUUID();
29
+ mkdirSync(dirname(path), { recursive: true });
30
+ writeFileSync(path, `${id}\n`, "utf8");
31
+ return id;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ const enrichmentCache = new Map();
38
+ /**
39
+ * Build the identity/provenance enrichment stamped on attribution records (#2376).
40
+ *
41
+ * Memoized per `projectRoot` (identity/version/repo are stable within a session)
42
+ * so a caller that emits many signals in one run does not spawn a git process and
43
+ * open the install-id file per event -- mirrors `detectOriginOrg`'s cache to keep
44
+ * this off the hot emit path (#2377 review). A custom `repoResolver` bypasses the
45
+ * cache so tests stay deterministic. Every field degrades gracefully; never throws.
46
+ */
47
+ export function buildAttributionEnrichment(projectRoot, options = {}) {
48
+ const useCache = options.repoResolver === undefined;
49
+ if (useCache) {
50
+ const cached = enrichmentCache.get(projectRoot);
51
+ if (cached !== undefined) {
52
+ return cached;
53
+ }
54
+ }
55
+ const resolver = options.repoResolver ?? inferRepoFromGit;
56
+ let repo = null;
57
+ try {
58
+ repo = resolver(projectRoot);
59
+ }
60
+ catch {
61
+ repo = null;
62
+ }
63
+ const enrichment = {
64
+ repo,
65
+ directive_version: readCorePackageVersion(),
66
+ install_id: resolveInstallId(projectRoot),
67
+ schema_version: ATTRIBUTION_SCHEMA_VERSION,
68
+ };
69
+ if (useCache) {
70
+ enrichmentCache.set(projectRoot, enrichment);
71
+ }
72
+ return enrichment;
73
+ }
74
+ //# sourceMappingURL=attribution-enrichment.js.map
@@ -2,6 +2,7 @@ import { resolve } from "node:path";
2
2
  import { DEFAULT_EVENT_LOG, emit } from "../lifecycle/events.js";
3
3
  import { isValueFeedbackPathAllowed, resolveValueFeedback, } from "../policy/value-feedback.js";
4
4
  import { ATTRIBUTION_EVENT_NAMES } from "./attribution-constants.js";
5
+ import { buildAttributionEnrichment } from "./attribution-enrichment.js";
5
6
  export { ALL_ATTRIBUTION_EVENT_NAMES, ATTRIBUTION_EVENT_NAMES, ATTRIBUTION_REQUIRED_PAYLOAD, } from "./attribution-constants.js";
6
7
  function resolveLedgerLogPath(projectRoot, logPath) {
7
8
  if (logPath !== undefined && logPath !== null) {
@@ -28,9 +29,13 @@ export function emitAttributionSignal(name, payload, options) {
28
29
  }
29
30
  const signalClass = signalClassForEvent(name);
30
31
  const logPath = resolveLedgerLogPath(options.projectRoot, options.logPath);
32
+ const enrichment = buildAttributionEnrichment(options.projectRoot);
33
+ // Authoritative fields (signal_class + provenance enrichment) are spread LAST
34
+ // so a caller payload can never silently shadow them (#2377 review).
31
35
  return emit(name, {
32
- signal_class: signalClass,
33
36
  ...payload,
37
+ signal_class: signalClass,
38
+ ...enrichment,
34
39
  }, { logPath });
35
40
  }
36
41
  catch {
@@ -31,6 +31,16 @@ export const CANONICAL_GITIGNORE_BASELINE = [
31
31
  "vbrief/.triage-cache/scope-lifecycle.jsonl",
32
32
  "vbrief/.triage-cache/decompositions/",
33
33
  "vbrief/.triage-cache/doctor-state.json",
34
+ // Symmetric `xbrief/` layout entries (#2348). On the migrated `xbrief/` tree
35
+ // the engine writes operator-private triage-cache files to
36
+ // `xbrief/.triage-cache/`; without these the paths are trackable, violating
37
+ // the #1144 hybrid policy. Both layouts are emitted (harmless extra lines on
38
+ // the layout not in use) to match the both-layout `.eval/` treatment.
39
+ "xbrief/.triage-cache/candidates.jsonl",
40
+ "xbrief/.triage-cache/summary-history.jsonl",
41
+ "xbrief/.triage-cache/scope-lifecycle.jsonl",
42
+ "xbrief/.triage-cache/decompositions/",
43
+ "xbrief/.triage-cache/doctor-state.json",
34
44
  "vbrief/*.lock",
35
45
  ".deft/core.bak-*/",
36
46
  ".deft/*.bak-*",
@@ -1,4 +1,3 @@
1
- export declare const DEFAULT_LOG_PATH: string;
2
1
  export declare class CandidatesLogError extends Error {
3
2
  constructor(message: string);
4
3
  }
@@ -2,7 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, unlinkSync, writeSync, } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { pyRepr } from "../scm/py-format.js";
5
- export const DEFAULT_LOG_PATH = resolve(import.meta.dirname, "..", "..", "..", "..", "vbrief", ".eval", "candidates.jsonl");
5
+ import { resolveCandidatesLogPath } from "../triage/cache-path.js";
6
+ function defaultLogPath() {
7
+ return resolveCandidatesLogPath(process.cwd());
8
+ }
6
9
  const VALID_DECISIONS = new Set([
7
10
  "accept",
8
11
  "reject",
@@ -105,7 +108,7 @@ function validateEntry(entry) {
105
108
  }
106
109
  }
107
110
  function resolvePath(path) {
108
- return path !== undefined && path !== null ? resolve(path) : DEFAULT_LOG_PATH;
111
+ return path !== undefined && path !== null ? resolve(path) : defaultLogPath();
109
112
  }
110
113
  function acquireAppendLock(logPath) {
111
114
  while (threadLocked) {
@@ -44,6 +44,14 @@ export interface FetchIssueOptions {
44
44
  readonly cacheRoot?: string | null;
45
45
  readonly scmCall?: ScmCallFn;
46
46
  }
47
+ /**
48
+ * Strip the `# #<number>: <title>` header that `renderContent` prepends when it
49
+ * materializes an issue's `content.md` at cache-put (#2314). The header is the
50
+ * only shape difference between the cache-read body and the live/raw-body path;
51
+ * removing it makes an ingested xBRIEF's `Overview` identical whether the source
52
+ * was a cache hit or a live fetch. A missing/older-shape header is left intact.
53
+ */
54
+ export declare function stripRenderedIssueHeader(content: string, number: number): string;
47
55
  export declare function fetchFromCache(repo: string, number: number, options?: FetchIssueOptions): Record<string, unknown> | null;
48
56
  export declare function fetchIssueComments(repo: string, number: number, options?: FetchIssueOptions): IssueComment[];
49
57
  export declare function attachIssueCommentThread(issue: Record<string, unknown>, comments: readonly IssueComment[]): Record<string, unknown>;
@@ -386,6 +386,20 @@ export function targetFilename(number, title, artifactSuffix = LEGACY_ARTIFACT_S
386
386
  const slug = slugify(title) || `issue-${number}`;
387
387
  return `${TODAY}-${number}-${slug}${artifactSuffix}`;
388
388
  }
389
+ /**
390
+ * Strip the `# #<number>: <title>` header that `renderContent` prepends when it
391
+ * materializes an issue's `content.md` at cache-put (#2314). The header is the
392
+ * only shape difference between the cache-read body and the live/raw-body path;
393
+ * removing it makes an ingested xBRIEF's `Overview` identical whether the source
394
+ * was a cache hit or a live fetch. A missing/older-shape header is left intact.
395
+ */
396
+ export function stripRenderedIssueHeader(content, number) {
397
+ if (!Number.isFinite(number)) {
398
+ return content;
399
+ }
400
+ const header = new RegExp(`^# #${number}:[^\\n]*\\n\\n`);
401
+ return content.replace(header, "");
402
+ }
389
403
  export function fetchFromCache(repo, number, options = {}) {
390
404
  const key = `${repo}/${number}`;
391
405
  try {
@@ -405,7 +419,11 @@ export function fetchFromCache(repo, number, options = {}) {
405
419
  // buildIssueVbrief.
406
420
  if (result.contentPath !== null) {
407
421
  try {
408
- issue.body = readFileSync(result.contentPath, "utf8");
422
+ // #2314: content.md is `renderContent`-prefixed with a `# #<n>: <title>`
423
+ // header at cache-put. Strip that header so the cache-read Overview
424
+ // matches the live/raw-body path (which has no header), keeping the
425
+ // durable xBRIEF identical for a cache hit vs a live fetch.
426
+ issue.body = stripRenderedIssueHeader(readFileSync(result.contentPath, "utf8"), Number(issue.number));
409
427
  }
410
428
  catch {
411
429
  // fall back to the raw body (re-scanned downstream)
@@ -6,6 +6,7 @@ import { call } from "../scm/call.js";
6
6
  import { updateDecomposedChildBackReferences } from "../scope/decomposed-refs.js";
7
7
  import { resolveProjectRoot } from "../scope/project-context.js";
8
8
  import { resolveProjectRepo } from "../slice/project-context.js";
9
+ import { LEGACY_INFO_ROOT_KEY, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
9
10
  export const LIFECYCLE_FOLDERS = [
10
11
  "proposed",
11
12
  "pending",
@@ -633,8 +634,14 @@ export function applyLifecycleFixes(vbriefDir, report, projectRoot = null) {
633
634
  const terminalStatus = destFolder === "cancelled" ? "cancelled" : "completed";
634
635
  plan.status = terminalStatus;
635
636
  const stamp = utcNowIso();
636
- const info = (data.vBRIEFInfo ?? {});
637
- data.vBRIEFInfo = info;
637
+ // Stamp `updated` into whichever info envelope the file already uses (#2346).
638
+ // Canonical v0.8 briefs use `xBRIEFInfo`; stamping `vBRIEFInfo` unconditionally
639
+ // appended a stray, version-less `vBRIEFInfo` block that then failed
640
+ // `vbrief:validate` ('vBRIEFInfo.version' must be one of ..., got 'undefined').
641
+ // Legacy briefs (or files without either envelope) keep the `vBRIEFInfo` key.
642
+ const infoKey = MIGRATED_INFO_ROOT_KEY in data ? MIGRATED_INFO_ROOT_KEY : LEGACY_INFO_ROOT_KEY;
643
+ const info = (data[infoKey] ?? {});
644
+ data[infoKey] = info;
638
645
  info.updated = stamp;
639
646
  plan.updated = stamp;
640
647
  propagateItemStatus(plan.items, terminalStatus, stamp);
@@ -379,8 +379,8 @@ export function cmdSubagentMonitor(argv, cwd = process.cwd()) {
379
379
  process.stderr.write(`Error: ${args.error}\n`);
380
380
  return EXIT_EXTERNAL_ERROR;
381
381
  }
382
- if (args.thresholdMinutes <= 0) {
383
- process.stderr.write(`Error: --threshold-minutes must be positive, got ${args.thresholdMinutes}\n`);
382
+ if (Number.isNaN(args.thresholdMinutes) || args.thresholdMinutes <= 0) {
383
+ process.stderr.write(`Error: --threshold-minutes must be a positive number, got ${args.thresholdMinutes}\n`);
384
384
  return EXIT_EXTERNAL_ERROR;
385
385
  }
386
386
  const scratchEntries = args.scratchDirs.length > 0
@@ -215,8 +215,8 @@ function inspectSwarmSubagentBackend(data) {
215
215
  source: "default-on-error",
216
216
  };
217
217
  }
218
- function inspectValueFeedbackField(data) {
219
- const field = inspectValueFeedback(data);
218
+ function inspectValueFeedbackField(data, projectRoot) {
219
+ const field = inspectValueFeedback(data, projectRoot);
220
220
  return {
221
221
  name: field.name,
222
222
  current: field.current,
@@ -241,7 +241,7 @@ const REGISTERED_POLICIES = [
241
241
  /** Walk registered inspectors and return one row per field (#1148). */
242
242
  export function inspectAllPolicies(projectRoot) {
243
243
  const [data] = loadProjectDefinition(projectRoot);
244
- return REGISTERED_POLICIES.map((inspect) => inspect(data));
244
+ return REGISTERED_POLICIES.map((inspect) => inspect(data, projectRoot));
245
245
  }
246
246
  /** Look up a single registered field by canonical dotted-path name (or CLI alias). */
247
247
  export function inspectOnePolicy(name, projectRoot) {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Company-owned GitHub orgs whose repos auto-enable LOCAL value-feedback
3
+ * collection (#2376). Org membership is the consent for local, no-egress
4
+ * collection; network/upstream surfaces stay opt-in regardless.
5
+ */
6
+ export declare const DEFAULT_AUTOENABLE_ORGS: readonly string[];
7
+ /** Optional comma-separated env override that EXTENDS the built-in trusted-org set. */
8
+ export declare const AUTOENABLE_ORGS_ENV = "DEFT_VALUE_AUTOENABLE_ORGS";
9
+ /** Built-in trusted orgs plus any from the env override, normalized to lowercase. */
10
+ export declare function resolveTrustedOrgs(env?: NodeJS.ProcessEnv): Set<string>;
11
+ /** Test/hygiene hook: drop the memoized origin-org lookups. */
12
+ export declare function clearOriginOrgCache(): void;
13
+ export interface OriginOrgOptions {
14
+ /** Test seam for origin-repo resolution; defaults to `inferRepoFromGit`. */
15
+ readonly repoResolver?: (projectRoot: string) => string | null;
16
+ /** Override caching; defaults to caching only when no custom resolver is supplied. */
17
+ readonly useCache?: boolean;
18
+ }
19
+ /**
20
+ * Lowercased owner of the origin remote for `projectRoot`, or null when there is
21
+ * no remote / not a git checkout. Memoized per projectRoot (the org does not
22
+ * change within a session) to keep the resolver off the hot emit path.
23
+ */
24
+ export declare function detectOriginOrg(projectRoot: string, options?: OriginOrgOptions): string | null;
25
+ export interface OrgAutoEnableOptions extends OriginOrgOptions {
26
+ readonly env?: NodeJS.ProcessEnv;
27
+ }
28
+ /**
29
+ * True when `projectRoot`'s origin org is in the trusted-org set (#2376).
30
+ * Fail-safe: any resolution failure (no remote, non-GitHub, git absent) is false.
31
+ */
32
+ export declare function isTrustedOrgAutoEnable(projectRoot: string, options?: OrgAutoEnableOptions): boolean;
33
+ //# sourceMappingURL=value-feedback-autoenable.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { inferRepoFromGit } from "../triage/queue/repo.js";
2
+ /**
3
+ * Company-owned GitHub orgs whose repos auto-enable LOCAL value-feedback
4
+ * collection (#2376). Org membership is the consent for local, no-egress
5
+ * collection; network/upstream surfaces stay opt-in regardless.
6
+ */
7
+ export const DEFAULT_AUTOENABLE_ORGS = ["deftai"];
8
+ /** Optional comma-separated env override that EXTENDS the built-in trusted-org set. */
9
+ export const AUTOENABLE_ORGS_ENV = "DEFT_VALUE_AUTOENABLE_ORGS";
10
+ /** Built-in trusted orgs plus any from the env override, normalized to lowercase. */
11
+ export function resolveTrustedOrgs(env = process.env) {
12
+ const orgs = new Set(DEFAULT_AUTOENABLE_ORGS.map((o) => o.toLowerCase()));
13
+ const raw = env[AUTOENABLE_ORGS_ENV];
14
+ if (typeof raw === "string") {
15
+ for (const part of raw.split(",")) {
16
+ const trimmed = part.trim().toLowerCase();
17
+ if (trimmed.length > 0) {
18
+ orgs.add(trimmed);
19
+ }
20
+ }
21
+ }
22
+ return orgs;
23
+ }
24
+ const originOrgCache = new Map();
25
+ /** Test/hygiene hook: drop the memoized origin-org lookups. */
26
+ export function clearOriginOrgCache() {
27
+ originOrgCache.clear();
28
+ }
29
+ /**
30
+ * Lowercased owner of the origin remote for `projectRoot`, or null when there is
31
+ * no remote / not a git checkout. Memoized per projectRoot (the org does not
32
+ * change within a session) to keep the resolver off the hot emit path.
33
+ */
34
+ export function detectOriginOrg(projectRoot, options = {}) {
35
+ const useCache = options.useCache ?? options.repoResolver === undefined;
36
+ if (useCache && originOrgCache.has(projectRoot)) {
37
+ return originOrgCache.get(projectRoot) ?? null;
38
+ }
39
+ const resolver = options.repoResolver ?? inferRepoFromGit;
40
+ let org = null;
41
+ try {
42
+ const repo = resolver(projectRoot);
43
+ if (typeof repo === "string") {
44
+ const owner = repo.split("/")[0]?.trim().toLowerCase() ?? "";
45
+ org = owner.length > 0 ? owner : null;
46
+ }
47
+ }
48
+ catch {
49
+ org = null;
50
+ }
51
+ if (useCache) {
52
+ originOrgCache.set(projectRoot, org);
53
+ }
54
+ return org;
55
+ }
56
+ /**
57
+ * True when `projectRoot`'s origin org is in the trusted-org set (#2376).
58
+ * Fail-safe: any resolution failure (no remote, non-GitHub, git absent) is false.
59
+ */
60
+ export function isTrustedOrgAutoEnable(projectRoot, options = {}) {
61
+ const org = detectOriginOrg(projectRoot, options);
62
+ if (org === null) {
63
+ return false;
64
+ }
65
+ return resolveTrustedOrgs(options.env).has(org);
66
+ }
67
+ //# sourceMappingURL=value-feedback-autoenable.js.map
@@ -1,3 +1,4 @@
1
+ import { type OrgAutoEnableOptions } from "./value-feedback-autoenable.js";
1
2
  /** Canonical registered policy field name (matches other FIELD_* dotted paths). */
2
3
  export declare const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
3
4
  /** Short alias accepted by `policy:show --field=valueFeedback` (#1709). */
@@ -16,7 +17,7 @@ export interface ValueFeedbackConfig {
16
17
  readonly sessionLine: boolean;
17
18
  readonly upstreamPrompt: boolean;
18
19
  }
19
- export type ValueFeedbackSource = "typed" | "default" | "default-on-error";
20
+ export type ValueFeedbackSource = "typed" | "org-auto" | "default" | "default-on-error";
20
21
  export interface ValueFeedbackResolved extends ValueFeedbackConfig {
21
22
  readonly source: ValueFeedbackSource;
22
23
  readonly error: string | null;
@@ -24,8 +25,20 @@ export interface ValueFeedbackResolved extends ValueFeedbackConfig {
24
25
  export declare const VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE: string;
25
26
  /** Validate a `plan.policy.valueFeedback` payload. */
26
27
  export declare function validateValueFeedback(value: unknown): string[];
27
- /** Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709). */
28
- export declare function resolveValueFeedback(projectRoot: string): ValueFeedbackResolved;
28
+ export interface ResolveValueFeedbackOptions {
29
+ /** Test seam for origin-org auto-enable resolution (#2376). */
30
+ readonly autoEnable?: OrgAutoEnableOptions;
31
+ }
32
+ /**
33
+ * Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709).
34
+ *
35
+ * Precedence (#2376): an explicit typed `valueFeedback` block always wins
36
+ * (including `enabled: false`). Only when the typed flag is ABSENT does the
37
+ * trusted-org auto-enable layer apply -- for company-owned (deftai) repos it
38
+ * turns LOCAL emit + session readback ON while leaving network/upstream OFF.
39
+ * Any other repo (or no origin remote) stays OFF.
40
+ */
41
+ export declare function resolveValueFeedback(projectRoot: string, options?: ResolveValueFeedbackOptions): ValueFeedbackResolved;
29
42
  /** Master gate: when `enabled` is false, every downstream path is rejected. */
30
43
  export declare function isValueFeedbackPathAllowed(path: ValueFeedbackSubFlag, policy: ValueFeedbackResolved): boolean;
31
44
  /** Resolved per-path gate booleans for policy:show and enable status output. */
@@ -38,8 +51,20 @@ export interface ValueFeedbackPolicyField {
38
51
  readonly default: ValueFeedbackConfig;
39
52
  readonly source: string;
40
53
  }
41
- /** Inspector row for `policy:show --field=valueFeedback`. */
42
- export declare function inspectValueFeedback(data: Record<string, unknown> | null): ValueFeedbackPolicyField;
54
+ export interface InspectValueFeedbackOptions {
55
+ /** Test seam for origin-org auto-enable resolution (#2376). */
56
+ readonly autoEnable?: OrgAutoEnableOptions;
57
+ }
58
+ /**
59
+ * Inspector row for `policy:show --field=valueFeedback`.
60
+ *
61
+ * When `projectRoot` is supplied this MUST mirror {@link resolveValueFeedback}'s
62
+ * precedence exactly (#2377 review): with no explicit typed block, a trusted-org
63
+ * checkout resolves to `org-auto`/ON so `policy:show` never reports OFF while the
64
+ * ledger is actively collecting. Omitting `projectRoot` keeps the legacy
65
+ * data-only behavior for callers that only need the field name/shape.
66
+ */
67
+ export declare function inspectValueFeedback(data: Record<string, unknown> | null, projectRoot?: string, options?: InspectValueFeedbackOptions): ValueFeedbackPolicyField;
43
68
  export interface EnableValueFeedbackOptions {
44
69
  readonly confirm: boolean;
45
70
  readonly actor?: string;
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
3
3
  import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
4
4
  import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
5
+ import { isTrustedOrgAutoEnable } from "./value-feedback-autoenable.js";
5
6
  /** Canonical registered policy field name (matches other FIELD_* dotted paths). */
6
7
  export const FIELD_VALUE_FEEDBACK = "plan.policy.valueFeedback";
7
8
  /** Short alias accepted by `policy:show --field=valueFeedback` (#1709). */
@@ -33,6 +34,20 @@ function defaultResolved(source, error = null) {
33
34
  error,
34
35
  };
35
36
  }
37
+ /**
38
+ * Trusted-org auto-enable resolution (#2376): LOCAL emit + session readback ON,
39
+ * network/upstream OFF. Applies only when the typed flag is absent.
40
+ */
41
+ function orgAutoResolved() {
42
+ return {
43
+ enabled: true,
44
+ emitEvents: VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED.emitEvents,
45
+ sessionLine: VALUE_FEEDBACK_SUBFLAG_DEFAULTS_WHEN_ENABLED.sessionLine,
46
+ upstreamPrompt: false,
47
+ source: "org-auto",
48
+ error: null,
49
+ };
50
+ }
36
51
  function readSubFlag(block, key, masterEnabled) {
37
52
  if (!masterEnabled) {
38
53
  return false;
@@ -88,8 +103,16 @@ function resolveFromPolicyBlock(raw) {
88
103
  error: null,
89
104
  };
90
105
  }
91
- /** Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709). */
92
- export function resolveValueFeedback(projectRoot) {
106
+ /**
107
+ * Resolve `plan.policy.valueFeedback` from PROJECT-DEFINITION (#1709).
108
+ *
109
+ * Precedence (#2376): an explicit typed `valueFeedback` block always wins
110
+ * (including `enabled: false`). Only when the typed flag is ABSENT does the
111
+ * trusted-org auto-enable layer apply -- for company-owned (deftai) repos it
112
+ * turns LOCAL emit + session readback ON while leaving network/upstream OFF.
113
+ * Any other repo (or no origin remote) stays OFF.
114
+ */
115
+ export function resolveValueFeedback(projectRoot, options = {}) {
93
116
  const [data, err] = loadProjectDefinition(projectRoot);
94
117
  if (data === null) {
95
118
  return defaultResolved("default-on-error", err);
@@ -99,6 +122,9 @@ export function resolveValueFeedback(projectRoot) {
99
122
  policyBlock === null ||
100
123
  Array.isArray(policyBlock) ||
101
124
  !("valueFeedback" in policyBlock)) {
125
+ if (isTrustedOrgAutoEnable(projectRoot, options.autoEnable)) {
126
+ return orgAutoResolved();
127
+ }
102
128
  return defaultResolved("default");
103
129
  }
104
130
  return resolveFromPolicyBlock(policyBlock.valueFeedback);
@@ -126,57 +152,49 @@ export function formatValueFeedbackStatusLine(policy) {
126
152
  `sessionLine=${String(policy.sessionLine)} (path=${String(gates.sessionLine)}) ` +
127
153
  `upstreamPrompt=${String(policy.upstreamPrompt)} (path=${String(gates.upstreamPrompt)}).`);
128
154
  }
129
- function defaultInspectorConfig() {
130
- const defaults = defaultResolved("default");
131
- return {
132
- enabled: defaults.enabled,
133
- emitEvents: defaults.emitEvents,
134
- sessionLine: defaults.sessionLine,
135
- upstreamPrompt: defaults.upstreamPrompt,
136
- };
137
- }
138
- function buildDefaultInspectorField() {
155
+ function fieldFromResolved(resolved) {
139
156
  return {
140
157
  name: FIELD_VALUE_FEEDBACK,
141
- current: defaultInspectorConfig(),
158
+ current: {
159
+ enabled: resolved.enabled,
160
+ emitEvents: resolved.emitEvents,
161
+ sessionLine: resolved.sessionLine,
162
+ upstreamPrompt: resolved.upstreamPrompt,
163
+ },
142
164
  default: {
143
165
  enabled: DEFAULT_VALUE_FEEDBACK_ENABLED,
144
166
  emitEvents: false,
145
167
  sessionLine: false,
146
168
  upstreamPrompt: false,
147
169
  },
148
- source: "default",
170
+ source: resolved.source,
149
171
  };
150
172
  }
151
- /** Inspector row for `policy:show --field=valueFeedback`. */
152
- export function inspectValueFeedback(data) {
173
+ /**
174
+ * Inspector row for `policy:show --field=valueFeedback`.
175
+ *
176
+ * When `projectRoot` is supplied this MUST mirror {@link resolveValueFeedback}'s
177
+ * precedence exactly (#2377 review): with no explicit typed block, a trusted-org
178
+ * checkout resolves to `org-auto`/ON so `policy:show` never reports OFF while the
179
+ * ledger is actively collecting. Omitting `projectRoot` keeps the legacy
180
+ * data-only behavior for callers that only need the field name/shape.
181
+ */
182
+ export function inspectValueFeedback(data, projectRoot, options = {}) {
153
183
  if (data === null) {
154
- return buildDefaultInspectorField();
184
+ return fieldFromResolved(defaultResolved("default"));
155
185
  }
156
186
  const policyBlock = readPlanPolicy(data.plan);
157
187
  if (typeof policyBlock !== "object" ||
158
188
  policyBlock === null ||
159
189
  Array.isArray(policyBlock) ||
160
190
  !("valueFeedback" in policyBlock)) {
161
- return buildDefaultInspectorField();
191
+ if (projectRoot !== undefined && isTrustedOrgAutoEnable(projectRoot, options.autoEnable)) {
192
+ return fieldFromResolved(orgAutoResolved());
193
+ }
194
+ return fieldFromResolved(defaultResolved("default"));
162
195
  }
163
196
  const resolved = resolveFromPolicyBlock(policyBlock.valueFeedback);
164
- return {
165
- name: FIELD_VALUE_FEEDBACK,
166
- current: {
167
- enabled: resolved.enabled,
168
- emitEvents: resolved.emitEvents,
169
- sessionLine: resolved.sessionLine,
170
- upstreamPrompt: resolved.upstreamPrompt,
171
- },
172
- default: {
173
- enabled: DEFAULT_VALUE_FEEDBACK_ENABLED,
174
- emitEvents: false,
175
- sessionLine: false,
176
- upstreamPrompt: false,
177
- },
178
- source: resolved.source,
179
- };
197
+ return fieldFromResolved(resolved);
180
198
  }
181
199
  /** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
182
200
  export function enableValueFeedback(projectRoot, options) {
@@ -37,7 +37,10 @@ export function runChangelogCheck(projectRoot, io) {
37
37
  io.writeOut("FAIL: CHANGELOG.md not found\n");
38
38
  return 1;
39
39
  }
40
- const text = readFileSync(path, "utf8");
40
+ // Normalize CRLF -> LF before matching (#2329). On Windows checkouts with
41
+ // core.autocrlf=true the working tree uses CRLF, and the `[ \t]*\n` header
42
+ // pattern does not consume the `\r`, so the section falsely reads as absent.
43
+ const text = readFileSync(path, "utf8").replace(/\r\n/g, "\n");
41
44
  const match = UNRELEASED_RE.exec(text);
42
45
  if (match === null) {
43
46
  io.writeOut("FAIL: No [Unreleased] section found in CHANGELOG.md\n");
@@ -1,8 +1,7 @@
1
- import { spawnSync } from "node:child_process";
2
- import { dirname, join, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
1
+ import { join } from "node:path";
4
2
  import { CacheNotFoundError } from "../../cache/errors.js";
5
3
  import { cacheGet } from "../../cache/operations.js";
4
+ import { ingestSingleForAccept as ingestSingleForAcceptTs } from "../../intake/issue-ingest.js";
6
5
  import { call } from "../../scm/call.js";
7
6
  import { ScmStubError } from "../../scm/errors.js";
8
7
  import { createCandidatesLog, findByIssue, resolveAuditLogPath, rollbackAuditEntry, } from "./candidates-log.js";
@@ -18,12 +17,6 @@ const TERMINAL_DECISIONS = new Set(["accept", "reject", "mark-duplicate"]);
18
17
  const DEFAULT_ACTOR = "agent:triage";
19
18
  const DEFAULT_NEEDS_AC_COMMENT = "This issue lacks acceptance criteria. Please add a Test/Acceptance " +
20
19
  "narrative before this can be triaged. (deft #845)";
21
- function resolveDeftRoot() {
22
- if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
23
- return resolve(process.env.DEFT_ROOT);
24
- }
25
- return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..", "..");
26
- }
27
20
  function defaultNowIso() {
28
21
  return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
29
22
  }
@@ -62,39 +55,22 @@ function defaultScmRunner() {
62
55
  },
63
56
  };
64
57
  }
65
- function defaultIssueIngest(deftRoot) {
58
+ function defaultIssueIngest() {
66
59
  return {
67
60
  ingestSingleForAccept(issueNumber, repo, options = {}) {
68
61
  const projectRoot = options.projectRoot ?? process.cwd();
69
- const script = [
70
- "import sys",
71
- "from pathlib import Path",
72
- `sys.path.insert(0, ${JSON.stringify(join(deftRoot, "scripts"))})`,
73
- "import issue_ingest",
74
- "issue_ingest.ingest_single_for_accept(",
75
- `${issueNumber},`,
76
- `${JSON.stringify(repo)},`,
77
- `project_root=Path(${JSON.stringify(projectRoot)}),`,
78
- ")",
79
- ].join("\n");
80
- const result = spawnSync("uv", ["run", "python", "-c", script], {
81
- cwd: deftRoot,
82
- encoding: "utf8",
83
- stdio: ["ignore", "pipe", "pipe"],
84
- });
85
- if (result.status !== 0) {
86
- const stderr = (result.stderr ?? "").trim();
87
- throw new Error(stderr || "issue:ingest delegation failed");
88
- }
62
+ // Delegate to the native TS intake path (#2350). The legacy Python
63
+ // `scripts/issue_ingest.py` shell-out was orphaned when #1933 removed the
64
+ // Python surface, leaving `triage:accept` raising ModuleNotFoundError.
65
+ ingestSingleForAcceptTs(issueNumber, repo, { projectRoot });
89
66
  },
90
67
  };
91
68
  }
92
69
  /** Default dependency bundle for production CLI use. */
93
70
  export function createDefaultDeps(projectRoot) {
94
- const deftRoot = resolveDeftRoot();
95
71
  const deps = {
96
72
  candidatesLog: createCandidatesLog(projectRoot),
97
- issueIngest: defaultIssueIngest(deftRoot),
73
+ issueIngest: defaultIssueIngest(),
98
74
  scm: defaultScmRunner(),
99
75
  nowIso: defaultNowIso,
100
76
  stderr: (message) => process.stderr.write(`${message}\n`),
@@ -9,6 +9,8 @@ export declare function gitattributesTriageCacheGlob(projectRoot: string): strin
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
11
  export declare const EVAL_README_BODY = "# `vbrief/.triage-cache/` -- triage working-set artefacts\n\nThis directory holds the append-only JSON-lines logs that the triage and\nslicing skills emit. The framework governs which files in here are tracked\nby git versus gitignored using a **hybrid policy** (#1144, child of #1119).\n\n## Tracking policy\n\n| File | Tracked? | Why |\n| --- | --- | --- |\n| `slices.jsonl` | Yes -- **committed** | Team-shared cohort records produced by slicing skills (D13 / #1132). New operators joining the team need to see prior cohort outputs to detect orphans and avoid re-slicing the same scope. |\n| `candidates.jsonl` | No -- **gitignored** | Operator-private triage decisions (#845 Story 2). Each operator's local accept / defer / reject stream is per-machine state; sharing it would conflate operators' timing + identity across the team. Re-derive on a fresh clone via `task triage:bootstrap`. |\n| `summary-history.jsonl` | No -- **gitignored** | Operator-private observability for `task triage:summary` output time-series. Not load-bearing for any decision. |\n| `scope-lifecycle.jsonl` | No -- **gitignored** | Operator-private scope-lifecycle audit decisions (D1 / #1121). Each demote (`task scope:demote`) appends one entry including a `demote_meta` block (`was_promoted`, `original_promotion_decision_id`, `days_in_pending`, `demote_reason`, `demoted_from`). Per-operator stream; sharing would conflate operators' demote timing across the team. Lightweight metrics over this log are tracked separately at #1180. |\n| `decompositions/` | No -- **gitignored** | Temporary story-decomposition proposal drafts. These JSON drafts are local scratch artifacts, not vBRIEFs; generated child story vBRIEFs are created by `task scope:decompose` in lifecycle folders, defaulting to `vbrief/pending/`. |\n| `doctor-state.json` | No -- **gitignored** | Per-machine `task doctor` throttle state (last exit code + timestamps) persisted to gate the 24h/4h re-probe window (#1308 / #1464). Local to each clone; never committed. |\n\nThe gitignore lines live in the 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`). All paths\nnot listed above remain committed by default.\n\n## Fresh-clone regeneration\n\nOn a fresh clone (or any machine that has never run triage), `candidates.jsonl`\nis absent. Regenerate it with:\n\n```\ntask triage:bootstrap\n```\n\nThe bootstrap path detects the missing file, runs the auto-classifier, and\nwrites a fresh `vbrief/.triage-cache/candidates.jsonl`. It does NOT touch the tracked\n`slices.jsonl`; cohort records remain a team-shared resource.\n\n## `merge=union` policy for `*.jsonl`\n\nThe repo-root `.gitattributes` declares:\n\n```\nvbrief/.triage-cache/*.jsonl merge=union\n```\n\nThe `union` merge driver concatenates both sides' appended lines on\nauto-merge, so two branches that each appended a different record to the\nsame JSON-lines file rebase cleanly without operator surgery. Two things\noperators should know:\n\n- **Concatenation, not set-union.** When two branches append DIFFERENT\n records to the file, the merge driver concatenates both sides' lines\n -- there is no smart deduplication of \"semantically similar\" records.\n (Identical line-for-line appends collapse because git's three-way\n merge sees them as the same change, but distinct records always\n survive verbatim, even if a downstream reader would consider them\n redundant.) The append-only writers in `scripts/candidates_log.py`\n mint a fresh `decision_id` per call, so genuinely duplicate records\n are not the expected case, but downstream readers MUST tolerate\n multiple records describing the same logical decision.\n- **Single-operator scope only.** This is the foundational rebase\n ergonomic for the single-operator case (operator A rebases their\n feature branch onto a master that grew while they were AFK).\n Multi-operator merge-conflict resolution is explicitly out of scope per\n #1119 R4 (tracked separately as M1-M4 in #1183).\n\n## See also\n\n- Current Shape comment on #1144 for the canonical decisions (the source\n of truth this README documents).\n- `.gitignore` -- selective gitignore entries for the operator-private\n files.\n- `.gitattributes` -- the `merge=union` rule.\n- `scripts/candidates_log.py` -- the writer for `candidates.jsonl`.\n";
12
+ /** Layout-aware triage-cache README body for the active lifecycle tree (#2344 / #2349). */
13
+ export declare function generateTriageCacheReadmeBody(projectRoot: string): string;
12
14
  /** Strip an inline `# ...` comment from a gitignore line. */
13
15
  export declare function stripGitignoreInlineComment(line: string): string;
14
16
  /** Append `.deft-cache/` to `.gitignore` when absent. */
@@ -152,6 +152,12 @@ operators should know:
152
152
  - \`.gitattributes\` -- the \`merge=union\` rule.
153
153
  - \`scripts/candidates_log.py\` -- the writer for \`candidates.jsonl\`.
154
154
  `;
155
+ /** Layout-aware triage-cache README body for the active lifecycle tree (#2344 / #2349). */
156
+ export function generateTriageCacheReadmeBody(projectRoot) {
157
+ const layout = resolveLifecycleLayout(projectRoot);
158
+ const triagePrefix = `${layout.artifactDir}/${TRIAGE_CACHE_DIR_NAME}`;
159
+ return EVAL_README_BODY.replaceAll("vbrief/.triage-cache", triagePrefix);
160
+ }
155
161
  function stepOutcome(name, ok, message, details = {}, error = null) {
156
162
  return { name, ok, message, error, details };
157
163
  }
@@ -338,7 +344,8 @@ function ensureGitattributesMergeUnion(gitattributesPath, stepName, glob, ruleLi
338
344
  gitattributes_created: true,
339
345
  });
340
346
  }
341
- function ensureEvalReadme(readmePath, readmeRel, stepName) {
347
+ function ensureEvalReadme(options) {
348
+ const { projectRoot, readmePath, readmeRel, stepName } = options;
342
349
  try {
343
350
  readFileSync(readmePath, { encoding: "utf8" });
344
351
  return stepOutcome(stepName, true, `${readmeRel} already present (no-op)`, {
@@ -351,7 +358,7 @@ function ensureEvalReadme(readmePath, readmeRel, stepName) {
351
358
  }
352
359
  try {
353
360
  mkdirSync(dirname(readmePath), { recursive: true });
354
- writeFileSync(readmePath, EVAL_README_BODY, { encoding: "utf8" });
361
+ writeFileSync(readmePath, generateTriageCacheReadmeBody(projectRoot), { encoding: "utf8" });
355
362
  }
356
363
  catch (exc) {
357
364
  return stepOutcome(stepName, false, `could not create ${readmePath}`, { readme_created: false }, String(exc));
@@ -385,7 +392,7 @@ export function stepEnsureGitignoreEvalEntries(projectRoot) {
385
392
  return stepOutcome(stepName, false, gaResult.message, details, gaResult.error ?? null);
386
393
  }
387
394
  Object.assign(details, gaResult.details);
388
- const rdResult = ensureEvalReadme(readmePath, readmeRel, stepName);
395
+ const rdResult = ensureEvalReadme({ projectRoot, readmePath, readmeRel, stepName });
389
396
  if (!rdResult.ok) {
390
397
  Object.assign(details, rdResult.details);
391
398
  return stepOutcome(stepName, false, rdResult.message, details, rdResult.error ?? null);
@@ -16,6 +16,8 @@ export interface TriageCacheMigrationResult {
16
16
  readonly migratedFiles: readonly string[];
17
17
  readonly skippedFiles: readonly string[];
18
18
  readonly migratedDirs: readonly string[];
19
+ readonly regeneratedFiles: readonly string[];
20
+ readonly removedLegacyFiles: readonly string[];
19
21
  }
20
22
  /** Absolute path to the layout-aware `.triage-cache/` directory. */
21
23
  export declare function resolveTriageCacheDir(projectRoot: string): string;
@@ -4,9 +4,10 @@
4
4
  * Triage append-only logs and scratch dirs live under `.triage-cache/` so the
5
5
  * `.eval/` namespace can be reclaimed for version-eval results (#1703 Tier 2).
6
6
  */
7
- import { existsSync, mkdirSync, renameSync } from "node:fs";
7
+ import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
8
8
  import { join, relative } from "node:path";
9
9
  import { resolveEvalDir, resolveLifecycleLayout, resolveLifecycleRoot } from "../layout/resolve.js";
10
+ import { generateTriageCacheReadmeBody } from "./bootstrap/gitignore.js";
10
11
  /** Directory name for the triage working-set cache (not version-eval results). */
11
12
  export const TRIAGE_CACHE_DIR_NAME = ".triage-cache";
12
13
  /** Legacy directory that previously held triage cache files before #1703. */
@@ -42,8 +43,10 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
42
43
  const migratedFiles = [];
43
44
  const skippedFiles = [];
44
45
  const migratedDirs = [];
46
+ const regeneratedFiles = [];
47
+ const removedLegacyFiles = [];
45
48
  if (!existsSync(legacyDir)) {
46
- return { migratedFiles, skippedFiles, migratedDirs };
49
+ return { migratedFiles, skippedFiles, migratedDirs, regeneratedFiles, removedLegacyFiles };
47
50
  }
48
51
  mkdirSync(targetDir, { recursive: true });
49
52
  for (const name of TRIAGE_CACHE_FILE_NAMES) {
@@ -52,8 +55,21 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
52
55
  if (!existsSync(legacyPath)) {
53
56
  continue;
54
57
  }
58
+ if (name === "README.md") {
59
+ if (existsSync(targetPath)) {
60
+ unlinkSync(legacyPath);
61
+ removedLegacyFiles.push(name);
62
+ }
63
+ else {
64
+ writeFileSync(targetPath, generateTriageCacheReadmeBody(projectRoot), "utf8");
65
+ unlinkSync(legacyPath);
66
+ regeneratedFiles.push(name);
67
+ }
68
+ continue;
69
+ }
55
70
  if (existsSync(targetPath)) {
56
- skippedFiles.push(name);
71
+ unlinkSync(legacyPath);
72
+ removedLegacyFiles.push(name);
57
73
  continue;
58
74
  }
59
75
  renameSync(legacyPath, targetPath);
@@ -72,7 +88,7 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
72
88
  renameSync(legacyPath, targetPath);
73
89
  migratedDirs.push(name);
74
90
  }
75
- return { migratedFiles, skippedFiles, migratedDirs };
91
+ return { migratedFiles, skippedFiles, migratedDirs, regeneratedFiles, removedLegacyFiles };
76
92
  }
77
93
  /** Resolve a path under `.triage-cache/`, migrating legacy `.eval/` files first. */
78
94
  export function resolveTriageCachePath(projectRoot, ...segments) {
@@ -91,6 +107,7 @@ export function resolveCandidatesLogPath(projectRoot) {
91
107
  }
92
108
  /** Ensure the triage cache directory exists (post-migration). */
93
109
  export function ensureTriageCacheDir(projectRoot) {
110
+ migrateLegacyTriageCacheFromEval(projectRoot);
94
111
  const dir = resolveTriageCacheDir(projectRoot);
95
112
  mkdirSync(dir, { recursive: true });
96
113
  return dir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.71.0",
3
+ "version": "0.72.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -281,8 +281,8 @@
281
281
  "provenance": true
282
282
  },
283
283
  "dependencies": {
284
- "@deftai/directive-content": "^0.71.0",
285
- "@deftai/directive-types": "^0.71.0",
284
+ "@deftai/directive-content": "^0.72.0",
285
+ "@deftai/directive-types": "^0.72.0",
286
286
  "archiver": "^8.0.0"
287
287
  },
288
288
  "scripts": {