@deftai/directive-core 0.80.0 → 0.82.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 (82) hide show
  1. package/dist/content-contracts/skills/helpers.d.ts +6 -0
  2. package/dist/content-contracts/skills/helpers.js +47 -3
  3. package/dist/content-contracts/skills/skill-external-fetch-gate.d.ts +18 -0
  4. package/dist/content-contracts/skills/skill-external-fetch-gate.js +81 -0
  5. package/dist/doctor/checks.d.ts +6 -0
  6. package/dist/doctor/checks.js +139 -0
  7. package/dist/doctor/main.js +2 -1
  8. package/dist/eval/crud-telemetry.d.ts +2 -0
  9. package/dist/eval/crud-telemetry.js +30 -0
  10. package/dist/intake/github-body-cli.js +2 -0
  11. package/dist/intake/github-body.d.ts +11 -0
  12. package/dist/intake/github-body.js +95 -24
  13. package/dist/integration-e2e/helpers.js +1 -1
  14. package/dist/issue-sync/sync-from-xbrief.js +3 -0
  15. package/dist/orphan-active/evaluate.d.ts +25 -0
  16. package/dist/orphan-active/evaluate.js +275 -0
  17. package/dist/orphan-active/index.d.ts +3 -0
  18. package/dist/orphan-active/index.js +3 -0
  19. package/dist/orphan-active/refs.d.ts +16 -0
  20. package/dist/orphan-active/refs.js +105 -0
  21. package/dist/policy/index.d.ts +1 -0
  22. package/dist/policy/index.js +19 -5
  23. package/dist/policy/product-signal.d.ts +45 -0
  24. package/dist/policy/product-signal.js +210 -0
  25. package/dist/pr-wait-mergeable/cascade.d.ts +8 -0
  26. package/dist/pr-wait-mergeable/cascade.js +18 -0
  27. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  28. package/dist/pr-wait-mergeable/main.js +44 -73
  29. package/dist/pr-wait-mergeable/result.d.ts +2 -1
  30. package/dist/pr-wait-mergeable/result.js +4 -0
  31. package/dist/pr-wait-mergeable/semantic-green.d.ts +27 -0
  32. package/dist/pr-wait-mergeable/semantic-green.js +202 -0
  33. package/dist/pr-wait-mergeable/types.d.ts +1 -0
  34. package/dist/pr-watch/constants.d.ts +4 -4
  35. package/dist/pr-watch/constants.js +4 -4
  36. package/dist/pr-watch/watch.js +5 -3
  37. package/dist/product-signal/actor-name.d.ts +18 -0
  38. package/dist/product-signal/actor-name.js +94 -0
  39. package/dist/product-signal/consent.d.ts +30 -0
  40. package/dist/product-signal/consent.js +116 -0
  41. package/dist/product-signal/gates.d.ts +17 -0
  42. package/dist/product-signal/gates.js +66 -0
  43. package/dist/product-signal/github-private-sink-adapter.d.ts +28 -0
  44. package/dist/product-signal/github-private-sink-adapter.js +274 -0
  45. package/dist/product-signal/headless.d.ts +8 -0
  46. package/dist/product-signal/headless.js +32 -0
  47. package/dist/product-signal/install-context.d.ts +13 -0
  48. package/dist/product-signal/install-context.js +41 -0
  49. package/dist/product-signal/local-signal-summary.d.ts +7 -0
  50. package/dist/product-signal/local-signal-summary.js +173 -0
  51. package/dist/product-signal/payload.d.ts +90 -0
  52. package/dist/product-signal/payload.js +124 -0
  53. package/dist/product-signal/sink-bootstrap.d.ts +29 -0
  54. package/dist/product-signal/sink-bootstrap.js +108 -0
  55. package/dist/product-signal/submit-adapter.d.ts +14 -0
  56. package/dist/product-signal/submit-adapter.js +2 -0
  57. package/dist/product-signal/submit.d.ts +54 -0
  58. package/dist/product-signal/submit.js +290 -0
  59. package/dist/release/native-steps.js +11 -4
  60. package/dist/render/framework-commands.js +6 -0
  61. package/dist/scm/gh-rest.d.ts +3 -1
  62. package/dist/scm/gh-rest.js +22 -0
  63. package/dist/scope/brief-io.d.ts +26 -0
  64. package/dist/scope/brief-io.js +77 -0
  65. package/dist/scope/decompose.js +2 -2
  66. package/dist/scope/decomposed-refs.js +3 -3
  67. package/dist/scope/demote.js +2 -2
  68. package/dist/scope/project-definition-sync.d.ts +2 -2
  69. package/dist/scope/project-definition-sync.js +15 -3
  70. package/dist/scope/registry-artifact-sync.d.ts +6 -1
  71. package/dist/scope/registry-artifact-sync.js +31 -13
  72. package/dist/scope/transition.js +23 -15
  73. package/dist/scope/undo.js +2 -2
  74. package/dist/scope/vbrief-json.d.ts +1 -2
  75. package/dist/scope/vbrief-json.js +1 -4
  76. package/dist/staleness-tickler/state.js +19 -2
  77. package/dist/triage/queue/cache.js +5 -0
  78. package/dist/verify-source/index.d.ts +1 -0
  79. package/dist/verify-source/index.js +1 -0
  80. package/dist/verify-source/skill-external-fetch-gate.d.ts +21 -0
  81. package/dist/verify-source/skill-external-fetch-gate.js +71 -0
  82. package/package.json +7 -3
@@ -0,0 +1,25 @@
1
+ import type { RunGhFn } from "../pr-protected-issues/types.js";
2
+ export type OutputStream = "stdout" | "stderr" | "none";
3
+ export interface OrphanActiveBrief {
4
+ readonly path: string;
5
+ readonly reason: string;
6
+ }
7
+ export interface EvaluateResult {
8
+ readonly code: 0 | 1 | 2;
9
+ readonly message: string;
10
+ readonly stream: OutputStream;
11
+ readonly orphans: readonly OrphanActiveBrief[];
12
+ }
13
+ export interface EvaluateOptions {
14
+ readonly quiet?: boolean;
15
+ readonly repo?: string | null;
16
+ readonly runGh?: RunGhFn;
17
+ readonly skipGh?: boolean;
18
+ }
19
+ /**
20
+ * Pure evaluator for orphaned active/running xBRIEF detection (#2321).
21
+ * Fails when active/ briefs with plan.status==running reference only closed
22
+ * issues and/or a merged PR — the stop-at:pr-open orphan signature.
23
+ */
24
+ export declare function evaluate(projectRoot: string, options?: EvaluateOptions): EvaluateResult;
25
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1,275 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join, relative, resolve } from "node:path";
3
+ import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
4
+ import { defaultRunGh } from "../pr-protected-issues/gh.js";
5
+ import { CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE } from "../triage/queue/constants.js";
6
+ import { resolveRepo } from "../triage/queue/repo.js";
7
+ import { collectGithubRefs } from "./refs.js";
8
+ function readJson(path) {
9
+ try {
10
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
11
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
12
+ ? parsed
13
+ : null;
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ function planOf(data) {
20
+ const plan = data?.plan;
21
+ return typeof plan === "object" && plan !== null && !Array.isArray(plan)
22
+ ? plan
23
+ : null;
24
+ }
25
+ function relBriefPath(path, projectRoot) {
26
+ try {
27
+ return relative(resolve(projectRoot), resolve(path)).replace(/\\/g, "/");
28
+ }
29
+ catch {
30
+ return path.replace(/\\/g, "/");
31
+ }
32
+ }
33
+ function listActiveRunningBriefs(projectRoot) {
34
+ let lifecycleRoot;
35
+ try {
36
+ lifecycleRoot = resolveLifecycleRoot(projectRoot);
37
+ }
38
+ catch {
39
+ return [];
40
+ }
41
+ const activeDir = join(lifecycleRoot, "active");
42
+ if (!existsSync(activeDir)) {
43
+ return [];
44
+ }
45
+ const out = [];
46
+ for (const entry of readdirSync(activeDir, { withFileTypes: true })) {
47
+ if (!entry.isFile() || !hasArtifactSuffix(entry.name)) {
48
+ continue;
49
+ }
50
+ const path = join(activeDir, entry.name);
51
+ const data = readJson(path);
52
+ const plan = planOf(data);
53
+ if (plan === null) {
54
+ continue;
55
+ }
56
+ if (String(plan.status ?? "").toLowerCase() !== "running") {
57
+ continue;
58
+ }
59
+ out.push({ path, plan });
60
+ }
61
+ return out.sort((a, b) => a.path.localeCompare(b.path));
62
+ }
63
+ function readCachedIssueState(projectRoot, ref) {
64
+ const [owner, name] = ref.repo.split("/", 2);
65
+ if (!owner || !name) {
66
+ return null;
67
+ }
68
+ const rawPath = join(projectRoot, CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE, owner, name, String(ref.number), "raw.json");
69
+ if (!existsSync(rawPath)) {
70
+ return null;
71
+ }
72
+ const raw = readJson(rawPath);
73
+ if (raw === null) {
74
+ return null;
75
+ }
76
+ const state = typeof raw.state === "string" ? raw.state.toLowerCase() : "";
77
+ if (state === "closed") {
78
+ return "closed";
79
+ }
80
+ if (state === "open") {
81
+ return "open";
82
+ }
83
+ return null;
84
+ }
85
+ function fetchIssueStateLive(ref, runGh) {
86
+ const path = `repos/${ref.repo}/issues/${ref.number}`;
87
+ const result = runGh(["gh", "api", path]);
88
+ if (result.returncode !== 0) {
89
+ return null;
90
+ }
91
+ try {
92
+ const payload = JSON.parse(result.stdout);
93
+ if (payload === null || typeof payload !== "object") {
94
+ return null;
95
+ }
96
+ const state = payload.state;
97
+ if (state === "closed") {
98
+ return "closed";
99
+ }
100
+ if (state === "open") {
101
+ return "open";
102
+ }
103
+ return null;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ function resolveIssueState(ref, projectRoot, runGh, skipGh) {
110
+ const cached = readCachedIssueState(projectRoot, ref);
111
+ if (cached !== null) {
112
+ return cached;
113
+ }
114
+ if (skipGh) {
115
+ return null;
116
+ }
117
+ return fetchIssueStateLive(ref, runGh);
118
+ }
119
+ function fetchPrMerged(ref, runGh) {
120
+ const path = `repos/${ref.repo}/pulls/${ref.number}`;
121
+ const result = runGh(["gh", "api", path]);
122
+ if (result.returncode !== 0) {
123
+ return null;
124
+ }
125
+ try {
126
+ const payload = JSON.parse(result.stdout);
127
+ if (payload === null || typeof payload !== "object") {
128
+ return null;
129
+ }
130
+ const mergedAt = payload.merged_at;
131
+ if (mergedAt === null) {
132
+ return false;
133
+ }
134
+ return typeof mergedAt === "string" && mergedAt.length > 0;
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ function assessOrphanSignature(issueRefs, prRefs, projectRoot, runGh, skipGh) {
141
+ for (const pr of prRefs) {
142
+ if (skipGh) {
143
+ continue;
144
+ }
145
+ const merged = fetchPrMerged(pr, runGh);
146
+ if (merged === true) {
147
+ return { orphaned: true, reason: `linked PR #${pr.number} is merged` };
148
+ }
149
+ }
150
+ if (issueRefs.length === 0) {
151
+ return { orphaned: false, reason: null };
152
+ }
153
+ let resolved = 0;
154
+ for (const ref of issueRefs) {
155
+ const state = resolveIssueState(ref, projectRoot, runGh, skipGh);
156
+ if (state === null) {
157
+ return { orphaned: false, reason: null };
158
+ }
159
+ resolved += 1;
160
+ if (state !== "closed") {
161
+ return { orphaned: false, reason: null };
162
+ }
163
+ }
164
+ if (resolved > 0) {
165
+ return { orphaned: true, reason: "all referenced issues are closed" };
166
+ }
167
+ return { orphaned: false, reason: null };
168
+ }
169
+ function formatRefusal(orphans, projectRoot) {
170
+ const lines = [
171
+ `verify:orphan-active: ${orphans.length} active/running xBRIEF${orphans.length === 1 ? "" : "s"} look shipped but still consume WIP (project_root=${projectRoot}).`,
172
+ " Remediation: move each brief out of active/ with lifecycle ownership:",
173
+ " task scope:complete -- xbrief/active/<file>.xbrief.json",
174
+ " task scope:cancel -- xbrief/active/<file>.xbrief.json # when abandoning",
175
+ " For stop-at:pr-open workers the orchestrator owns post-merge complete/cancel;",
176
+ " for drive-to:merge-ready workers scope:complete is part of the worker unit (#2321).",
177
+ " Or run task swarm:finalize-cohort / task swarm:complete-cohort after cohort merge.",
178
+ " Offending briefs:",
179
+ ];
180
+ for (const orphan of orphans) {
181
+ lines.push(` - ${orphan.path} (${orphan.reason})`);
182
+ }
183
+ return lines.join("\n");
184
+ }
185
+ /**
186
+ * Pure evaluator for orphaned active/running xBRIEF detection (#2321).
187
+ * Fails when active/ briefs with plan.status==running reference only closed
188
+ * issues and/or a merged PR — the stop-at:pr-open orphan signature.
189
+ */
190
+ export function evaluate(projectRoot, options = {}) {
191
+ const root = resolve(projectRoot);
192
+ const quiet = options.quiet ?? false;
193
+ const skipGh = options.skipGh ?? false;
194
+ const runGh = options.runGh ?? defaultRunGh;
195
+ const defaultRepo = resolveRepo(options.repo, root);
196
+ if (!existsSync(root)) {
197
+ return {
198
+ code: 2,
199
+ message: `verify:orphan-active: project root does not exist: ${root}`,
200
+ stream: "stderr",
201
+ orphans: [],
202
+ };
203
+ }
204
+ let lifecycleRoot;
205
+ try {
206
+ lifecycleRoot = resolveLifecycleRoot(root);
207
+ }
208
+ catch (err) {
209
+ const message = err instanceof Error ? err.message : String(err);
210
+ // Consumer/greenfield fixtures may still use legacy vbrief/ only (#2112).
211
+ // Orphan detection applies only to xbrief/active/ — skip cleanly, not config fail.
212
+ if (message.includes("No xbrief/ layout found")) {
213
+ if (quiet) {
214
+ return { code: 0, message: "", stream: "none", orphans: [] };
215
+ }
216
+ return {
217
+ code: 0,
218
+ message: "verify:orphan-active: no xbrief/ lifecycle root; nothing to scan.",
219
+ stream: "stdout",
220
+ orphans: [],
221
+ };
222
+ }
223
+ return {
224
+ code: 2,
225
+ message: `verify:orphan-active: ${message}`,
226
+ stream: "stderr",
227
+ orphans: [],
228
+ };
229
+ }
230
+ if (!existsSync(lifecycleRoot)) {
231
+ if (quiet) {
232
+ return { code: 0, message: "", stream: "none", orphans: [] };
233
+ }
234
+ return {
235
+ code: 0,
236
+ message: "verify:orphan-active: no xbrief/ lifecycle root; nothing to scan.",
237
+ stream: "stdout",
238
+ orphans: [],
239
+ };
240
+ }
241
+ const orphans = [];
242
+ for (const brief of listActiveRunningBriefs(root)) {
243
+ const { issues, prs } = collectGithubRefs(brief.plan, defaultRepo);
244
+ if (issues.length === 0 && prs.length === 0) {
245
+ continue;
246
+ }
247
+ const assessment = assessOrphanSignature(issues, prs, root, runGh, skipGh);
248
+ if (assessment.orphaned && assessment.reason !== null) {
249
+ orphans.push({
250
+ path: relBriefPath(brief.path, root),
251
+ reason: assessment.reason,
252
+ });
253
+ }
254
+ }
255
+ if (orphans.length > 0) {
256
+ return {
257
+ code: 1,
258
+ message: formatRefusal(orphans, root),
259
+ stream: "stderr",
260
+ orphans,
261
+ };
262
+ }
263
+ if (quiet) {
264
+ return { code: 0, message: "", stream: "none", orphans: [] };
265
+ }
266
+ const scanned = listActiveRunningBriefs(root).length;
267
+ return {
268
+ code: 0,
269
+ message: `verify:orphan-active: no orphaned active/running xBRIEFs ` +
270
+ `(scanned ${scanned} running brief${scanned === 1 ? "" : "s"} in active/).`,
271
+ stream: "stdout",
272
+ orphans: [],
273
+ };
274
+ }
275
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1,3 @@
1
+ export * from "./evaluate.js";
2
+ export * from "./refs.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export * from "./evaluate.js";
2
+ export * from "./refs.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ export interface IssueRef {
2
+ readonly repo: string;
3
+ readonly number: number;
4
+ }
5
+ export interface PrRef {
6
+ readonly repo: string;
7
+ readonly number: number;
8
+ }
9
+ /** Parse (repo, pr_number) from a github-pr reference URI. */
10
+ export declare function parseGithubPrUri(uri: unknown): [string | null, number | null];
11
+ /** Collect GitHub issue and PR refs from a scope plan object. */
12
+ export declare function collectGithubRefs(plan: Record<string, unknown>, defaultRepo: string | null): {
13
+ issues: IssueRef[];
14
+ prs: PrRef[];
15
+ };
16
+ //# sourceMappingURL=refs.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { referenceTypeMatches } from "@deftai/directive-types";
2
+ import { parseGithubIssueUri } from "../triage/reconcile/parse-uri.js";
3
+ /** Parse (repo, pr_number) from a github-pr reference URI. */
4
+ export function parseGithubPrUri(uri) {
5
+ if (typeof uri !== "string") {
6
+ return [null, null];
7
+ }
8
+ const cleaned = uri.trim().replace(/\/$/, "");
9
+ if (!cleaned) {
10
+ return [null, null];
11
+ }
12
+ const noScheme = cleaned.includes("://") ? cleaned.split("://").slice(1).join("://") : cleaned;
13
+ const parts = noScheme.split("/").filter(Boolean);
14
+ if (parts.length >= 4 && parts[parts.length - 2] === "pull") {
15
+ const tail = parts[parts.length - 1] ?? "";
16
+ if (/^\d+$/.test(tail)) {
17
+ const owner = parts[parts.length - 4];
18
+ const repo = parts[parts.length - 3];
19
+ if (owner && repo) {
20
+ return [`${owner}/${repo}`, Number(tail)];
21
+ }
22
+ }
23
+ }
24
+ const tail = parts[parts.length - 1] ?? "";
25
+ if (/^\d+$/.test(tail)) {
26
+ return [null, Number(tail)];
27
+ }
28
+ return [null, null];
29
+ }
30
+ function parseTrackingIssue(value) {
31
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
32
+ return value;
33
+ }
34
+ if (typeof value !== "string") {
35
+ return null;
36
+ }
37
+ const match = value.match(/#(\d+)/);
38
+ return match ? Number(match[1]) : null;
39
+ }
40
+ function addIssueRef(out, seen, repo, number, defaultRepo) {
41
+ if (number === null) {
42
+ return;
43
+ }
44
+ const resolvedRepo = repo ?? defaultRepo;
45
+ if (resolvedRepo === null || resolvedRepo.length === 0) {
46
+ return;
47
+ }
48
+ const key = `${resolvedRepo}:${number}`;
49
+ if (seen.has(key)) {
50
+ return;
51
+ }
52
+ seen.add(key);
53
+ out.push({ repo: resolvedRepo, number });
54
+ }
55
+ function addPrRef(out, seen, repo, number, defaultRepo) {
56
+ if (number === null) {
57
+ return;
58
+ }
59
+ const resolvedRepo = repo ?? defaultRepo;
60
+ if (resolvedRepo === null || resolvedRepo.length === 0) {
61
+ return;
62
+ }
63
+ const key = `${resolvedRepo}:${number}`;
64
+ if (seen.has(key)) {
65
+ return;
66
+ }
67
+ seen.add(key);
68
+ out.push({ repo: resolvedRepo, number });
69
+ }
70
+ /** Collect GitHub issue and PR refs from a scope plan object. */
71
+ export function collectGithubRefs(plan, defaultRepo) {
72
+ const issues = [];
73
+ const prs = [];
74
+ const seenIssues = new Set();
75
+ const seenPrs = new Set();
76
+ const refs = plan.references;
77
+ if (Array.isArray(refs)) {
78
+ for (const ref of refs) {
79
+ if (typeof ref !== "object" || ref === null || Array.isArray(ref)) {
80
+ continue;
81
+ }
82
+ const typed = ref;
83
+ const type = String(typed.type ?? "");
84
+ if (referenceTypeMatches(type, "github-issue")) {
85
+ const [repo, number] = parseGithubIssueUri(typed.uri);
86
+ addIssueRef(issues, seenIssues, repo, number, defaultRepo);
87
+ }
88
+ else if (referenceTypeMatches(type, "github-pr")) {
89
+ const [repo, number] = parseGithubPrUri(typed.uri);
90
+ addPrRef(prs, seenPrs, repo, number, defaultRepo);
91
+ }
92
+ }
93
+ }
94
+ const metadata = plan.metadata;
95
+ if (typeof metadata === "object" && metadata !== null && !Array.isArray(metadata)) {
96
+ const tracking = metadata["x-tracking"];
97
+ if (typeof tracking === "object" && tracking !== null && !Array.isArray(tracking)) {
98
+ const t = tracking;
99
+ addIssueRef(issues, seenIssues, defaultRepo, parseTrackingIssue(t.parent_issue), defaultRepo);
100
+ addIssueRef(issues, seenIssues, defaultRepo, parseTrackingIssue(t.decomposition_origin), defaultRepo);
101
+ }
102
+ }
103
+ return { issues, prs };
104
+ }
105
+ //# sourceMappingURL=refs.js.map
@@ -5,6 +5,7 @@ export * from "./decisions.js";
5
5
  export * from "./disclosure.js";
6
6
  export * from "./plan-extensions.js";
7
7
  export * from "./policy-invocation.js";
8
+ export * from "./product-signal.js";
8
9
  export * from "./resolve.js";
9
10
  export * from "./runtime-authority.js";
10
11
  export * from "./staleness-tickler.js";
@@ -1,4 +1,5 @@
1
1
  import { readPlanPolicy } from "./plan-extensions.js";
2
+ import { FIELD_PRODUCT_SIGNAL, FIELD_PRODUCT_SIGNAL_CLI_ALIAS, inspectProductSignal, } from "./product-signal.js";
2
3
  import { coerceLegacyNarrative, LEGACY_NARRATIVE_KEY, loadProjectDefinition } from "./resolve.js";
3
4
  import { FIELD_RUNTIME_AUTHORITY, FIELD_RUNTIME_AUTHORITY_CLI_ALIAS, inspectRuntimeAuthority, } from "./runtime-authority.js";
4
5
  import { FIELD_STALENESS_TICKLER, FIELD_STALENESS_TICKLER_CLI_ALIAS, inspectStalenessTickler, } from "./staleness-tickler.js";
@@ -11,6 +12,7 @@ export * from "./decisions.js";
11
12
  export * from "./disclosure.js";
12
13
  export * from "./plan-extensions.js";
13
14
  export * from "./policy-invocation.js";
15
+ export * from "./product-signal.js";
14
16
  export * from "./resolve.js";
15
17
  export * from "./runtime-authority.js";
16
18
  export * from "./staleness-tickler.js";
@@ -220,6 +222,15 @@ function inspectSwarmSubagentBackend(data) {
220
222
  source: "default-on-error",
221
223
  };
222
224
  }
225
+ function inspectProductSignalField(data, projectRoot) {
226
+ const field = inspectProductSignal(data, projectRoot);
227
+ return {
228
+ name: field.name,
229
+ current: field.current,
230
+ default: field.default,
231
+ source: field.source,
232
+ };
233
+ }
223
234
  function inspectValueFeedbackField(data, projectRoot) {
224
235
  const field = inspectValueFeedback(data, projectRoot);
225
236
  return {
@@ -261,6 +272,7 @@ const REGISTERED_POLICIES = [
261
272
  inspectSwarmSubagentBackend,
262
273
  inspectStalenessTicklerField,
263
274
  inspectRuntimeAuthorityField,
275
+ inspectProductSignalField,
264
276
  inspectValueFeedbackField,
265
277
  ];
266
278
  /** Walk registered inspectors and return one row per field (#1148). */
@@ -272,11 +284,13 @@ export function inspectAllPolicies(projectRoot) {
272
284
  export function inspectOnePolicy(name, projectRoot) {
273
285
  const normalized = name === FIELD_VALUE_FEEDBACK_CLI_ALIAS
274
286
  ? FIELD_VALUE_FEEDBACK
275
- : name === FIELD_STALENESS_TICKLER_CLI_ALIAS
276
- ? FIELD_STALENESS_TICKLER
277
- : name === FIELD_RUNTIME_AUTHORITY_CLI_ALIAS
278
- ? FIELD_RUNTIME_AUTHORITY
279
- : name;
287
+ : name === FIELD_PRODUCT_SIGNAL_CLI_ALIAS
288
+ ? FIELD_PRODUCT_SIGNAL
289
+ : name === FIELD_STALENESS_TICKLER_CLI_ALIAS
290
+ ? FIELD_STALENESS_TICKLER
291
+ : name === FIELD_RUNTIME_AUTHORITY_CLI_ALIAS
292
+ ? FIELD_RUNTIME_AUTHORITY
293
+ : name;
280
294
  for (const field of inspectAllPolicies(projectRoot)) {
281
295
  if (field.name === normalized)
282
296
  return field;
@@ -0,0 +1,45 @@
1
+ /** Canonical registered policy field name (#2693). */
2
+ export declare const FIELD_PRODUCT_SIGNAL = "plan.policy.productSignal";
3
+ /** Short alias for `policy:show --field=productSignal`. */
4
+ export declare const FIELD_PRODUCT_SIGNAL_CLI_ALIAS = "productSignal";
5
+ export declare const DEFAULT_PRODUCT_SIGNAL_ENABLED = false;
6
+ /** Baked-in default private sink (#2693 D6). */
7
+ export declare const DEFAULT_PRODUCT_SIGNAL_SINK_REPO = "deftai/product-signal";
8
+ export interface ProductSignalConfig {
9
+ readonly enabled: boolean;
10
+ readonly sinkRepo: string;
11
+ }
12
+ export type ProductSignalSource = "typed" | "default" | "default-on-error";
13
+ export interface ProductSignalResolved extends ProductSignalConfig {
14
+ readonly source: ProductSignalSource;
15
+ readonly error: string | null;
16
+ }
17
+ export declare const PRODUCT_SIGNAL_CAPABILITY_COST_DISCLOSURE: string;
18
+ /** Validate a `plan.policy.productSignal` payload. */
19
+ export declare function validateProductSignal(value: unknown): string[];
20
+ /** Resolve `plan.policy.productSignal` from PROJECT-DEFINITION (#2693). */
21
+ export declare function resolveProductSignal(projectRoot: string): ProductSignalResolved;
22
+ /** Human-readable status line for CLI surfaces. */
23
+ export declare function formatProductSignalStatusLine(policy: ProductSignalResolved): string;
24
+ export interface ProductSignalPolicyField {
25
+ readonly name: typeof FIELD_PRODUCT_SIGNAL;
26
+ readonly current: ProductSignalConfig;
27
+ readonly default: ProductSignalConfig;
28
+ readonly source: string;
29
+ }
30
+ /** Inspector row for `policy:show --field=productSignal`. */
31
+ export declare function inspectProductSignal(data: Record<string, unknown> | null, projectRoot?: string): ProductSignalPolicyField;
32
+ export interface EnableProductSignalOptions {
33
+ readonly confirm: boolean;
34
+ readonly actor?: string;
35
+ readonly note?: string;
36
+ readonly sinkRepo?: string;
37
+ }
38
+ export interface EnableProductSignalResult {
39
+ readonly exitCode: 0 | 1 | 2;
40
+ readonly stdout: string;
41
+ readonly changed: boolean;
42
+ }
43
+ /** Persist `productSignal.enabled=true` after capability-cost disclosure (#2693). */
44
+ export declare function enableProductSignal(projectRoot: string, options: EnableProductSignalOptions): EnableProductSignalResult;
45
+ //# sourceMappingURL=product-signal.d.ts.map