@deftai/directive-core 0.79.3 → 0.80.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 (77) hide show
  1. package/dist/coverage-hotspots/evaluate.d.ts +49 -0
  2. package/dist/coverage-hotspots/evaluate.js +262 -0
  3. package/dist/coverage-hotspots/index.d.ts +3 -0
  4. package/dist/coverage-hotspots/index.js +3 -0
  5. package/dist/coverage-hotspots/thresholds.d.ts +5 -0
  6. package/dist/coverage-hotspots/thresholds.js +48 -0
  7. package/dist/hooks/dispatcher.d.ts +25 -3
  8. package/dist/hooks/dispatcher.js +216 -65
  9. package/dist/hooks/readonly.d.ts +7 -0
  10. package/dist/hooks/readonly.js +83 -0
  11. package/dist/hooks/tools.d.ts +7 -0
  12. package/dist/hooks/tools.js +16 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/init-deposit/agent-hooks.d.ts +4 -1
  16. package/dist/init-deposit/agent-hooks.js +77 -21
  17. package/dist/init-deposit/xbrief-projections.d.ts +7 -0
  18. package/dist/init-deposit/xbrief-projections.js +29 -1
  19. package/dist/integration-e2e/bootstrap-cache-module.d.ts +2 -4
  20. package/dist/integration-e2e/bootstrap-cache-module.js +2 -20
  21. package/dist/policy/index.d.ts +2 -0
  22. package/dist/policy/index.js +31 -1
  23. package/dist/policy/runtime-authority.d.ts +54 -0
  24. package/dist/policy/runtime-authority.js +180 -0
  25. package/dist/policy/staleness-tickler.d.ts +20 -0
  26. package/dist/policy/staleness-tickler.js +151 -0
  27. package/dist/pr-merge-readiness/ci-gate.d.ts +7 -1
  28. package/dist/pr-merge-readiness/ci-gate.js +38 -5
  29. package/dist/pr-merge-readiness/gh.d.ts +4 -0
  30. package/dist/pr-merge-readiness/gh.js +10 -3
  31. package/dist/pr-merge-readiness/index.d.ts +2 -1
  32. package/dist/pr-merge-readiness/index.js +2 -1
  33. package/dist/pr-merge-readiness/runner-capacity-stall.d.ts +35 -0
  34. package/dist/pr-merge-readiness/runner-capacity-stall.js +46 -0
  35. package/dist/pr-wait-mergeable/cascade.js +8 -1
  36. package/dist/pr-wait-mergeable/classify.js +5 -1
  37. package/dist/pr-wait-mergeable/wrappers.js +15 -4
  38. package/dist/pr-watch/constants.d.ts +15 -0
  39. package/dist/pr-watch/constants.js +16 -1
  40. package/dist/pr-watch/main.js +12 -0
  41. package/dist/pr-watch/probe.js +13 -1
  42. package/dist/pr-watch/types.d.ts +9 -0
  43. package/dist/pr-watch/watch.js +24 -1
  44. package/dist/scope/main.js +12 -0
  45. package/dist/scope/undo.js +12 -2
  46. package/dist/session/release-availability.d.ts +21 -0
  47. package/dist/session/release-availability.js +109 -0
  48. package/dist/session/ritual-sentinel.d.ts +14 -0
  49. package/dist/session/ritual-sentinel.js +28 -0
  50. package/dist/session/session-start.d.ts +10 -0
  51. package/dist/session/session-start.js +18 -0
  52. package/dist/staleness-tickler/escalation.d.ts +14 -0
  53. package/dist/staleness-tickler/escalation.js +106 -0
  54. package/dist/staleness-tickler/idle.d.ts +18 -0
  55. package/dist/staleness-tickler/idle.js +47 -0
  56. package/dist/staleness-tickler/index.d.ts +8 -0
  57. package/dist/staleness-tickler/index.js +8 -0
  58. package/dist/staleness-tickler/probe-directive.d.ts +32 -0
  59. package/dist/staleness-tickler/probe-directive.js +106 -0
  60. package/dist/staleness-tickler/probe-xbrief.d.ts +12 -0
  61. package/dist/staleness-tickler/probe-xbrief.js +103 -0
  62. package/dist/staleness-tickler/run.d.ts +31 -0
  63. package/dist/staleness-tickler/run.js +227 -0
  64. package/dist/staleness-tickler/state.d.ts +10 -0
  65. package/dist/staleness-tickler/state.js +46 -0
  66. package/dist/staleness-tickler/types.d.ts +85 -0
  67. package/dist/staleness-tickler/types.js +5 -0
  68. package/dist/triage/bootstrap/cache-module.d.ts +25 -0
  69. package/dist/triage/bootstrap/cache-module.js +39 -0
  70. package/dist/triage/bootstrap/index.d.ts +1 -0
  71. package/dist/triage/bootstrap/index.js +7 -82
  72. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  73. package/dist/vbrief-validate/plan-hooks.js +50 -0
  74. package/dist/verify-env/agent-hooks.js +4 -2
  75. package/dist/xbrief-migrate/agents-header.d.ts +0 -11
  76. package/dist/xbrief-migrate/agents-header.js +10 -1
  77. package/package.json +3 -3
@@ -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,6 +5,13 @@
5
5
  * freshness. A current `.deft/core/VERSION` therefore cannot short-circuit
6
6
  * these repairs.
7
7
  */
8
+ /** Rewrite legacy vbrief/.eval description paths for xbrief/schemas projection (#2670). */
9
+ export declare function rewriteProjectedSchemaContent(content: string): string;
10
+ /**
11
+ * Fail closed when a projected xbrief/schemas file still cites vbrief/.eval/.
12
+ * Upstream vbrief/schemas/ may keep legacy paths; consumer copies must not (#2670).
13
+ */
14
+ export declare function assertProjectedSchemaDescriptionsRooted(projectDir: string, destinationDir: string): void;
8
15
  /**
9
16
  * Synchronize framework-owned xBRIEF schemas while preserving unknown consumer
10
17
  * files. The obsolete v0.6 root schema is the only destination-only file this
@@ -13,6 +13,10 @@ import { DEV_FALLBACK } from "../platform/constants.js";
13
13
  import { MIGRATED_ARTIFACT_DIR } from "../xbrief-migrate/constants.js";
14
14
  const OBSOLETE_CORE_SCHEMA = "vbrief-core.schema.json";
15
15
  const CURRENT_CORE_SCHEMA = "xbrief-core-0.8.schema.json";
16
+ /** Legacy lifecycle eval prefix in framework vbrief/schemas source copies. */
17
+ const LEGACY_EVAL_PATH_PREFIX = "vbrief/.eval/";
18
+ /** Consumer xbrief/schemas projection must root description paths here (#2670). */
19
+ const XBRIEF_EVAL_PATH_PREFIX = "xbrief/.eval/";
16
20
  function normalizeVersion(version) {
17
21
  return version.trim().replace(/^v/, "");
18
22
  }
@@ -39,6 +43,28 @@ function collectSchemaFiles(root, dir = root, files = []) {
39
43
  }
40
44
  return files;
41
45
  }
46
+ /** Rewrite legacy vbrief/.eval description paths for xbrief/schemas projection (#2670). */
47
+ export function rewriteProjectedSchemaContent(content) {
48
+ return content.includes(LEGACY_EVAL_PATH_PREFIX)
49
+ ? content.replaceAll(LEGACY_EVAL_PATH_PREFIX, XBRIEF_EVAL_PATH_PREFIX)
50
+ : content;
51
+ }
52
+ /**
53
+ * Fail closed when a projected xbrief/schemas file still cites vbrief/.eval/.
54
+ * Upstream vbrief/schemas/ may keep legacy paths; consumer copies must not (#2670).
55
+ */
56
+ export function assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir) {
57
+ assertProjectionContained(projectDir, destinationDir);
58
+ if (!isDirectory(destinationDir))
59
+ return;
60
+ for (const rel of collectSchemaFiles(destinationDir)) {
61
+ const full = join(destinationDir, rel);
62
+ const text = readFileSync(full, "utf8");
63
+ if (text.includes(LEGACY_EVAL_PATH_PREFIX)) {
64
+ throw new Error(`projected xbrief schema still cites ${LEGACY_EVAL_PATH_PREFIX}: ${join(MIGRATED_ARTIFACT_DIR, "schemas", rel)}`);
65
+ }
66
+ }
67
+ }
42
68
  function writeFileIfChanged(projectDir, target, content) {
43
69
  assertProjectionContained(projectDir, target);
44
70
  const desired = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
@@ -73,7 +99,8 @@ export function syncConsumerXbriefSchemas(projectDir, deftDir) {
73
99
  continue;
74
100
  const source = join(sourceDir, rel);
75
101
  const destination = join(destinationDir, rel);
76
- changed = writeFileIfChanged(projectDir, destination, readFileSync(source)) || changed;
102
+ const projected = rewriteProjectedSchemaContent(readFileSync(source, "utf8"));
103
+ changed = writeFileIfChanged(projectDir, destination, projected) || changed;
77
104
  }
78
105
  const obsoleteDestination = join(destinationDir, OBSOLETE_CORE_SCHEMA);
79
106
  assertProjectionContained(projectDir, obsoleteDestination);
@@ -81,6 +108,7 @@ export function syncConsumerXbriefSchemas(projectDir, deftDir) {
81
108
  rmSync(obsoleteDestination, { force: true });
82
109
  changed = true;
83
110
  }
111
+ assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir);
84
112
  return changed;
85
113
  }
86
114
  function syncBareVersionMarkerWithPolicy(projectDir, version, allowRootFallback) {
@@ -1,5 +1,3 @@
1
- import type { FetchAllReportImpl } from "../cache/fetch.js";
2
- import type { CacheModule } from "../triage/bootstrap/types.js";
3
- /** Adapt TS cache reports to the bootstrap `FetchAllReport` shape. */
4
- export declare function bootstrapCacheModule(cacheFetchAll: (options: Parameters<typeof import("../cache/fetch.js").cacheFetchAll>[0]) => FetchAllReportImpl): CacheModule;
1
+ /** Re-export bootstrap TS cache adapter (canonical home: triage/bootstrap). */
2
+ export { bootstrapCacheModule } from "../triage/bootstrap/cache-module.js";
5
3
  //# sourceMappingURL=bootstrap-cache-module.d.ts.map
@@ -1,21 +1,3 @@
1
- /** Adapt TS cache reports to the bootstrap `FetchAllReport` shape. */
2
- export function bootstrapCacheModule(cacheFetchAll) {
3
- return {
4
- cacheFetchAll(kwargs) {
5
- const report = cacheFetchAll({
6
- source: kwargs.source,
7
- repo: kwargs.repo,
8
- batchSize: kwargs.batchSize,
9
- delayMs: kwargs.delayMs,
10
- cacheRoot: kwargs.cacheRoot,
11
- });
12
- return Promise.resolve({
13
- succeeded: report.issuesWritten,
14
- failed: report.issuesFailed,
15
- skipped: report.alreadyFresh,
16
- summaryLine: (source, repo) => report.summaryLine(source, repo),
17
- });
18
- },
19
- };
20
- }
1
+ /** Re-export bootstrap TS cache adapter (canonical home: triage/bootstrap). */
2
+ export { bootstrapCacheModule } from "../triage/bootstrap/cache-module.js";
21
3
  //# sourceMappingURL=bootstrap-cache-module.js.map
@@ -6,6 +6,8 @@ export * from "./disclosure.js";
6
6
  export * from "./plan-extensions.js";
7
7
  export * from "./policy-invocation.js";
8
8
  export * from "./resolve.js";
9
+ export * from "./runtime-authority.js";
10
+ export * from "./staleness-tickler.js";
9
11
  export * from "./value-feedback.js";
10
12
  export * from "./wip.js";
11
13
  export declare const FIELD_ALLOW_DIRECT_COMMITS = "plan.policy.allowDirectCommitsToMaster";
@@ -1,5 +1,7 @@
1
1
  import { readPlanPolicy } from "./plan-extensions.js";
2
2
  import { coerceLegacyNarrative, LEGACY_NARRATIVE_KEY, loadProjectDefinition } from "./resolve.js";
3
+ import { FIELD_RUNTIME_AUTHORITY, FIELD_RUNTIME_AUTHORITY_CLI_ALIAS, inspectRuntimeAuthority, } from "./runtime-authority.js";
4
+ import { FIELD_STALENESS_TICKLER, FIELD_STALENESS_TICKLER_CLI_ALIAS, inspectStalenessTickler, } from "./staleness-tickler.js";
3
5
  import { FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, inspectValueFeedback, } from "./value-feedback.js";
4
6
  import { DEFAULT_WIP_CAP } from "./wip.js";
5
7
  export * from "./agents-md-advisory.js";
@@ -10,6 +12,8 @@ export * from "./disclosure.js";
10
12
  export * from "./plan-extensions.js";
11
13
  export * from "./policy-invocation.js";
12
14
  export * from "./resolve.js";
15
+ export * from "./runtime-authority.js";
16
+ export * from "./staleness-tickler.js";
13
17
  export * from "./value-feedback.js";
14
18
  export * from "./wip.js";
15
19
  export const FIELD_ALLOW_DIRECT_COMMITS = "plan.policy.allowDirectCommitsToMaster";
@@ -225,6 +229,24 @@ function inspectValueFeedbackField(data, projectRoot) {
225
229
  source: field.source,
226
230
  };
227
231
  }
232
+ function inspectStalenessTicklerField(data) {
233
+ const field = inspectStalenessTickler(data);
234
+ return {
235
+ name: field.name,
236
+ current: field.current,
237
+ default: field.default,
238
+ source: field.source,
239
+ };
240
+ }
241
+ function inspectRuntimeAuthorityField(data) {
242
+ const field = inspectRuntimeAuthority(data);
243
+ return {
244
+ name: field.name,
245
+ current: field.current,
246
+ default: field.default,
247
+ source: field.source,
248
+ };
249
+ }
228
250
  const REGISTERED_POLICIES = [
229
251
  inspectAllowDirectCommits,
230
252
  inspectWipCap,
@@ -237,6 +259,8 @@ const REGISTERED_POLICIES = [
237
259
  emptyIsTyped: true,
238
260
  }),
239
261
  inspectSwarmSubagentBackend,
262
+ inspectStalenessTicklerField,
263
+ inspectRuntimeAuthorityField,
240
264
  inspectValueFeedbackField,
241
265
  ];
242
266
  /** Walk registered inspectors and return one row per field (#1148). */
@@ -246,7 +270,13 @@ export function inspectAllPolicies(projectRoot) {
246
270
  }
247
271
  /** Look up a single registered field by canonical dotted-path name (or CLI alias). */
248
272
  export function inspectOnePolicy(name, projectRoot) {
249
- const normalized = name === FIELD_VALUE_FEEDBACK_CLI_ALIAS ? FIELD_VALUE_FEEDBACK : name;
273
+ const normalized = name === FIELD_VALUE_FEEDBACK_CLI_ALIAS
274
+ ? 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;
250
280
  for (const field of inspectAllPolicies(projectRoot)) {
251
281
  if (field.name === normalized)
252
282
  return field;
@@ -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
@@ -0,0 +1,180 @@
1
+ import { matchAny } from "../orchestration/pathspec.js";
2
+ import { readPlanPolicy } from "./plan-extensions.js";
3
+ import { loadProjectDefinition } from "./resolve.js";
4
+ export const FIELD_RUNTIME_AUTHORITY = "plan.policy.runtimeAuthority";
5
+ export const FIELD_RUNTIME_AUTHORITY_CLI_ALIAS = "runtimeAuthority";
6
+ export const DEFAULT_RUNTIME_AUTHORITY_SCOPES = {
7
+ edits: true,
8
+ push: false,
9
+ merge: false,
10
+ };
11
+ /** Safe default: opt-in only — existing consumers see no runtime authority until enabled. */
12
+ export const DEFAULT_RUNTIME_AUTHORITY_POLICY = {
13
+ enabled: false,
14
+ allowPaths: [],
15
+ denyPaths: [],
16
+ scopes: DEFAULT_RUNTIME_AUTHORITY_SCOPES,
17
+ };
18
+ function readBoolean(rec, key, fallback) {
19
+ if (key in rec && typeof rec[key] === "boolean") {
20
+ return rec[key];
21
+ }
22
+ return fallback;
23
+ }
24
+ function readStringArray(raw) {
25
+ if (!Array.isArray(raw))
26
+ return [];
27
+ return raw.filter((item) => typeof item === "string" && item.trim().length > 0);
28
+ }
29
+ function readScopes(raw) {
30
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
31
+ return DEFAULT_RUNTIME_AUTHORITY_SCOPES;
32
+ }
33
+ const rec = raw;
34
+ return {
35
+ edits: readBoolean(rec, "edits", DEFAULT_RUNTIME_AUTHORITY_SCOPES.edits),
36
+ push: readBoolean(rec, "push", DEFAULT_RUNTIME_AUTHORITY_SCOPES.push),
37
+ merge: readBoolean(rec, "merge", DEFAULT_RUNTIME_AUTHORITY_SCOPES.merge),
38
+ };
39
+ }
40
+ export function resolveRuntimeAuthorityPolicy(raw) {
41
+ if (raw === null || raw === undefined) {
42
+ return DEFAULT_RUNTIME_AUTHORITY_POLICY;
43
+ }
44
+ if (typeof raw !== "object" || Array.isArray(raw)) {
45
+ return DEFAULT_RUNTIME_AUTHORITY_POLICY;
46
+ }
47
+ const rec = raw;
48
+ return {
49
+ enabled: readBoolean(rec, "enabled", DEFAULT_RUNTIME_AUTHORITY_POLICY.enabled),
50
+ allowPaths: readStringArray(rec.allowPaths),
51
+ denyPaths: readStringArray(rec.denyPaths),
52
+ scopes: readScopes(rec.scopes),
53
+ };
54
+ }
55
+ export function validateRuntimeAuthority(value) {
56
+ if (value === null || value === undefined) {
57
+ return [];
58
+ }
59
+ if (typeof value !== "object" || Array.isArray(value)) {
60
+ return [`${FIELD_RUNTIME_AUTHORITY} must be an object; got ${typeof value}`];
61
+ }
62
+ const rec = value;
63
+ const errors = [];
64
+ if ("enabled" in rec && typeof rec.enabled !== "boolean") {
65
+ errors.push(`${FIELD_RUNTIME_AUTHORITY}.enabled must be a boolean`);
66
+ }
67
+ for (const key of ["allowPaths", "denyPaths"]) {
68
+ if (key in rec && !Array.isArray(rec[key])) {
69
+ errors.push(`${FIELD_RUNTIME_AUTHORITY}.${key} must be an array of path globs`);
70
+ }
71
+ }
72
+ if ("scopes" in rec) {
73
+ const scopes = rec.scopes;
74
+ if (typeof scopes !== "object" || scopes === null || Array.isArray(scopes)) {
75
+ errors.push(`${FIELD_RUNTIME_AUTHORITY}.scopes must be an object`);
76
+ }
77
+ else {
78
+ for (const key of ["edits", "push", "merge"]) {
79
+ if (key in scopes &&
80
+ typeof scopes[key] !== "boolean") {
81
+ errors.push(`${FIELD_RUNTIME_AUTHORITY}.scopes.${key} must be a boolean`);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return errors;
87
+ }
88
+ function fieldFromResolved(resolved, source) {
89
+ return {
90
+ name: FIELD_RUNTIME_AUTHORITY,
91
+ current: resolved,
92
+ default: DEFAULT_RUNTIME_AUTHORITY_POLICY,
93
+ source,
94
+ };
95
+ }
96
+ /** Inspector row for `policy:show --field=runtimeAuthority`. */
97
+ export function inspectRuntimeAuthority(data) {
98
+ if (data === null) {
99
+ return fieldFromResolved(DEFAULT_RUNTIME_AUTHORITY_POLICY, "default");
100
+ }
101
+ const policyBlock = readPlanPolicy(data.plan);
102
+ if (typeof policyBlock !== "object" ||
103
+ policyBlock === null ||
104
+ Array.isArray(policyBlock) ||
105
+ !("runtimeAuthority" in policyBlock)) {
106
+ return fieldFromResolved(DEFAULT_RUNTIME_AUTHORITY_POLICY, "default");
107
+ }
108
+ const resolved = resolveRuntimeAuthorityPolicy(policyBlock.runtimeAuthority);
109
+ return fieldFromResolved(resolved, "typed");
110
+ }
111
+ /** Resolve typed runtime authority policy from PROJECT-DEFINITION. */
112
+ export function loadRuntimeAuthorityPolicy(data) {
113
+ if (data === null) {
114
+ return DEFAULT_RUNTIME_AUTHORITY_POLICY;
115
+ }
116
+ const policyBlock = readPlanPolicy(data.plan);
117
+ if (typeof policyBlock !== "object" ||
118
+ policyBlock === null ||
119
+ Array.isArray(policyBlock) ||
120
+ !("runtimeAuthority" in policyBlock)) {
121
+ return DEFAULT_RUNTIME_AUTHORITY_POLICY;
122
+ }
123
+ return resolveRuntimeAuthorityPolicy(policyBlock.runtimeAuthority);
124
+ }
125
+ /** Load runtime authority from a project root (PROJECT-DEFINITION on disk). */
126
+ export function loadRuntimeAuthorityFromProject(projectRoot) {
127
+ const [data] = loadProjectDefinition(projectRoot);
128
+ return loadRuntimeAuthorityPolicy(data);
129
+ }
130
+ /** Evaluate a project-relative POSIX path against allow/deny globs. */
131
+ export function evaluateRuntimeAuthorityPath(policy, relPathPosix) {
132
+ if (!policy.enabled)
133
+ return "allow";
134
+ if (matchAny(policy.denyPaths, relPathPosix))
135
+ return "deny-denylist";
136
+ if (policy.allowPaths.length > 0 && !matchAny(policy.allowPaths, relPathPosix)) {
137
+ return "deny-allowlist";
138
+ }
139
+ return "allow";
140
+ }
141
+ /**
142
+ * Evaluate direct-write runtime authority after ritual/scope gates (#1394).
143
+ * Unclassifiable paths (null) pass — host gap documented for Shell/MCP.
144
+ */
145
+ export function evaluateRuntimeAuthorityDirectWrite(input) {
146
+ const { policy, relPathPosix } = input;
147
+ if (!policy.enabled) {
148
+ return { allowed: true, reason: null, code: null };
149
+ }
150
+ if (!policy.scopes.edits) {
151
+ return {
152
+ allowed: false,
153
+ code: "runtime-policy-deny-scope",
154
+ reason: "Directive denied this direct write: plan.policy.runtimeAuthority.scopes.edits is false. " +
155
+ "Grant the edits scope in PROJECT-DEFINITION or disable runtimeAuthority.",
156
+ };
157
+ }
158
+ if (relPathPosix === null) {
159
+ return { allowed: true, reason: null, code: null };
160
+ }
161
+ const pathVerdict = evaluateRuntimeAuthorityPath(policy, relPathPosix);
162
+ if (pathVerdict === "deny-denylist") {
163
+ return {
164
+ allowed: false,
165
+ code: "runtime-policy-deny-path",
166
+ reason: `Directive denied this direct write: path ${relPathPosix} matches ` +
167
+ "plan.policy.runtimeAuthority.denyPaths.",
168
+ };
169
+ }
170
+ if (pathVerdict === "deny-allowlist") {
171
+ return {
172
+ allowed: false,
173
+ code: "runtime-policy-deny-path",
174
+ reason: `Directive denied this direct write: path ${relPathPosix} is outside ` +
175
+ "plan.policy.runtimeAuthority.allowPaths.",
176
+ };
177
+ }
178
+ return { allowed: true, reason: null, code: null };
179
+ }
180
+ //# sourceMappingURL=runtime-authority.js.map
@@ -0,0 +1,20 @@
1
+ import type { StalenessTicklerPolicy, StalenessTicklerSnoozePolicy, StalenessTicklerTierThresholds, StalenessTicklerWeights } from "../staleness-tickler/types.js";
2
+ export declare const FIELD_STALENESS_TICKLER = "plan.policy.stalenessTickler";
3
+ export declare const FIELD_STALENESS_TICKLER_CLI_ALIAS = "stalenessTickler";
4
+ export declare const DEFAULT_STALENESS_TICKLER_WEIGHTS: StalenessTicklerWeights;
5
+ export declare const DEFAULT_STALENESS_TICKLER_TIERS: StalenessTicklerTierThresholds;
6
+ export declare const DEFAULT_STALENESS_TICKLER_SNOOZE: StalenessTicklerSnoozePolicy;
7
+ export declare const DEFAULT_STALENESS_TICKLER_POLICY: StalenessTicklerPolicy;
8
+ export interface StalenessTicklerPolicyField {
9
+ readonly name: string;
10
+ readonly current: StalenessTicklerPolicy;
11
+ readonly default: StalenessTicklerPolicy;
12
+ readonly source: string;
13
+ }
14
+ export declare function resolveStalenessTicklerPolicy(raw: unknown): StalenessTicklerPolicy;
15
+ export declare function validateStalenessTickler(value: unknown): string[];
16
+ /** Inspector row for `policy:show --field=stalenessTickler`. */
17
+ export declare function inspectStalenessTickler(data: Record<string, unknown> | null): StalenessTicklerPolicyField;
18
+ /** Resolve typed staleness tickler policy from PROJECT-DEFINITION. */
19
+ export declare function loadStalenessTicklerPolicy(data: Record<string, unknown> | null): StalenessTicklerPolicy;
20
+ //# sourceMappingURL=staleness-tickler.d.ts.map