@deftai/directive-core 0.79.4 → 0.81.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 (84) hide show
  1. package/dist/doctor/checks.d.ts +6 -0
  2. package/dist/doctor/checks.js +139 -0
  3. package/dist/doctor/main.js +2 -1
  4. package/dist/hooks/dispatcher.d.ts +18 -3
  5. package/dist/hooks/dispatcher.js +173 -62
  6. package/dist/hooks/readonly.d.ts +7 -0
  7. package/dist/hooks/readonly.js +83 -0
  8. package/dist/hooks/tools.d.ts +7 -0
  9. package/dist/hooks/tools.js +16 -0
  10. package/dist/init-deposit/agent-hooks.d.ts +4 -1
  11. package/dist/init-deposit/agent-hooks.js +77 -21
  12. package/dist/policy/index.d.ts +3 -0
  13. package/dist/policy/index.js +45 -1
  14. package/dist/policy/product-signal.d.ts +45 -0
  15. package/dist/policy/product-signal.js +210 -0
  16. package/dist/policy/runtime-authority.d.ts +54 -0
  17. package/dist/policy/runtime-authority.js +180 -0
  18. package/dist/policy/staleness-tickler.d.ts +20 -0
  19. package/dist/policy/staleness-tickler.js +151 -0
  20. package/dist/pr-merge-readiness/ci-gate.d.ts +7 -1
  21. package/dist/pr-merge-readiness/ci-gate.js +38 -5
  22. package/dist/pr-merge-readiness/gh.d.ts +4 -0
  23. package/dist/pr-merge-readiness/gh.js +10 -3
  24. package/dist/pr-merge-readiness/index.d.ts +2 -1
  25. package/dist/pr-merge-readiness/index.js +2 -1
  26. package/dist/pr-merge-readiness/runner-capacity-stall.d.ts +35 -0
  27. package/dist/pr-merge-readiness/runner-capacity-stall.js +46 -0
  28. package/dist/pr-watch/constants.d.ts +5 -0
  29. package/dist/pr-watch/constants.js +6 -1
  30. package/dist/pr-watch/main.js +8 -0
  31. package/dist/pr-watch/probe.js +8 -0
  32. package/dist/pr-watch/types.d.ts +7 -0
  33. package/dist/pr-watch/watch.js +8 -1
  34. package/dist/product-signal/actor-name.d.ts +18 -0
  35. package/dist/product-signal/actor-name.js +94 -0
  36. package/dist/product-signal/consent.d.ts +30 -0
  37. package/dist/product-signal/consent.js +116 -0
  38. package/dist/product-signal/gates.d.ts +17 -0
  39. package/dist/product-signal/gates.js +66 -0
  40. package/dist/product-signal/github-private-sink-adapter.d.ts +28 -0
  41. package/dist/product-signal/github-private-sink-adapter.js +274 -0
  42. package/dist/product-signal/headless.d.ts +8 -0
  43. package/dist/product-signal/headless.js +32 -0
  44. package/dist/product-signal/install-context.d.ts +13 -0
  45. package/dist/product-signal/install-context.js +41 -0
  46. package/dist/product-signal/local-signal-summary.d.ts +7 -0
  47. package/dist/product-signal/local-signal-summary.js +173 -0
  48. package/dist/product-signal/payload.d.ts +90 -0
  49. package/dist/product-signal/payload.js +124 -0
  50. package/dist/product-signal/sink-bootstrap.d.ts +29 -0
  51. package/dist/product-signal/sink-bootstrap.js +108 -0
  52. package/dist/product-signal/submit-adapter.d.ts +14 -0
  53. package/dist/product-signal/submit-adapter.js +2 -0
  54. package/dist/product-signal/submit.d.ts +54 -0
  55. package/dist/product-signal/submit.js +290 -0
  56. package/dist/scm/gh-rest.d.ts +3 -1
  57. package/dist/scm/gh-rest.js +22 -0
  58. package/dist/scope/main.js +12 -0
  59. package/dist/session/release-availability.d.ts +21 -0
  60. package/dist/session/release-availability.js +109 -0
  61. package/dist/session/ritual-sentinel.d.ts +14 -0
  62. package/dist/session/ritual-sentinel.js +28 -0
  63. package/dist/session/session-start.d.ts +10 -0
  64. package/dist/session/session-start.js +18 -0
  65. package/dist/staleness-tickler/escalation.d.ts +14 -0
  66. package/dist/staleness-tickler/escalation.js +106 -0
  67. package/dist/staleness-tickler/idle.d.ts +18 -0
  68. package/dist/staleness-tickler/idle.js +47 -0
  69. package/dist/staleness-tickler/index.d.ts +8 -0
  70. package/dist/staleness-tickler/index.js +8 -0
  71. package/dist/staleness-tickler/probe-directive.d.ts +32 -0
  72. package/dist/staleness-tickler/probe-directive.js +106 -0
  73. package/dist/staleness-tickler/probe-xbrief.d.ts +12 -0
  74. package/dist/staleness-tickler/probe-xbrief.js +103 -0
  75. package/dist/staleness-tickler/run.d.ts +31 -0
  76. package/dist/staleness-tickler/run.js +227 -0
  77. package/dist/staleness-tickler/state.d.ts +10 -0
  78. package/dist/staleness-tickler/state.js +46 -0
  79. package/dist/staleness-tickler/types.d.ts +85 -0
  80. package/dist/staleness-tickler/types.js +5 -0
  81. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  82. package/dist/vbrief-validate/plan-hooks.js +50 -0
  83. package/dist/verify-env/agent-hooks.js +4 -2
  84. package/package.json +3 -3
@@ -1,15 +1,18 @@
1
1
  import type { HookHost } from "../hooks/dispatcher.js";
2
2
  import type { InitDepositIo } from "./constants.js";
3
- export declare const DIRECT_WRITE_HOOK_MATCHER: string;
3
+ export { DIRECT_WRITE_HOOK_MATCHER, SPAWN_HOOK_MATCHER } from "../hooks/tools.js";
4
4
  export declare const DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
5
5
  export declare const AGENT_HOOK_PATHS: readonly [".claude/settings.json", ".grok/hooks/deft.json", ".cursor/hooks.json", ".codex/hooks.json"];
6
6
  export type AgentHookPath = (typeof AGENT_HOOK_PATHS)[number];
7
7
  export type AgentHookRegistrationStatus = "healthy" | "missing" | "drifted";
8
+ /** Whether the host receives a compact/resume hook deposit (#2113). */
9
+ export type AgentHookCompactSupport = "deposited" | "unsupported";
8
10
  export interface AgentHookInspection {
9
11
  readonly host: HookHost;
10
12
  readonly path: AgentHookPath;
11
13
  readonly status: AgentHookRegistrationStatus;
12
14
  readonly detail: string;
15
+ readonly compactSupport: AgentHookCompactSupport;
13
16
  }
14
17
  export interface AgentHookDepositResult {
15
18
  readonly changed: boolean;
@@ -1,8 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { assertDepositContained } from "../deposit/contain.js";
4
- import { DIRECT_WRITE_TOOL_NAMES } from "../hooks/tools.js";
5
- export const DIRECT_WRITE_HOOK_MATCHER = DIRECT_WRITE_TOOL_NAMES.join("|");
4
+ import { DIRECT_WRITE_HOOK_MATCHER, SPAWN_HOOK_MATCHER } from "../hooks/tools.js";
5
+ export { DIRECT_WRITE_HOOK_MATCHER, SPAWN_HOOK_MATCHER } from "../hooks/tools.js";
6
6
  export const DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
7
7
  export const AGENT_HOOK_PATHS = [
8
8
  ".claude/settings.json",
@@ -67,9 +67,9 @@ function isManagedCursorEntry(value) {
67
67
  const entry = object(value);
68
68
  return typeof entry?.command === "string" && entry.command.includes(DEFT_HOOK_COMMAND_MARKER);
69
69
  }
70
- function nestedGroup(host, event) {
70
+ function nestedGroup(host, event, matcher) {
71
71
  return {
72
- ...(event === "tool.before" ? { matcher: DIRECT_WRITE_HOOK_MATCHER } : {}),
72
+ ...(event === "tool.before" && matcher !== undefined ? { matcher } : {}),
73
73
  hooks: [
74
74
  {
75
75
  type: "command",
@@ -79,18 +79,29 @@ function nestedGroup(host, event) {
79
79
  ],
80
80
  };
81
81
  }
82
- function mergeNestedConfig(config, path, host) {
82
+ function mergeNestedConfig(config, path, host, options = {}) {
83
83
  const hooks = hooksObject(config, path);
84
84
  const session = eventArray(hooks, "SessionStart", path).filter((entry) => !isManagedNestedGroup(entry));
85
85
  const preTool = eventArray(hooks, "PreToolUse", path).filter((entry) => !isManagedNestedGroup(entry));
86
86
  hooks.SessionStart = [...session, nestedGroup(host, "session.start")];
87
- hooks.PreToolUse = [...preTool, nestedGroup(host, "tool.before")];
87
+ hooks.PreToolUse = [
88
+ ...preTool,
89
+ nestedGroup(host, "tool.before", DIRECT_WRITE_HOOK_MATCHER),
90
+ nestedGroup(host, "tool.before", SPAWN_HOOK_MATCHER),
91
+ ];
92
+ if (options.compact) {
93
+ const preCompact = eventArray(hooks, "PreCompact", path).filter((entry) => !isManagedNestedGroup(entry));
94
+ const postCompact = eventArray(hooks, "PostCompact", path).filter((entry) => !isManagedNestedGroup(entry));
95
+ hooks.PreCompact = [...preCompact, nestedGroup(host, "session.compact")];
96
+ hooks.PostCompact = [...postCompact, nestedGroup(host, "session.compact")];
97
+ }
88
98
  return { ...config, hooks };
89
99
  }
90
100
  function mergeCursorConfig(config, path) {
91
101
  const hooks = hooksObject(config, path);
92
102
  const session = eventArray(hooks, "sessionStart", path).filter((entry) => !isManagedCursorEntry(entry));
93
103
  const preTool = eventArray(hooks, "preToolUse", path).filter((entry) => !isManagedCursorEntry(entry));
104
+ const preCompact = eventArray(hooks, "preCompact", path).filter((entry) => !isManagedCursorEntry(entry));
94
105
  hooks.sessionStart = [...session, { command: command("cursor", "session.start"), timeout: 5 }];
95
106
  hooks.preToolUse = [
96
107
  ...preTool,
@@ -100,7 +111,14 @@ function mergeCursorConfig(config, path) {
100
111
  failClosed: true,
101
112
  timeout: 5,
102
113
  },
114
+ {
115
+ command: command("cursor", "tool.before"),
116
+ matcher: SPAWN_HOOK_MATCHER,
117
+ failClosed: true,
118
+ timeout: 5,
119
+ },
103
120
  ];
121
+ hooks.preCompact = [...preCompact, { command: command("cursor", "session.compact"), timeout: 5 }];
104
122
  return { ...config, version: 1, hooks };
105
123
  }
106
124
  function writeJsonIfChanged(path, payload) {
@@ -119,16 +137,16 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
119
137
  const definitions = [
120
138
  {
121
139
  path: AGENT_HOOK_PATHS[0],
122
- merge: (config, path) => mergeNestedConfig(config, path, "claude"),
140
+ merge: (config, path) => mergeNestedConfig(config, path, "claude", { compact: true }),
123
141
  },
124
142
  {
125
143
  path: AGENT_HOOK_PATHS[1],
126
- merge: (config, path) => mergeNestedConfig(config, path, "grok"),
144
+ merge: (config, path) => mergeNestedConfig(config, path, "grok", { compact: true }),
127
145
  },
128
146
  { path: AGENT_HOOK_PATHS[2], merge: mergeCursorConfig },
129
147
  {
130
148
  path: AGENT_HOOK_PATHS[3],
131
- merge: (config, path) => mergeNestedConfig(config, path, "codex"),
149
+ merge: (config, path) => mergeNestedConfig(config, path, "codex", { compact: false }),
132
150
  },
133
151
  ];
134
152
  const prepared = definitions.map((definition) => {
@@ -150,7 +168,7 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
150
168
  }
151
169
  return { changed: changedPaths.length > 0, changedPaths };
152
170
  }
153
- function hasNestedRegistration(config, host) {
171
+ function hasNestedRegistration(config, host, options = {}) {
154
172
  const hooks = object(config.hooks);
155
173
  if (hooks === null)
156
174
  return false;
@@ -158,11 +176,24 @@ function hasNestedRegistration(config, host) {
158
176
  const preTool = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
159
177
  const sessionCommand = command(host, "session.start");
160
178
  const toolCommand = command(host, "tool.before");
161
- return (session.some((entry) => nestedCommands(entry).includes(sessionCommand)) &&
179
+ const compactCommand = command(host, "session.compact");
180
+ const base = session.some((entry) => nestedCommands(entry).includes(sessionCommand)) &&
162
181
  preTool.some((entry) => {
163
182
  const group = object(entry);
164
183
  return (group?.matcher === DIRECT_WRITE_HOOK_MATCHER && nestedCommands(entry).includes(toolCommand));
165
- }));
184
+ }) &&
185
+ preTool.some((entry) => {
186
+ const group = object(entry);
187
+ return group?.matcher === SPAWN_HOOK_MATCHER && nestedCommands(entry).includes(toolCommand);
188
+ });
189
+ if (!base)
190
+ return false;
191
+ if (!options.compact)
192
+ return true;
193
+ const preCompact = Array.isArray(hooks.PreCompact) ? hooks.PreCompact : [];
194
+ const postCompact = Array.isArray(hooks.PostCompact) ? hooks.PostCompact : [];
195
+ return (preCompact.some((entry) => nestedCommands(entry).includes(compactCommand)) &&
196
+ postCompact.some((entry) => nestedCommands(entry).includes(compactCommand)));
166
197
  }
167
198
  function hasCursorRegistration(config) {
168
199
  const hooks = object(config.hooks);
@@ -170,13 +201,21 @@ function hasCursorRegistration(config) {
170
201
  return false;
171
202
  const session = Array.isArray(hooks.sessionStart) ? hooks.sessionStart : [];
172
203
  const preTool = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
204
+ const preCompact = Array.isArray(hooks.preCompact) ? hooks.preCompact : [];
173
205
  return (session.some((entry) => object(entry)?.command === command("cursor", "session.start")) &&
174
206
  preTool.some((entry) => {
175
207
  const hook = object(entry);
176
208
  return (hook?.command === command("cursor", "tool.before") &&
177
209
  hook.matcher === DIRECT_WRITE_HOOK_MATCHER &&
178
210
  hook.failClosed === true);
179
- }));
211
+ }) &&
212
+ preTool.some((entry) => {
213
+ const hook = object(entry);
214
+ return (hook?.command === command("cursor", "tool.before") &&
215
+ hook.matcher === SPAWN_HOOK_MATCHER &&
216
+ hook.failClosed === true);
217
+ }) &&
218
+ preCompact.some((entry) => object(entry)?.command === command("cursor", "session.compact")));
180
219
  }
181
220
  /** Read-only registration probe shared by verify and doctor. */
182
221
  export function inspectAgentHookDeposit(projectRoot) {
@@ -184,28 +223,40 @@ export function inspectAgentHookDeposit(projectRoot) {
184
223
  {
185
224
  host: "claude",
186
225
  path: AGENT_HOOK_PATHS[0],
187
- valid: (config) => hasNestedRegistration(config, "claude"),
226
+ compactSupport: "deposited",
227
+ valid: (config) => hasNestedRegistration(config, "claude", { compact: true }),
188
228
  },
189
229
  {
190
230
  host: "grok",
191
231
  path: AGENT_HOOK_PATHS[1],
192
- valid: (config) => hasNestedRegistration(config, "grok"),
232
+ compactSupport: "deposited",
233
+ valid: (config) => hasNestedRegistration(config, "grok", { compact: true }),
234
+ },
235
+ {
236
+ host: "cursor",
237
+ path: AGENT_HOOK_PATHS[2],
238
+ compactSupport: "deposited",
239
+ valid: hasCursorRegistration,
193
240
  },
194
- { host: "cursor", path: AGENT_HOOK_PATHS[2], valid: hasCursorRegistration },
195
241
  {
196
242
  host: "codex",
197
243
  path: AGENT_HOOK_PATHS[3],
198
- valid: (config) => hasNestedRegistration(config, "codex"),
244
+ compactSupport: "unsupported",
245
+ valid: (config) => hasNestedRegistration(config, "codex", { compact: false }),
199
246
  },
200
247
  ];
201
248
  return definitions.map((definition) => {
202
249
  const absolute = join(projectRoot, definition.path);
250
+ const compactNote = definition.compactSupport === "unsupported"
251
+ ? " Compact re-arm is not deposited for Codex (no native compact hook surface)."
252
+ : " PreCompact/PostCompact or preCompact compact re-arm is deposited.";
203
253
  if (!existsSync(absolute)) {
204
254
  return {
205
255
  host: definition.host,
206
256
  path: definition.path,
207
257
  status: "missing",
208
- detail: `${definition.path} is missing.`,
258
+ compactSupport: definition.compactSupport,
259
+ detail: `${definition.path} is missing.${compactNote}`,
209
260
  };
210
261
  }
211
262
  try {
@@ -215,14 +266,18 @@ export function inspectAgentHookDeposit(projectRoot) {
215
266
  host: definition.host,
216
267
  path: definition.path,
217
268
  status: "healthy",
218
- detail: "SessionStart and direct-write PreToolUse registrations are current.",
269
+ compactSupport: definition.compactSupport,
270
+ detail: "SessionStart, direct-write + spawn PreToolUse, and compact re-arm registrations are current." +
271
+ (definition.compactSupport === "unsupported" ? compactNote : ""),
219
272
  };
220
273
  }
221
274
  return {
222
275
  host: definition.host,
223
276
  path: definition.path,
224
277
  status: "drifted",
225
- detail: "Directive SessionStart or direct-write PreToolUse registration is missing/drifted.",
278
+ compactSupport: definition.compactSupport,
279
+ detail: "Directive SessionStart, direct-write/spawn PreToolUse, or compact re-arm registration is missing/drifted." +
280
+ compactNote,
226
281
  };
227
282
  }
228
283
  catch (cause) {
@@ -230,7 +285,8 @@ export function inspectAgentHookDeposit(projectRoot) {
230
285
  host: definition.host,
231
286
  path: definition.path,
232
287
  status: "drifted",
233
- detail: String(cause),
288
+ compactSupport: definition.compactSupport,
289
+ detail: `${String(cause)}${compactNote}`,
234
290
  };
235
291
  }
236
292
  });
@@ -5,7 +5,10 @@ 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";
10
+ export * from "./runtime-authority.js";
11
+ export * from "./staleness-tickler.js";
9
12
  export * from "./value-feedback.js";
10
13
  export * from "./wip.js";
11
14
  export declare const FIELD_ALLOW_DIRECT_COMMITS = "plan.policy.allowDirectCommitsToMaster";
@@ -1,5 +1,8 @@
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";
4
+ import { FIELD_RUNTIME_AUTHORITY, FIELD_RUNTIME_AUTHORITY_CLI_ALIAS, inspectRuntimeAuthority, } from "./runtime-authority.js";
5
+ import { FIELD_STALENESS_TICKLER, FIELD_STALENESS_TICKLER_CLI_ALIAS, inspectStalenessTickler, } from "./staleness-tickler.js";
3
6
  import { FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, inspectValueFeedback, } from "./value-feedback.js";
4
7
  import { DEFAULT_WIP_CAP } from "./wip.js";
5
8
  export * from "./agents-md-advisory.js";
@@ -9,7 +12,10 @@ export * from "./decisions.js";
9
12
  export * from "./disclosure.js";
10
13
  export * from "./plan-extensions.js";
11
14
  export * from "./policy-invocation.js";
15
+ export * from "./product-signal.js";
12
16
  export * from "./resolve.js";
17
+ export * from "./runtime-authority.js";
18
+ export * from "./staleness-tickler.js";
13
19
  export * from "./value-feedback.js";
14
20
  export * from "./wip.js";
15
21
  export const FIELD_ALLOW_DIRECT_COMMITS = "plan.policy.allowDirectCommitsToMaster";
@@ -216,6 +222,15 @@ function inspectSwarmSubagentBackend(data) {
216
222
  source: "default-on-error",
217
223
  };
218
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
+ }
219
234
  function inspectValueFeedbackField(data, projectRoot) {
220
235
  const field = inspectValueFeedback(data, projectRoot);
221
236
  return {
@@ -225,6 +240,24 @@ function inspectValueFeedbackField(data, projectRoot) {
225
240
  source: field.source,
226
241
  };
227
242
  }
243
+ function inspectStalenessTicklerField(data) {
244
+ const field = inspectStalenessTickler(data);
245
+ return {
246
+ name: field.name,
247
+ current: field.current,
248
+ default: field.default,
249
+ source: field.source,
250
+ };
251
+ }
252
+ function inspectRuntimeAuthorityField(data) {
253
+ const field = inspectRuntimeAuthority(data);
254
+ return {
255
+ name: field.name,
256
+ current: field.current,
257
+ default: field.default,
258
+ source: field.source,
259
+ };
260
+ }
228
261
  const REGISTERED_POLICIES = [
229
262
  inspectAllowDirectCommits,
230
263
  inspectWipCap,
@@ -237,6 +270,9 @@ const REGISTERED_POLICIES = [
237
270
  emptyIsTyped: true,
238
271
  }),
239
272
  inspectSwarmSubagentBackend,
273
+ inspectStalenessTicklerField,
274
+ inspectRuntimeAuthorityField,
275
+ inspectProductSignalField,
240
276
  inspectValueFeedbackField,
241
277
  ];
242
278
  /** Walk registered inspectors and return one row per field (#1148). */
@@ -246,7 +282,15 @@ export function inspectAllPolicies(projectRoot) {
246
282
  }
247
283
  /** Look up a single registered field by canonical dotted-path name (or CLI alias). */
248
284
  export function inspectOnePolicy(name, projectRoot) {
249
- const normalized = name === FIELD_VALUE_FEEDBACK_CLI_ALIAS ? FIELD_VALUE_FEEDBACK : name;
285
+ const normalized = name === FIELD_VALUE_FEEDBACK_CLI_ALIAS
286
+ ? FIELD_VALUE_FEEDBACK
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;
250
294
  for (const field of inspectAllPolicies(projectRoot)) {
251
295
  if (field.name === normalized)
252
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
@@ -0,0 +1,210 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
3
+ import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
4
+ import { policyColonInvocation } from "./policy-invocation.js";
5
+ import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
6
+ /** Canonical registered policy field name (#2693). */
7
+ export const FIELD_PRODUCT_SIGNAL = "plan.policy.productSignal";
8
+ /** Short alias for `policy:show --field=productSignal`. */
9
+ export const FIELD_PRODUCT_SIGNAL_CLI_ALIAS = "productSignal";
10
+ export const DEFAULT_PRODUCT_SIGNAL_ENABLED = false;
11
+ /** Baked-in default private sink (#2693 D6). */
12
+ export const DEFAULT_PRODUCT_SIGNAL_SINK_REPO = "deftai/product-signal";
13
+ export const PRODUCT_SIGNAL_CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling product signal opts into " +
14
+ "consented qualitative check-ins that may submit minimized local summaries " +
15
+ "to a private GitHub sink when you also grant install-level consent.\n" +
16
+ " \u2022 Default OFF; no ambient consent nag while disabled.\n" +
17
+ " \u2022 Outbound requires enable + recorded consent (`product-signal:consent`).\n" +
18
+ " \u2022 Payloads include install context and windowed value/health/helped summaries " +
19
+ "(not raw ledgers or chat).\n" +
20
+ " \u2022 Inspect: `" +
21
+ policyColonInvocation("show", " --field=productSignal") +
22
+ "` and `task product-signal:status`.\n" +
23
+ " \u2022 Reversible: set `enabled: false` or revoke consent.\n" +
24
+ " \u2022 Changes are recorded to meta/policy-changes.log for auditability.";
25
+ function defaultResolved(source, error = null) {
26
+ return {
27
+ enabled: DEFAULT_PRODUCT_SIGNAL_ENABLED,
28
+ sinkRepo: DEFAULT_PRODUCT_SIGNAL_SINK_REPO,
29
+ source,
30
+ error,
31
+ };
32
+ }
33
+ /** Validate a `plan.policy.productSignal` payload. */
34
+ export function validateProductSignal(value) {
35
+ if (value === null || value === undefined) {
36
+ return [];
37
+ }
38
+ if (typeof value !== "object" || Array.isArray(value)) {
39
+ return [`${FIELD_PRODUCT_SIGNAL} must be an object; got ${typeof value}`];
40
+ }
41
+ const rec = value;
42
+ const errors = [];
43
+ if ("enabled" in rec && typeof rec.enabled !== "boolean") {
44
+ errors.push(`${FIELD_PRODUCT_SIGNAL}.enabled must be a boolean`);
45
+ }
46
+ if ("sinkRepo" in rec && typeof rec.sinkRepo !== "string") {
47
+ errors.push(`${FIELD_PRODUCT_SIGNAL}.sinkRepo must be a string`);
48
+ }
49
+ return errors;
50
+ }
51
+ function resolveFromPolicyBlock(raw) {
52
+ const errors = validateProductSignal(raw);
53
+ if (errors.length > 0) {
54
+ return defaultResolved("default-on-error", errors[0] ?? "invalid productSignal block");
55
+ }
56
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
57
+ return defaultResolved("default");
58
+ }
59
+ const block = raw;
60
+ const enabled = typeof block.enabled === "boolean" ? block.enabled : DEFAULT_PRODUCT_SIGNAL_ENABLED;
61
+ const sinkRepo = typeof block.sinkRepo === "string" && block.sinkRepo.trim().length > 0
62
+ ? block.sinkRepo.trim()
63
+ : DEFAULT_PRODUCT_SIGNAL_SINK_REPO;
64
+ return {
65
+ enabled,
66
+ sinkRepo,
67
+ source: "typed",
68
+ error: null,
69
+ };
70
+ }
71
+ /** Resolve `plan.policy.productSignal` from PROJECT-DEFINITION (#2693). */
72
+ export function resolveProductSignal(projectRoot) {
73
+ const [data, err] = loadProjectDefinition(projectRoot);
74
+ if (data === null) {
75
+ return defaultResolved("default-on-error", err);
76
+ }
77
+ const policyBlock = readPlanPolicy(data.plan);
78
+ if (typeof policyBlock !== "object" ||
79
+ policyBlock === null ||
80
+ Array.isArray(policyBlock) ||
81
+ !("productSignal" in policyBlock)) {
82
+ return defaultResolved("default");
83
+ }
84
+ return resolveFromPolicyBlock(policyBlock.productSignal);
85
+ }
86
+ /** Human-readable status line for CLI surfaces. */
87
+ export function formatProductSignalStatusLine(policy) {
88
+ return (`[deft policy] productSignal enabled=${String(policy.enabled)} ` +
89
+ `sinkRepo=${policy.sinkRepo}.`);
90
+ }
91
+ function fieldFromResolved(resolved) {
92
+ return {
93
+ name: FIELD_PRODUCT_SIGNAL,
94
+ current: {
95
+ enabled: resolved.enabled,
96
+ sinkRepo: resolved.sinkRepo,
97
+ },
98
+ default: {
99
+ enabled: DEFAULT_PRODUCT_SIGNAL_ENABLED,
100
+ sinkRepo: DEFAULT_PRODUCT_SIGNAL_SINK_REPO,
101
+ },
102
+ source: resolved.source,
103
+ };
104
+ }
105
+ /** Inspector row for `policy:show --field=productSignal`. */
106
+ export function inspectProductSignal(data, projectRoot) {
107
+ if (data === null) {
108
+ return fieldFromResolved(defaultResolved("default"));
109
+ }
110
+ const policyBlock = readPlanPolicy(data.plan);
111
+ if (typeof policyBlock !== "object" ||
112
+ policyBlock === null ||
113
+ Array.isArray(policyBlock) ||
114
+ !("productSignal" in policyBlock)) {
115
+ if (projectRoot !== undefined) {
116
+ return fieldFromResolved(resolveProductSignal(projectRoot));
117
+ }
118
+ return fieldFromResolved(defaultResolved("default"));
119
+ }
120
+ return fieldFromResolved(resolveFromPolicyBlock(policyBlock.productSignal));
121
+ }
122
+ /** Persist `productSignal.enabled=true` after capability-cost disclosure (#2693). */
123
+ export function enableProductSignal(projectRoot, options) {
124
+ if (!options.confirm) {
125
+ return {
126
+ exitCode: 1,
127
+ stdout: `${PRODUCT_SIGNAL_CAPABILITY_COST_DISCLOSURE}\n\n` +
128
+ `Re-run with --confirm to apply: task product-signal:enable -- --confirm\n`,
129
+ changed: false,
130
+ };
131
+ }
132
+ const path = projectDefinitionPath(projectRoot);
133
+ try {
134
+ const { changed } = projectDefinitionMutationLock(projectRoot, () => {
135
+ const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
136
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
137
+ throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
138
+ }
139
+ const data = parsed;
140
+ if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
141
+ if (data.plan === undefined) {
142
+ data.plan = {};
143
+ }
144
+ else {
145
+ throw new Error("PROJECT-DEFINITION 'plan' is not an object");
146
+ }
147
+ }
148
+ const plan = data.plan;
149
+ migrateLegacyPolicyKey(plan);
150
+ const existingPolicy = plan[PLAN_POLICY_KEY];
151
+ if (typeof existingPolicy !== "object" ||
152
+ existingPolicy === null ||
153
+ Array.isArray(existingPolicy)) {
154
+ if (existingPolicy === undefined) {
155
+ plan[PLAN_POLICY_KEY] = {};
156
+ }
157
+ else {
158
+ throw new Error("plan.policy is not an object");
159
+ }
160
+ }
161
+ const policyBlock = plan[PLAN_POLICY_KEY];
162
+ const previous = policyBlock.productSignal;
163
+ const prevObj = typeof previous === "object" && previous !== null && !Array.isArray(previous)
164
+ ? previous
165
+ : {};
166
+ const sinkRepo = options.sinkRepo?.trim() ||
167
+ (typeof prevObj.sinkRepo === "string" && prevObj.sinkRepo.trim().length > 0
168
+ ? prevObj.sinkRepo.trim()
169
+ : DEFAULT_PRODUCT_SIGNAL_SINK_REPO);
170
+ const nextBlock = { enabled: true, sinkRepo };
171
+ const previousNormalized = resolveFromPolicyBlock(previous);
172
+ const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
173
+ previousNormalized.sinkRepo !== nextBlock.sinkRepo;
174
+ policyBlock.productSignal = nextBlock;
175
+ if (changedFlag) {
176
+ atomicWriteProjectDefinition(path, data);
177
+ }
178
+ const actor = options.actor ?? "task product-signal:enable";
179
+ const note = options.note ?? "";
180
+ const parts = [
181
+ `actor=${actor}`,
182
+ "productSignal.enabled=true",
183
+ `sinkRepo=${sinkRepo}`,
184
+ `previous=${JSON.stringify(previous ?? null)}`,
185
+ ];
186
+ if (note) {
187
+ parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
188
+ }
189
+ appendAuditLog(projectRoot, parts.join(" "));
190
+ return { changed: changedFlag };
191
+ });
192
+ const resolved = resolveProductSignal(projectRoot);
193
+ const lines = [
194
+ `\u2713 ${FIELD_PRODUCT_SIGNAL}.enabled=true (product-signal ON).`,
195
+ changed
196
+ ? " audit: meta/policy-changes.log updated."
197
+ : " no-op: value already matched (audit entry still appended for trail).",
198
+ formatProductSignalStatusLine(resolved),
199
+ ];
200
+ return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
201
+ }
202
+ catch (err) {
203
+ const message = err instanceof Error ? err.message : String(err);
204
+ if (message.includes("PROJECT-DEFINITION not found")) {
205
+ return { exitCode: 2, stdout: `\u274c ${message}\n`, changed: false };
206
+ }
207
+ return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
208
+ }
209
+ }
210
+ //# sourceMappingURL=product-signal.js.map
@@ -0,0 +1,54 @@
1
+ export declare const FIELD_RUNTIME_AUTHORITY = "plan.policy.runtimeAuthority";
2
+ export declare const FIELD_RUNTIME_AUTHORITY_CLI_ALIAS = "runtimeAuthority";
3
+ /** Graduated permission scopes (#1394 steipete ladder). */
4
+ export interface RuntimeAuthorityScopes {
5
+ /** Direct edit/write tools (PreToolUse classifiable today). */
6
+ readonly edits: boolean;
7
+ /** git push / tag push — enforced only when Shell payload is classifiable (host gap). */
8
+ readonly push: boolean;
9
+ /** merge / close / release — enforced only when Shell payload is classifiable (host gap). */
10
+ readonly merge: boolean;
11
+ }
12
+ export interface RuntimeAuthorityPolicy {
13
+ readonly enabled: boolean;
14
+ /** When non-empty, write targets must match at least one pattern. Empty = allow all paths. */
15
+ readonly allowPaths: readonly string[];
16
+ /** Deny wins over allow. */
17
+ readonly denyPaths: readonly string[];
18
+ readonly scopes: RuntimeAuthorityScopes;
19
+ }
20
+ export declare const DEFAULT_RUNTIME_AUTHORITY_SCOPES: RuntimeAuthorityScopes;
21
+ /** Safe default: opt-in only — existing consumers see no runtime authority until enabled. */
22
+ export declare const DEFAULT_RUNTIME_AUTHORITY_POLICY: RuntimeAuthorityPolicy;
23
+ export interface RuntimeAuthorityPolicyField {
24
+ readonly name: string;
25
+ readonly current: RuntimeAuthorityPolicy;
26
+ readonly default: RuntimeAuthorityPolicy;
27
+ readonly source: string;
28
+ }
29
+ export declare function resolveRuntimeAuthorityPolicy(raw: unknown): RuntimeAuthorityPolicy;
30
+ export declare function validateRuntimeAuthority(value: unknown): string[];
31
+ /** Inspector row for `policy:show --field=runtimeAuthority`. */
32
+ export declare function inspectRuntimeAuthority(data: Record<string, unknown> | null): RuntimeAuthorityPolicyField;
33
+ /** Resolve typed runtime authority policy from PROJECT-DEFINITION. */
34
+ export declare function loadRuntimeAuthorityPolicy(data: Record<string, unknown> | null): RuntimeAuthorityPolicy;
35
+ /** Load runtime authority from a project root (PROJECT-DEFINITION on disk). */
36
+ export declare function loadRuntimeAuthorityFromProject(projectRoot: string): RuntimeAuthorityPolicy;
37
+ export type RuntimeAuthorityPathVerdict = "allow" | "deny-allowlist" | "deny-denylist";
38
+ /** Evaluate a project-relative POSIX path against allow/deny globs. */
39
+ export declare function evaluateRuntimeAuthorityPath(policy: RuntimeAuthorityPolicy, relPathPosix: string): RuntimeAuthorityPathVerdict;
40
+ export interface RuntimeAuthorityDirectWriteInput {
41
+ readonly policy: RuntimeAuthorityPolicy;
42
+ readonly relPathPosix: string | null;
43
+ }
44
+ export interface RuntimeAuthorityDirectWriteResult {
45
+ readonly allowed: boolean;
46
+ readonly reason: string | null;
47
+ readonly code: "runtime-policy-deny-scope" | "runtime-policy-deny-path" | null;
48
+ }
49
+ /**
50
+ * Evaluate direct-write runtime authority after ritual/scope gates (#1394).
51
+ * Unclassifiable paths (null) pass — host gap documented for Shell/MCP.
52
+ */
53
+ export declare function evaluateRuntimeAuthorityDirectWrite(input: RuntimeAuthorityDirectWriteInput): RuntimeAuthorityDirectWriteResult;
54
+ //# sourceMappingURL=runtime-authority.d.ts.map