context-brake 1.0.0 → 1.1.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 (195) hide show
  1. package/README.md +31 -41
  2. package/dist/assets/runtime/antigravity-cli-hook.mjs +68 -23
  3. package/dist/assets/runtime/claude-code-hook.mjs +133 -35
  4. package/dist/assets/runtime/claude-code-mod.mjs +165 -59
  5. package/dist/assets/runtime/claude-code-statusline.mjs +41 -9
  6. package/dist/assets/runtime/codex-cli-hook.mjs +71 -23
  7. package/dist/assets/runtime/cursor-hook.mjs +71 -23
  8. package/dist/assets/runtime/github-copilot-cli-hook.mjs +71 -23
  9. package/dist/assets/runtime/omp-extension.js +70 -23
  10. package/dist/assets/runtime/omp-restart.js +41 -9
  11. package/dist/assets/runtime/opencode-plugin.js +70 -23
  12. package/dist/assets/runtime/pi-extension.js +73 -23
  13. package/dist/assets/runtime/pi-restart.js +41 -9
  14. package/dist/src/cli/assistant/assistant-context.js +7 -0
  15. package/dist/src/cli/assistant/assistant-questions.js +5 -4
  16. package/dist/src/cli/assistant/assistant-session.js +3 -1
  17. package/dist/src/cli/assistant/invocation-facts.d.ts +5 -0
  18. package/dist/src/cli/assistant/invocation-facts.js +6 -0
  19. package/dist/src/cli/assistant/questions-misc.d.ts +1 -0
  20. package/dist/src/cli/assistant/questions-misc.js +3 -2
  21. package/dist/src/cli/assistant/questions-skill.d.ts +21 -0
  22. package/dist/src/cli/assistant/questions-skill.js +45 -0
  23. package/dist/src/cli/assistant/questions-snapshot.d.ts +3 -1
  24. package/dist/src/cli/assistant/questions-snapshot.js +21 -26
  25. package/dist/src/cli/assistant/questions-trigger.d.ts +6 -0
  26. package/dist/src/cli/assistant/questions-trigger.js +56 -0
  27. package/dist/src/cli/assistant/skill-options.d.ts +20 -0
  28. package/dist/src/cli/assistant/skill-options.js +40 -0
  29. package/dist/src/cli/assistant/snapshot-specs.d.ts +7 -1
  30. package/dist/src/cli/assistant/snapshot-specs.js +17 -7
  31. package/dist/src/cli/assistant/summary-rows.d.ts +5 -0
  32. package/dist/src/cli/assistant/summary-rows.js +47 -0
  33. package/dist/src/cli/assistant/summary.d.ts +2 -1
  34. package/dist/src/cli/assistant/summary.js +21 -30
  35. package/dist/src/cli/assistant/types.d.ts +15 -0
  36. package/dist/src/cli/commands/doctor.js +2 -1
  37. package/dist/src/cli/commands/remove.js +1 -1
  38. package/dist/src/cli/init-arguments.js +5 -4
  39. package/dist/src/cli/init-flow.js +1 -1
  40. package/dist/src/cli/init-option-rules.d.ts +5 -0
  41. package/dist/src/cli/init-option-rules.js +16 -0
  42. package/dist/src/cli/output/card.d.ts +8 -0
  43. package/dist/src/cli/output/card.js +30 -0
  44. package/dist/src/cli/output/doctor-mode-text.js +8 -2
  45. package/dist/src/cli/output/install-rows.d.ts +6 -0
  46. package/dist/src/cli/output/install-rows.js +31 -0
  47. package/dist/src/cli/output/install-text.d.ts +4 -0
  48. package/dist/src/cli/output/install-text.js +26 -0
  49. package/dist/src/cli/output/style.d.ts +22 -0
  50. package/dist/src/cli/output/style.js +27 -0
  51. package/dist/src/cli/output/text.d.ts +1 -2
  52. package/dist/src/cli/output/text.js +1 -28
  53. package/dist/src/core/contracts/adapter.d.ts +2 -1
  54. package/dist/src/core/contracts/configuration.d.ts +2 -1
  55. package/dist/src/core/contracts/configuration.js +2 -10
  56. package/dist/src/core/contracts/context-reading.d.ts +16 -0
  57. package/dist/src/core/contracts/context-reading.js +20 -0
  58. package/dist/src/core/contracts/context-window-report.d.ts +8 -1
  59. package/dist/src/core/contracts/context-window-report.js +3 -1
  60. package/dist/src/core/contracts/diagnostics.d.ts +21 -0
  61. package/dist/src/core/contracts/diagnostics.js +2 -1
  62. package/dist/src/core/contracts/manifest.d.ts +16 -0
  63. package/dist/src/core/contracts/manifest.js +6 -0
  64. package/dist/src/core/contracts/runtime.d.ts +2 -0
  65. package/dist/src/core/contracts/skill-catalog.d.ts +11 -0
  66. package/dist/src/core/contracts/skill-catalog.js +1 -0
  67. package/dist/src/core/contracts/skill-invocation.d.ts +3 -0
  68. package/dist/src/core/contracts/skill-invocation.js +1 -0
  69. package/dist/src/core/contracts/skill-name.d.ts +5 -0
  70. package/dist/src/core/contracts/skill-name.js +31 -0
  71. package/dist/src/core/services/brake-engine.js +4 -3
  72. package/dist/src/core/services/doctor-report-extras.d.ts +2 -0
  73. package/dist/src/core/services/doctor-report-extras.js +22 -5
  74. package/dist/src/core/services/doctor-service.d.ts +2 -0
  75. package/dist/src/core/services/doctor-service.js +1 -1
  76. package/dist/src/core/services/failure-policy.js +1 -1
  77. package/dist/src/core/services/injection-policy.d.ts +1 -0
  78. package/dist/src/core/services/injection-policy.js +1 -1
  79. package/dist/src/core/services/installation-adapters.d.ts +2 -1
  80. package/dist/src/core/services/installation-adapters.js +3 -1
  81. package/dist/src/core/services/installation-builder.js +3 -2
  82. package/dist/src/core/services/installation-service.js +1 -1
  83. package/dist/src/core/services/manifest-change.d.ts +2 -1
  84. package/dist/src/core/services/manifest-change.js +2 -1
  85. package/dist/src/core/services/session-reset-handler.js +1 -1
  86. package/dist/src/core/services/skill-candidates.d.ts +16 -0
  87. package/dist/src/core/services/skill-candidates.js +14 -0
  88. package/dist/src/core/services/skill-invocation.d.ts +4 -0
  89. package/dist/src/core/services/skill-invocation.js +12 -0
  90. package/dist/src/core/services/skill-ranking.d.ts +7 -0
  91. package/dist/src/core/services/skill-ranking.js +25 -0
  92. package/dist/src/core/services/snapshot-merge.d.ts +1 -0
  93. package/dist/src/core/services/snapshot-merge.js +12 -5
  94. package/dist/src/core/services/trigger-text.d.ts +5 -0
  95. package/dist/src/core/services/trigger-text.js +4 -0
  96. package/dist/src/core/services/zone-guidance.d.ts +13 -2
  97. package/dist/src/core/services/zone-guidance.js +14 -8
  98. package/dist/src/infrastructure/harnesses/antigravity-cli/runtime.js +2 -1
  99. package/dist/src/infrastructure/harnesses/antigravity-cli/skill-folders.d.ts +1 -0
  100. package/dist/src/infrastructure/harnesses/antigravity-cli/skill-folders.js +1 -0
  101. package/dist/src/infrastructure/harnesses/antigravity-cli/skill-invocation.d.ts +2 -0
  102. package/dist/src/infrastructure/harnesses/antigravity-cli/skill-invocation.js +2 -0
  103. package/dist/src/infrastructure/harnesses/claude-code/adapter.js +6 -4
  104. package/dist/src/infrastructure/harnesses/claude-code/auto-restart-diagnostics.d.ts +1 -0
  105. package/dist/src/infrastructure/harnesses/claude-code/auto-restart-diagnostics.js +2 -2
  106. package/dist/src/infrastructure/harnesses/claude-code/auto-restart-planner.d.ts +5 -1
  107. package/dist/src/infrastructure/harnesses/claude-code/auto-restart-planner.js +6 -6
  108. package/dist/src/infrastructure/harnesses/claude-code/capabilities.js +1 -1
  109. package/dist/src/infrastructure/harnesses/claude-code/context-readings-scan.d.ts +2 -0
  110. package/dist/src/infrastructure/harnesses/claude-code/context-readings-scan.js +21 -0
  111. package/dist/src/infrastructure/harnesses/claude-code/context-source.d.ts +10 -0
  112. package/dist/src/infrastructure/harnesses/claude-code/context-source.js +16 -0
  113. package/dist/src/infrastructure/harnesses/claude-code/mod/context-reading.d.ts +2 -0
  114. package/dist/src/infrastructure/harnesses/claude-code/mod/context-reading.js +67 -0
  115. package/dist/src/infrastructure/harnesses/claude-code/mod/host.d.ts +9 -0
  116. package/dist/src/infrastructure/harnesses/claude-code/mod/mod-info.d.ts +1 -1
  117. package/dist/src/infrastructure/harnesses/claude-code/mod/mod-info.js +1 -1
  118. package/dist/src/infrastructure/harnesses/claude-code/mod/mod-log.d.ts +1 -0
  119. package/dist/src/infrastructure/harnesses/claude-code/mod/mod-log.js +3 -0
  120. package/dist/src/infrastructure/harnesses/claude-code/mod/register.js +6 -1
  121. package/dist/src/infrastructure/harnesses/claude-code/mod-reading.d.ts +6 -0
  122. package/dist/src/infrastructure/harnesses/claude-code/mod-reading.js +37 -0
  123. package/dist/src/infrastructure/harnesses/claude-code/planner.d.ts +2 -1
  124. package/dist/src/infrastructure/harnesses/claude-code/planner.js +4 -4
  125. package/dist/src/infrastructure/harnesses/claude-code/project-root.d.ts +2 -0
  126. package/dist/src/infrastructure/harnesses/claude-code/project-root.js +5 -0
  127. package/dist/src/infrastructure/harnesses/claude-code/runtime.js +12 -7
  128. package/dist/src/infrastructure/harnesses/claude-code/skill-folders.d.ts +1 -0
  129. package/dist/src/infrastructure/harnesses/claude-code/skill-folders.js +1 -0
  130. package/dist/src/infrastructure/harnesses/claude-code/skill-invocation.d.ts +2 -0
  131. package/dist/src/infrastructure/harnesses/claude-code/skill-invocation.js +2 -0
  132. package/dist/src/infrastructure/harnesses/claude-code/statusline-context-window.js +15 -2
  133. package/dist/src/infrastructure/harnesses/claude-code/statusline-diagnostics.js +3 -3
  134. package/dist/src/infrastructure/harnesses/claude-code/statusline-migration.d.ts +3 -0
  135. package/dist/src/infrastructure/harnesses/claude-code/statusline-migration.js +23 -0
  136. package/dist/src/infrastructure/harnesses/claude-code/statusline-planner.d.ts +2 -1
  137. package/dist/src/infrastructure/harnesses/claude-code/statusline-planner.js +7 -4
  138. package/dist/src/infrastructure/harnesses/claude-code/user-settings.d.ts +2 -0
  139. package/dist/src/infrastructure/harnesses/claude-code/user-settings.js +11 -0
  140. package/dist/src/infrastructure/harnesses/codex-cli/adapter.js +5 -4
  141. package/dist/src/infrastructure/harnesses/codex-cli/runtime.js +2 -1
  142. package/dist/src/infrastructure/harnesses/codex-cli/skill-folders.d.ts +1 -0
  143. package/dist/src/infrastructure/harnesses/codex-cli/skill-folders.js +1 -0
  144. package/dist/src/infrastructure/harnesses/codex-cli/skill-invocation.d.ts +2 -0
  145. package/dist/src/infrastructure/harnesses/codex-cli/skill-invocation.js +2 -0
  146. package/dist/src/infrastructure/harnesses/common/config-origin.d.ts +10 -0
  147. package/dist/src/infrastructure/harnesses/common/config-origin.js +52 -0
  148. package/dist/src/infrastructure/harnesses/cursor/adapter.js +5 -4
  149. package/dist/src/infrastructure/harnesses/cursor/runtime.js +2 -1
  150. package/dist/src/infrastructure/harnesses/cursor/skill-folders.d.ts +1 -0
  151. package/dist/src/infrastructure/harnesses/cursor/skill-folders.js +1 -0
  152. package/dist/src/infrastructure/harnesses/cursor/skill-invocation.d.ts +2 -0
  153. package/dist/src/infrastructure/harnesses/cursor/skill-invocation.js +2 -0
  154. package/dist/src/infrastructure/harnesses/github-copilot-cli/runtime.js +2 -1
  155. package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-folders.d.ts +1 -0
  156. package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-folders.js +1 -0
  157. package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-invocation.d.ts +2 -0
  158. package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-invocation.js +2 -0
  159. package/dist/src/infrastructure/harnesses/oh-my-pi/runtime.js +2 -1
  160. package/dist/src/infrastructure/harnesses/oh-my-pi/skill-folders.d.ts +1 -0
  161. package/dist/src/infrastructure/harnesses/oh-my-pi/skill-folders.js +1 -0
  162. package/dist/src/infrastructure/harnesses/oh-my-pi/skill-invocation.d.ts +2 -0
  163. package/dist/src/infrastructure/harnesses/oh-my-pi/skill-invocation.js +2 -0
  164. package/dist/src/infrastructure/harnesses/opencode/runtime.js +2 -1
  165. package/dist/src/infrastructure/harnesses/opencode/skill-folders.d.ts +1 -0
  166. package/dist/src/infrastructure/harnesses/opencode/skill-folders.js +1 -0
  167. package/dist/src/infrastructure/harnesses/opencode/skill-invocation.d.ts +2 -0
  168. package/dist/src/infrastructure/harnesses/opencode/skill-invocation.js +2 -0
  169. package/dist/src/infrastructure/harnesses/pi/runtime.js +2 -1
  170. package/dist/src/infrastructure/harnesses/pi/skill-folders.d.ts +1 -0
  171. package/dist/src/infrastructure/harnesses/pi/skill-folders.js +1 -0
  172. package/dist/src/infrastructure/harnesses/pi/skill-invocation.d.ts +2 -0
  173. package/dist/src/infrastructure/harnesses/pi/skill-invocation.js +2 -0
  174. package/dist/src/infrastructure/harnesses/skill-folders.d.ts +3 -0
  175. package/dist/src/infrastructure/harnesses/skill-folders.js +21 -0
  176. package/dist/src/infrastructure/harnesses/skill-invocations.d.ts +4 -0
  177. package/dist/src/infrastructure/harnesses/skill-invocations.js +21 -0
  178. package/dist/src/infrastructure/runtime/runtime-paths.js +2 -1
  179. package/dist/src/infrastructure/skills/frontmatter.d.ts +5 -0
  180. package/dist/src/infrastructure/skills/frontmatter.js +25 -0
  181. package/dist/src/infrastructure/skills/skill-catalog-reader.d.ts +4 -0
  182. package/dist/src/infrastructure/skills/skill-catalog-reader.js +78 -0
  183. package/dist/src/infrastructure/skills/skill-catalog.d.ts +2 -0
  184. package/dist/src/infrastructure/skills/skill-catalog.js +5 -0
  185. package/dist/src/infrastructure/storage/change-applier.d.ts +1 -0
  186. package/dist/src/infrastructure/storage/change-applier.js +2 -1
  187. package/dist/src/infrastructure/storage/json-document-editor.js +2 -1
  188. package/dist/src/infrastructure/storage/json-node-removal.d.ts +2 -0
  189. package/dist/src/infrastructure/storage/json-node-removal.js +70 -0
  190. package/dist/src/infrastructure/storage/json-span-utils.d.ts +1 -1
  191. package/dist/src/infrastructure/storage/json-span-utils.js +1 -48
  192. package/docs/telemetry-block.md +3 -1
  193. package/package.json +1 -1
  194. package/schemas/context-brake.config.schema.json +9 -4
  195. package/schemas/doctor-report.schema.json +51 -0
@@ -4,6 +4,14 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/core/services/skill-invocation.ts
8
+ function sentenceInvocation(skill) {
9
+ return `use the ${skill} skill`;
10
+ }
11
+
12
+ // src/infrastructure/harnesses/oh-my-pi/skill-invocation.ts
13
+ var OMP_SKILL_INVOCATION = sentenceInvocation;
14
+
7
15
  // node_modules/zod/v4/mini/external.js
8
16
  var external_exports = {};
9
17
  __export(external_exports, {
@@ -18155,6 +18163,34 @@ var HARNESS_IDS = [
18155
18163
  "antigravity-cli"
18156
18164
  ];
18157
18165
 
18166
+ // src/core/contracts/skill-name.ts
18167
+ var MAX_SKILL_NAME_LENGTH = 64;
18168
+ var NAME_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
18169
+ var NAMESPACE_SEPARATOR = ":";
18170
+ var INVOCATION_CHARACTERS = ["/", "$", "@"];
18171
+ var MAX_QUALIFIED_LENGTH = MAX_SKILL_NAME_LENGTH * 2 + NAMESPACE_SEPARATOR.length;
18172
+ function segmentFits(end) {
18173
+ return `(?=[^${NAMESPACE_SEPARATOR}]{1,${MAX_SKILL_NAME_LENGTH}}${end})`;
18174
+ }
18175
+ var PREFIX = `(?:${segmentFits(NAMESPACE_SEPARATOR)}${NAME_SEGMENT}${NAMESPACE_SEPARATOR})?`;
18176
+ var SKILL_NAME_PATTERN = new RegExp(`^${PREFIX}${segmentFits("$")}${NAME_SEGMENT}$`);
18177
+ var SHAPE_RULE = `lowercase letters, digits, and single hyphens, up to ${MAX_SKILL_NAME_LENGTH} characters, optionally prefixed by a plugin name and a colon`;
18178
+ function isSkillName(value) {
18179
+ return SKILL_NAME_PATTERN.test(value);
18180
+ }
18181
+ function skillNameProblem(value) {
18182
+ if (value === "") return "must not be empty";
18183
+ if (isSkillName(value)) return null;
18184
+ const bare = INVOCATION_CHARACTERS.includes(value.charAt(0)) ? value.slice(1) : null;
18185
+ if (bare !== null && isSkillName(bare)) return `"${value}" is not a skill name; use the bare name ${bare}`;
18186
+ return `"${value}" is not a skill name; use ${SHAPE_RULE}`;
18187
+ }
18188
+ var skillNameSchema = external_exports.string().check((ctx) => {
18189
+ const message = skillNameProblem(ctx.value);
18190
+ if (message !== null) ctx.issues.push({ code: "custom", input: ctx.value, message });
18191
+ });
18192
+ external_exports.globalRegistry.add(skillNameSchema, { pattern: SKILL_NAME_PATTERN.source, maxLength: MAX_QUALIFIED_LENGTH });
18193
+
18158
18194
  // src/core/contracts/zones.ts
18159
18195
  var ZONES = ["GREEN", "YELLOW", "RED", "CRITICAL"];
18160
18196
  var USAGE_SOURCES = ["measured", "estimated"];
@@ -18178,15 +18214,8 @@ function uniqueCheck(message) {
18178
18214
  if (!isUnique(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message });
18179
18215
  };
18180
18216
  }
18181
- var MAX_AGENT_COMMAND_LENGTH = 200;
18182
- var TRIMMED_RULE = "must not have leading or trailing whitespace";
18183
18217
  var RESUME_REQUIRES_COMMAND_RULE = "requires snapshot.command";
18184
- var SINGLE_LINE_RULE = "must be a single line";
18185
- var agentCommand = external_exports.string().check(external_exports.minLength(1), external_exports.maxLength(MAX_AGENT_COMMAND_LENGTH), (ctx) => {
18186
- if (ctx.value.trim() !== ctx.value) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: TRIMMED_RULE });
18187
- if (/[\r\n]/.test(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: SINGLE_LINE_RULE });
18188
- });
18189
- var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), command: external_exports.optional(agentCommand), resumeCommand: external_exports.optional(agentCommand) }).check((ctx) => {
18218
+ var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), triggerPercentage: external_exports.optional(external_exports.int().check(external_exports.minimum(1), external_exports.maximum(100))), command: external_exports.optional(skillNameSchema), resumeCommand: external_exports.optional(skillNameSchema) }).check((ctx) => {
18190
18219
  if (ctx.value.resumeCommand !== void 0 && ctx.value.command === void 0) addIssue(ctx, { code: "custom", path: ["resumeCommand"], input: ctx.value.resumeCommand, message: RESUME_REQUIRES_COMMAND_RULE });
18191
18220
  });
18192
18221
  var DEFAULT_SNAPSHOT = { triggerZone: "RED" };
@@ -18267,21 +18296,25 @@ var GENERIC_ACTIONS = {
18267
18296
  RED: "finish or pause the current unit and tell the user what remains",
18268
18297
  CRITICAL: "stop starting new work; tell the user what remains"
18269
18298
  };
18270
- function zoneAction(zone, snapshot, mode = "off") {
18271
- if (zone === "GREEN" || !isAtOrAbove(zone, snapshot.triggerZone)) return GENERIC_ACTIONS[zone];
18272
- if (snapshot.command !== void 0) return snapshotAction(zone, snapshot.command);
18273
- if (mode !== "handoff") return GENERIC_ACTIONS[zone];
18299
+ function isResetDue(reading, snapshot) {
18300
+ if (snapshot.triggerPercentage !== void 0) return reading.usagePercentage >= snapshot.triggerPercentage;
18301
+ return reading.zone !== "GREEN" && isAtOrAbove(reading.zone, snapshot.triggerZone);
18302
+ }
18303
+ function zoneAction(zone, snapshot, options = {}) {
18304
+ if (!isResetDue({ zone, usagePercentage: options.usagePercentage ?? 0 }, snapshot)) return GENERIC_ACTIONS[zone];
18305
+ if (snapshot.command !== void 0) return snapshotAction(zone, (options.invoke ?? sentenceInvocation)(snapshot.command));
18306
+ if ((options.mode ?? "off") !== "handoff") return GENERIC_ACTIONS[zone];
18274
18307
  return `save handoff to ${HANDOFF_PATH}, end reply with ${SESSION_RESET_SIGNAL}`;
18275
18308
  }
18276
- function snapshotAction(zone, command) {
18309
+ function snapshotAction(zone, invocation) {
18277
18310
  const now = zone === "CRITICAL" ? " now" : "";
18278
- return `run "${command}"${now}, then end reply with ${SESSION_RESET_SIGNAL}`;
18311
+ return `run "${invocation}"${now}, then end reply with ${SESSION_RESET_SIGNAL}`;
18279
18312
  }
18280
18313
  function handoffResumeText(archivedPath) {
18281
18314
  return `${RESUME_PREFIX} Read "${archivedPath}" and continue the previous work from it.`;
18282
18315
  }
18283
- function resumeText(snapshot) {
18284
- return snapshot.resumeCommand === void 0 ? null : `${RESUME_PREFIX} Run "${snapshot.resumeCommand}" before continuing.`;
18316
+ function resumeText(snapshot, invoke = sentenceInvocation) {
18317
+ return snapshot.resumeCommand === void 0 ? null : `${RESUME_PREFIX} Run "${invoke(snapshot.resumeCommand)}" before continuing.`;
18285
18318
  }
18286
18319
  function isAtOrAbove(zone, trigger) {
18287
18320
  return ZONES.indexOf(zone) >= ZONES.indexOf(trigger);
@@ -18326,7 +18359,7 @@ async function resolveFailure(input2) {
18326
18359
  return { kind: "neutral" };
18327
18360
  }
18328
18361
  function deadlineBootDecision(input2) {
18329
- const text = resumeText((input2.config ?? DEFAULT_CONFIG).snapshot);
18362
+ const text = resumeText((input2.config ?? DEFAULT_CONFIG).snapshot, input2.descriptor?.skillInvocation);
18330
18363
  return text === null ? { kind: "neutral" } : { kind: "context", block: text };
18331
18364
  }
18332
18365
  async function recordRuntimeFailure(errors, record2) {
@@ -18474,7 +18507,20 @@ async function listRuntimeStateFiles(root, readdirFn = readdir) {
18474
18507
  import { createHash } from "node:crypto";
18475
18508
  import { mkdir, writeFile } from "node:fs/promises";
18476
18509
  import { join as join2, resolve as resolve2 } from "node:path";
18477
- var RUNTIME_GITIGNORE_CONTENT = "*\n";
18510
+
18511
+ // src/core/contracts/context-reading.ts
18512
+ var RUNTIME_IGNORE_CONTENT = "*\n";
18513
+ var PERCENTAGE_MAXIMUM = 100;
18514
+ var contextReadingSchema = external_exports.strictObject({
18515
+ v: external_exports.literal(1),
18516
+ at: external_exports.string().check(external_exports.minLength(1)),
18517
+ windowTokens: external_exports.nullable(external_exports.int().check(external_exports.positive())),
18518
+ inputTokens: external_exports.nullable(external_exports.int().check(external_exports.nonnegative())),
18519
+ usedPercentage: external_exports.nullable(external_exports.number().check(external_exports.gte(0), external_exports.lte(PERCENTAGE_MAXIMUM)))
18520
+ });
18521
+
18522
+ // src/infrastructure/runtime/runtime-paths.ts
18523
+ var RUNTIME_GITIGNORE_CONTENT = RUNTIME_IGNORE_CONTENT;
18478
18524
  var SESSIONS_DIR_NAME = "sessions";
18479
18525
  var LEDGER_FILE_EXTENSION = ".jsonl";
18480
18526
  var SESSIONS_RELATIVE_PREFIX = `${RUNTIME_STATE_RELATIVE_DIR}/${SESSIONS_DIR_NAME}/`;
@@ -18582,7 +18628,7 @@ function isDebugModeInEffect(config2) {
18582
18628
 
18583
18629
  // src/core/services/injection-policy.ts
18584
18630
  function decideInjection(input2) {
18585
- if (input2.debug || input2.telemetry.injectionMode === "always") return true;
18631
+ if (input2.debug || input2.resetDue === true || input2.telemetry.injectionMode === "always") return true;
18586
18632
  return input2.zone !== "GREEN" || input2.usagePercentage >= input2.telemetry.activationThresholdPercentage;
18587
18633
  }
18588
18634
 
@@ -18733,7 +18779,7 @@ async function handleSessionReset(options, event, hooks = {}) {
18733
18779
  if (!options.descriptor.capabilities.some((entry) => entry.id === "session_boot" && entry.state === "supported")) return NEUTRAL;
18734
18780
  if (event.reason === "compact" && !COMPACTION_BOOT_HARNESSES.includes(options.descriptor.harness)) return NEUTRAL;
18735
18781
  hooks.onPhase?.("guidance");
18736
- const text = resumeText(options.config.snapshot) ?? await claimHandoff(options, event.reason, hooks.deadline);
18782
+ const text = resumeText(options.config.snapshot, options.descriptor.skillInvocation) ?? await claimHandoff(options, event.reason, hooks.deadline);
18737
18783
  return text === null ? NEUTRAL : { kind: "context", block: text };
18738
18784
  }
18739
18785
  async function claimHandoff(options, reason, deadline) {
@@ -18775,8 +18821,9 @@ async function handlePreInvocation(options, event, input2) {
18775
18821
  return telemetryDecision(options, summary.turns, readZone(options, { summary, turns: summary.turns, observedCharacters: 0, measured: input2.measured }));
18776
18822
  }
18777
18823
  async function telemetryDecision(options, turn, view) {
18778
- if (!decideInjection({ telemetry: options.config.telemetry, zone: view.zone, usagePercentage: view.percentage, debug: isDebugModeInEffect(options.config) })) return NEUTRAL2;
18779
- const action = zoneAction(view.zone, options.config.snapshot, restartMode(options.config));
18824
+ const resetDue = isResetDue({ zone: view.zone, usagePercentage: view.percentage }, options.config.snapshot);
18825
+ if (!decideInjection({ telemetry: options.config.telemetry, zone: view.zone, usagePercentage: view.percentage, debug: isDebugModeInEffect(options.config), resetDue })) return NEUTRAL2;
18826
+ const action = zoneAction(view.zone, options.config.snapshot, { mode: restartMode(options.config), invoke: options.descriptor.skillInvocation, usagePercentage: view.percentage });
18780
18827
  return { kind: "context", block: renderTelemetryBlock({ turn, turnCeiling: redStartTurn(options.config.telemetry.zones), usagePercentage: view.percentage, usage: view.reading, zone: view.zone, action, debug: isDebugModeInEffect(options.config) }) };
18781
18828
  }
18782
18829
  async function handleResponseEnd(options, event) {
@@ -19173,7 +19220,7 @@ function renderOmpToolResult(payload, block) {
19173
19220
  // src/infrastructure/harnesses/oh-my-pi/runtime.ts
19174
19221
  var HARNESS = "oh-my-pi";
19175
19222
  var ESTIMATION = { baselineTokens: 15e3, tokensPerTurn: 150 };
19176
- var ompDescriptor = { harness: HARNESS, capabilities: OMP_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: "/new" };
19223
+ var ompDescriptor = { harness: HARNESS, capabilities: OMP_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: "/new", skillInvocation: OMP_SKILL_INVOCATION };
19177
19224
  function rootOf(context) {
19178
19225
  return textValue(context.cwd);
19179
19226
  }
@@ -18308,6 +18308,34 @@ async function handleTurnEnd(host, turn) {
18308
18308
  });
18309
18309
  }
18310
18310
 
18311
+ // src/core/contracts/skill-name.ts
18312
+ var MAX_SKILL_NAME_LENGTH = 64;
18313
+ var NAME_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
18314
+ var NAMESPACE_SEPARATOR = ":";
18315
+ var INVOCATION_CHARACTERS = ["/", "$", "@"];
18316
+ var MAX_QUALIFIED_LENGTH = MAX_SKILL_NAME_LENGTH * 2 + NAMESPACE_SEPARATOR.length;
18317
+ function segmentFits(end) {
18318
+ return `(?=[^${NAMESPACE_SEPARATOR}]{1,${MAX_SKILL_NAME_LENGTH}}${end})`;
18319
+ }
18320
+ var PREFIX = `(?:${segmentFits(NAMESPACE_SEPARATOR)}${NAME_SEGMENT}${NAMESPACE_SEPARATOR})?`;
18321
+ var SKILL_NAME_PATTERN = new RegExp(`^${PREFIX}${segmentFits("$")}${NAME_SEGMENT}$`);
18322
+ var SHAPE_RULE = `lowercase letters, digits, and single hyphens, up to ${MAX_SKILL_NAME_LENGTH} characters, optionally prefixed by a plugin name and a colon`;
18323
+ function isSkillName(value) {
18324
+ return SKILL_NAME_PATTERN.test(value);
18325
+ }
18326
+ function skillNameProblem(value) {
18327
+ if (value === "") return "must not be empty";
18328
+ if (isSkillName(value)) return null;
18329
+ const bare = INVOCATION_CHARACTERS.includes(value.charAt(0)) ? value.slice(1) : null;
18330
+ if (bare !== null && isSkillName(bare)) return `"${value}" is not a skill name; use the bare name ${bare}`;
18331
+ return `"${value}" is not a skill name; use ${SHAPE_RULE}`;
18332
+ }
18333
+ var skillNameSchema = external_exports.string().check((ctx) => {
18334
+ const message = skillNameProblem(ctx.value);
18335
+ if (message !== null) ctx.issues.push({ code: "custom", input: ctx.value, message });
18336
+ });
18337
+ external_exports.globalRegistry.add(skillNameSchema, { pattern: SKILL_NAME_PATTERN.source, maxLength: MAX_QUALIFIED_LENGTH });
18338
+
18311
18339
  // src/core/contracts/zones.ts
18312
18340
  var ZONES = ["GREEN", "YELLOW", "RED", "CRITICAL"];
18313
18341
  var USAGE_SOURCES = ["measured", "estimated"];
@@ -18331,15 +18359,8 @@ function uniqueCheck(message) {
18331
18359
  if (!isUnique(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message });
18332
18360
  };
18333
18361
  }
18334
- var MAX_AGENT_COMMAND_LENGTH = 200;
18335
- var TRIMMED_RULE = "must not have leading or trailing whitespace";
18336
18362
  var RESUME_REQUIRES_COMMAND_RULE = "requires snapshot.command";
18337
- var SINGLE_LINE_RULE = "must be a single line";
18338
- var agentCommand = external_exports.string().check(external_exports.minLength(1), external_exports.maxLength(MAX_AGENT_COMMAND_LENGTH), (ctx) => {
18339
- if (ctx.value.trim() !== ctx.value) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: TRIMMED_RULE });
18340
- if (/[\r\n]/.test(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: SINGLE_LINE_RULE });
18341
- });
18342
- var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), command: external_exports.optional(agentCommand), resumeCommand: external_exports.optional(agentCommand) }).check((ctx) => {
18363
+ var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), triggerPercentage: external_exports.optional(external_exports.int().check(external_exports.minimum(1), external_exports.maximum(100))), command: external_exports.optional(skillNameSchema), resumeCommand: external_exports.optional(skillNameSchema) }).check((ctx) => {
18343
18364
  if (ctx.value.resumeCommand !== void 0 && ctx.value.command === void 0) addIssue(ctx, { code: "custom", path: ["resumeCommand"], input: ctx.value.resumeCommand, message: RESUME_REQUIRES_COMMAND_RULE });
18344
18365
  });
18345
18366
  var DEFAULT_SNAPSHOT = { triggerZone: "RED" };
@@ -18383,11 +18404,22 @@ var HANDOFF_ARCHIVE_LIMIT = 10;
18383
18404
  import { mkdir, writeFile } from "node:fs/promises";
18384
18405
  import { join, resolve } from "node:path";
18385
18406
 
18407
+ // src/core/contracts/context-reading.ts
18408
+ var RUNTIME_IGNORE_CONTENT = "*\n";
18409
+ var PERCENTAGE_MAXIMUM = 100;
18410
+ var contextReadingSchema = external_exports.strictObject({
18411
+ v: external_exports.literal(1),
18412
+ at: external_exports.string().check(external_exports.minLength(1)),
18413
+ windowTokens: external_exports.nullable(external_exports.int().check(external_exports.positive())),
18414
+ inputTokens: external_exports.nullable(external_exports.int().check(external_exports.nonnegative())),
18415
+ usedPercentage: external_exports.nullable(external_exports.number().check(external_exports.gte(0), external_exports.lte(PERCENTAGE_MAXIMUM)))
18416
+ });
18417
+
18386
18418
  // src/infrastructure/storage/runtime-state-files.ts
18387
18419
  var RUNTIME_STATE_RELATIVE_DIR = ".context-brake/runtime";
18388
18420
 
18389
18421
  // src/infrastructure/runtime/runtime-paths.ts
18390
- var RUNTIME_GITIGNORE_CONTENT = "*\n";
18422
+ var RUNTIME_GITIGNORE_CONTENT = RUNTIME_IGNORE_CONTENT;
18391
18423
  var SESSIONS_DIR_NAME = "sessions";
18392
18424
  var SESSIONS_RELATIVE_PREFIX = `${RUNTIME_STATE_RELATIVE_DIR}/${SESSIONS_DIR_NAME}/`;
18393
18425
  function runtimeDirectory(projectRoot) {
@@ -4,6 +4,14 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/core/services/skill-invocation.ts
8
+ function sentenceInvocation(skill) {
9
+ return `use the ${skill} skill`;
10
+ }
11
+
12
+ // src/infrastructure/harnesses/opencode/skill-invocation.ts
13
+ var OPENCODE_SKILL_INVOCATION = sentenceInvocation;
14
+
7
15
  // node_modules/zod/v4/mini/external.js
8
16
  var external_exports = {};
9
17
  __export(external_exports, {
@@ -18155,6 +18163,34 @@ var HARNESS_IDS = [
18155
18163
  "antigravity-cli"
18156
18164
  ];
18157
18165
 
18166
+ // src/core/contracts/skill-name.ts
18167
+ var MAX_SKILL_NAME_LENGTH = 64;
18168
+ var NAME_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
18169
+ var NAMESPACE_SEPARATOR = ":";
18170
+ var INVOCATION_CHARACTERS = ["/", "$", "@"];
18171
+ var MAX_QUALIFIED_LENGTH = MAX_SKILL_NAME_LENGTH * 2 + NAMESPACE_SEPARATOR.length;
18172
+ function segmentFits(end) {
18173
+ return `(?=[^${NAMESPACE_SEPARATOR}]{1,${MAX_SKILL_NAME_LENGTH}}${end})`;
18174
+ }
18175
+ var PREFIX = `(?:${segmentFits(NAMESPACE_SEPARATOR)}${NAME_SEGMENT}${NAMESPACE_SEPARATOR})?`;
18176
+ var SKILL_NAME_PATTERN = new RegExp(`^${PREFIX}${segmentFits("$")}${NAME_SEGMENT}$`);
18177
+ var SHAPE_RULE = `lowercase letters, digits, and single hyphens, up to ${MAX_SKILL_NAME_LENGTH} characters, optionally prefixed by a plugin name and a colon`;
18178
+ function isSkillName(value) {
18179
+ return SKILL_NAME_PATTERN.test(value);
18180
+ }
18181
+ function skillNameProblem(value) {
18182
+ if (value === "") return "must not be empty";
18183
+ if (isSkillName(value)) return null;
18184
+ const bare = INVOCATION_CHARACTERS.includes(value.charAt(0)) ? value.slice(1) : null;
18185
+ if (bare !== null && isSkillName(bare)) return `"${value}" is not a skill name; use the bare name ${bare}`;
18186
+ return `"${value}" is not a skill name; use ${SHAPE_RULE}`;
18187
+ }
18188
+ var skillNameSchema = external_exports.string().check((ctx) => {
18189
+ const message = skillNameProblem(ctx.value);
18190
+ if (message !== null) ctx.issues.push({ code: "custom", input: ctx.value, message });
18191
+ });
18192
+ external_exports.globalRegistry.add(skillNameSchema, { pattern: SKILL_NAME_PATTERN.source, maxLength: MAX_QUALIFIED_LENGTH });
18193
+
18158
18194
  // src/core/contracts/zones.ts
18159
18195
  var ZONES = ["GREEN", "YELLOW", "RED", "CRITICAL"];
18160
18196
  var USAGE_SOURCES = ["measured", "estimated"];
@@ -18178,15 +18214,8 @@ function uniqueCheck(message) {
18178
18214
  if (!isUnique(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message });
18179
18215
  };
18180
18216
  }
18181
- var MAX_AGENT_COMMAND_LENGTH = 200;
18182
- var TRIMMED_RULE = "must not have leading or trailing whitespace";
18183
18217
  var RESUME_REQUIRES_COMMAND_RULE = "requires snapshot.command";
18184
- var SINGLE_LINE_RULE = "must be a single line";
18185
- var agentCommand = external_exports.string().check(external_exports.minLength(1), external_exports.maxLength(MAX_AGENT_COMMAND_LENGTH), (ctx) => {
18186
- if (ctx.value.trim() !== ctx.value) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: TRIMMED_RULE });
18187
- if (/[\r\n]/.test(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: SINGLE_LINE_RULE });
18188
- });
18189
- var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), command: external_exports.optional(agentCommand), resumeCommand: external_exports.optional(agentCommand) }).check((ctx) => {
18218
+ var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), triggerPercentage: external_exports.optional(external_exports.int().check(external_exports.minimum(1), external_exports.maximum(100))), command: external_exports.optional(skillNameSchema), resumeCommand: external_exports.optional(skillNameSchema) }).check((ctx) => {
18190
18219
  if (ctx.value.resumeCommand !== void 0 && ctx.value.command === void 0) addIssue(ctx, { code: "custom", path: ["resumeCommand"], input: ctx.value.resumeCommand, message: RESUME_REQUIRES_COMMAND_RULE });
18191
18220
  });
18192
18221
  var DEFAULT_SNAPSHOT = { triggerZone: "RED" };
@@ -18267,21 +18296,25 @@ var GENERIC_ACTIONS = {
18267
18296
  RED: "finish or pause the current unit and tell the user what remains",
18268
18297
  CRITICAL: "stop starting new work; tell the user what remains"
18269
18298
  };
18270
- function zoneAction(zone, snapshot, mode = "off") {
18271
- if (zone === "GREEN" || !isAtOrAbove(zone, snapshot.triggerZone)) return GENERIC_ACTIONS[zone];
18272
- if (snapshot.command !== void 0) return snapshotAction(zone, snapshot.command);
18273
- if (mode !== "handoff") return GENERIC_ACTIONS[zone];
18299
+ function isResetDue(reading, snapshot) {
18300
+ if (snapshot.triggerPercentage !== void 0) return reading.usagePercentage >= snapshot.triggerPercentage;
18301
+ return reading.zone !== "GREEN" && isAtOrAbove(reading.zone, snapshot.triggerZone);
18302
+ }
18303
+ function zoneAction(zone, snapshot, options = {}) {
18304
+ if (!isResetDue({ zone, usagePercentage: options.usagePercentage ?? 0 }, snapshot)) return GENERIC_ACTIONS[zone];
18305
+ if (snapshot.command !== void 0) return snapshotAction(zone, (options.invoke ?? sentenceInvocation)(snapshot.command));
18306
+ if ((options.mode ?? "off") !== "handoff") return GENERIC_ACTIONS[zone];
18274
18307
  return `save handoff to ${HANDOFF_PATH}, end reply with ${SESSION_RESET_SIGNAL}`;
18275
18308
  }
18276
- function snapshotAction(zone, command) {
18309
+ function snapshotAction(zone, invocation) {
18277
18310
  const now = zone === "CRITICAL" ? " now" : "";
18278
- return `run "${command}"${now}, then end reply with ${SESSION_RESET_SIGNAL}`;
18311
+ return `run "${invocation}"${now}, then end reply with ${SESSION_RESET_SIGNAL}`;
18279
18312
  }
18280
18313
  function handoffResumeText(archivedPath) {
18281
18314
  return `${RESUME_PREFIX} Read "${archivedPath}" and continue the previous work from it.`;
18282
18315
  }
18283
- function resumeText(snapshot) {
18284
- return snapshot.resumeCommand === void 0 ? null : `${RESUME_PREFIX} Run "${snapshot.resumeCommand}" before continuing.`;
18316
+ function resumeText(snapshot, invoke = sentenceInvocation) {
18317
+ return snapshot.resumeCommand === void 0 ? null : `${RESUME_PREFIX} Run "${invoke(snapshot.resumeCommand)}" before continuing.`;
18285
18318
  }
18286
18319
  function isAtOrAbove(zone, trigger) {
18287
18320
  return ZONES.indexOf(zone) >= ZONES.indexOf(trigger);
@@ -18326,7 +18359,7 @@ async function resolveFailure(input2) {
18326
18359
  return { kind: "neutral" };
18327
18360
  }
18328
18361
  function deadlineBootDecision(input2) {
18329
- const text = resumeText((input2.config ?? DEFAULT_CONFIG).snapshot);
18362
+ const text = resumeText((input2.config ?? DEFAULT_CONFIG).snapshot, input2.descriptor?.skillInvocation);
18330
18363
  return text === null ? { kind: "neutral" } : { kind: "context", block: text };
18331
18364
  }
18332
18365
  async function recordRuntimeFailure(errors, record2) {
@@ -18474,7 +18507,20 @@ async function listRuntimeStateFiles(root, readdirFn = readdir) {
18474
18507
  import { createHash } from "node:crypto";
18475
18508
  import { mkdir, writeFile } from "node:fs/promises";
18476
18509
  import { join as join2, resolve as resolve2 } from "node:path";
18477
- var RUNTIME_GITIGNORE_CONTENT = "*\n";
18510
+
18511
+ // src/core/contracts/context-reading.ts
18512
+ var RUNTIME_IGNORE_CONTENT = "*\n";
18513
+ var PERCENTAGE_MAXIMUM = 100;
18514
+ var contextReadingSchema = external_exports.strictObject({
18515
+ v: external_exports.literal(1),
18516
+ at: external_exports.string().check(external_exports.minLength(1)),
18517
+ windowTokens: external_exports.nullable(external_exports.int().check(external_exports.positive())),
18518
+ inputTokens: external_exports.nullable(external_exports.int().check(external_exports.nonnegative())),
18519
+ usedPercentage: external_exports.nullable(external_exports.number().check(external_exports.gte(0), external_exports.lte(PERCENTAGE_MAXIMUM)))
18520
+ });
18521
+
18522
+ // src/infrastructure/runtime/runtime-paths.ts
18523
+ var RUNTIME_GITIGNORE_CONTENT = RUNTIME_IGNORE_CONTENT;
18478
18524
  var SESSIONS_DIR_NAME = "sessions";
18479
18525
  var LEDGER_FILE_EXTENSION = ".jsonl";
18480
18526
  var SESSIONS_RELATIVE_PREFIX = `${RUNTIME_STATE_RELATIVE_DIR}/${SESSIONS_DIR_NAME}/`;
@@ -18582,7 +18628,7 @@ function isDebugModeInEffect(config2) {
18582
18628
 
18583
18629
  // src/core/services/injection-policy.ts
18584
18630
  function decideInjection(input2) {
18585
- if (input2.debug || input2.telemetry.injectionMode === "always") return true;
18631
+ if (input2.debug || input2.resetDue === true || input2.telemetry.injectionMode === "always") return true;
18586
18632
  return input2.zone !== "GREEN" || input2.usagePercentage >= input2.telemetry.activationThresholdPercentage;
18587
18633
  }
18588
18634
 
@@ -18733,7 +18779,7 @@ async function handleSessionReset(options, event, hooks = {}) {
18733
18779
  if (!options.descriptor.capabilities.some((entry) => entry.id === "session_boot" && entry.state === "supported")) return NEUTRAL;
18734
18780
  if (event.reason === "compact" && !COMPACTION_BOOT_HARNESSES.includes(options.descriptor.harness)) return NEUTRAL;
18735
18781
  hooks.onPhase?.("guidance");
18736
- const text = resumeText(options.config.snapshot) ?? await claimHandoff(options, event.reason, hooks.deadline);
18782
+ const text = resumeText(options.config.snapshot, options.descriptor.skillInvocation) ?? await claimHandoff(options, event.reason, hooks.deadline);
18737
18783
  return text === null ? NEUTRAL : { kind: "context", block: text };
18738
18784
  }
18739
18785
  async function claimHandoff(options, reason, deadline) {
@@ -18775,8 +18821,9 @@ async function handlePreInvocation(options, event, input2) {
18775
18821
  return telemetryDecision(options, summary.turns, readZone(options, { summary, turns: summary.turns, observedCharacters: 0, measured: input2.measured }));
18776
18822
  }
18777
18823
  async function telemetryDecision(options, turn, view) {
18778
- if (!decideInjection({ telemetry: options.config.telemetry, zone: view.zone, usagePercentage: view.percentage, debug: isDebugModeInEffect(options.config) })) return NEUTRAL2;
18779
- const action = zoneAction(view.zone, options.config.snapshot, restartMode(options.config));
18824
+ const resetDue = isResetDue({ zone: view.zone, usagePercentage: view.percentage }, options.config.snapshot);
18825
+ if (!decideInjection({ telemetry: options.config.telemetry, zone: view.zone, usagePercentage: view.percentage, debug: isDebugModeInEffect(options.config), resetDue })) return NEUTRAL2;
18826
+ const action = zoneAction(view.zone, options.config.snapshot, { mode: restartMode(options.config), invoke: options.descriptor.skillInvocation, usagePercentage: view.percentage });
18780
18827
  return { kind: "context", block: renderTelemetryBlock({ turn, turnCeiling: redStartTurn(options.config.telemetry.zones), usagePercentage: view.percentage, usage: view.reading, zone: view.zone, action, debug: isDebugModeInEffect(options.config) }) };
18781
18828
  }
18782
18829
  async function handleResponseEnd(options, event) {
@@ -19151,7 +19198,7 @@ function mapOpenCodeSessionEvent(payload) {
19151
19198
  var HARNESS = "opencode";
19152
19199
  var ESTIMATION = { baselineTokens: 15e3, tokensPerTurn: 150 };
19153
19200
  var FALLBACK_SESSION_ID = "project";
19154
- var openCodeDescriptor = { harness: HARNESS, capabilities: OPENCODE_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null };
19201
+ var openCodeDescriptor = { harness: HARNESS, capabilities: OPENCODE_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null, skillInvocation: OPENCODE_SKILL_INVOCATION };
19155
19202
  function resolveProjectRoot(context) {
19156
19203
  const record2 = asRecord(context);
19157
19204
  return textValue(record2?.["directory"]) ?? textValue(record2?.["worktree"]) ?? process.cwd();
@@ -4,6 +4,17 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/core/services/skill-invocation.ts
8
+ function skillSlashInvocation(skill) {
9
+ return `/skill:${skill}`;
10
+ }
11
+ function sentenceInvocation(skill) {
12
+ return `use the ${skill} skill`;
13
+ }
14
+
15
+ // src/infrastructure/harnesses/pi/skill-invocation.ts
16
+ var PI_SKILL_INVOCATION = skillSlashInvocation;
17
+
7
18
  // node_modules/zod/v4/mini/external.js
8
19
  var external_exports = {};
9
20
  __export(external_exports, {
@@ -18155,6 +18166,34 @@ var HARNESS_IDS = [
18155
18166
  "antigravity-cli"
18156
18167
  ];
18157
18168
 
18169
+ // src/core/contracts/skill-name.ts
18170
+ var MAX_SKILL_NAME_LENGTH = 64;
18171
+ var NAME_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
18172
+ var NAMESPACE_SEPARATOR = ":";
18173
+ var INVOCATION_CHARACTERS = ["/", "$", "@"];
18174
+ var MAX_QUALIFIED_LENGTH = MAX_SKILL_NAME_LENGTH * 2 + NAMESPACE_SEPARATOR.length;
18175
+ function segmentFits(end) {
18176
+ return `(?=[^${NAMESPACE_SEPARATOR}]{1,${MAX_SKILL_NAME_LENGTH}}${end})`;
18177
+ }
18178
+ var PREFIX = `(?:${segmentFits(NAMESPACE_SEPARATOR)}${NAME_SEGMENT}${NAMESPACE_SEPARATOR})?`;
18179
+ var SKILL_NAME_PATTERN = new RegExp(`^${PREFIX}${segmentFits("$")}${NAME_SEGMENT}$`);
18180
+ var SHAPE_RULE = `lowercase letters, digits, and single hyphens, up to ${MAX_SKILL_NAME_LENGTH} characters, optionally prefixed by a plugin name and a colon`;
18181
+ function isSkillName(value) {
18182
+ return SKILL_NAME_PATTERN.test(value);
18183
+ }
18184
+ function skillNameProblem(value) {
18185
+ if (value === "") return "must not be empty";
18186
+ if (isSkillName(value)) return null;
18187
+ const bare = INVOCATION_CHARACTERS.includes(value.charAt(0)) ? value.slice(1) : null;
18188
+ if (bare !== null && isSkillName(bare)) return `"${value}" is not a skill name; use the bare name ${bare}`;
18189
+ return `"${value}" is not a skill name; use ${SHAPE_RULE}`;
18190
+ }
18191
+ var skillNameSchema = external_exports.string().check((ctx) => {
18192
+ const message = skillNameProblem(ctx.value);
18193
+ if (message !== null) ctx.issues.push({ code: "custom", input: ctx.value, message });
18194
+ });
18195
+ external_exports.globalRegistry.add(skillNameSchema, { pattern: SKILL_NAME_PATTERN.source, maxLength: MAX_QUALIFIED_LENGTH });
18196
+
18158
18197
  // src/core/contracts/zones.ts
18159
18198
  var ZONES = ["GREEN", "YELLOW", "RED", "CRITICAL"];
18160
18199
  var USAGE_SOURCES = ["measured", "estimated"];
@@ -18178,15 +18217,8 @@ function uniqueCheck(message) {
18178
18217
  if (!isUnique(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message });
18179
18218
  };
18180
18219
  }
18181
- var MAX_AGENT_COMMAND_LENGTH = 200;
18182
- var TRIMMED_RULE = "must not have leading or trailing whitespace";
18183
18220
  var RESUME_REQUIRES_COMMAND_RULE = "requires snapshot.command";
18184
- var SINGLE_LINE_RULE = "must be a single line";
18185
- var agentCommand = external_exports.string().check(external_exports.minLength(1), external_exports.maxLength(MAX_AGENT_COMMAND_LENGTH), (ctx) => {
18186
- if (ctx.value.trim() !== ctx.value) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: TRIMMED_RULE });
18187
- if (/[\r\n]/.test(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: SINGLE_LINE_RULE });
18188
- });
18189
- var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), command: external_exports.optional(agentCommand), resumeCommand: external_exports.optional(agentCommand) }).check((ctx) => {
18221
+ var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), triggerPercentage: external_exports.optional(external_exports.int().check(external_exports.minimum(1), external_exports.maximum(100))), command: external_exports.optional(skillNameSchema), resumeCommand: external_exports.optional(skillNameSchema) }).check((ctx) => {
18190
18222
  if (ctx.value.resumeCommand !== void 0 && ctx.value.command === void 0) addIssue(ctx, { code: "custom", path: ["resumeCommand"], input: ctx.value.resumeCommand, message: RESUME_REQUIRES_COMMAND_RULE });
18191
18223
  });
18192
18224
  var DEFAULT_SNAPSHOT = { triggerZone: "RED" };
@@ -18267,21 +18299,25 @@ var GENERIC_ACTIONS = {
18267
18299
  RED: "finish or pause the current unit and tell the user what remains",
18268
18300
  CRITICAL: "stop starting new work; tell the user what remains"
18269
18301
  };
18270
- function zoneAction(zone, snapshot, mode = "off") {
18271
- if (zone === "GREEN" || !isAtOrAbove(zone, snapshot.triggerZone)) return GENERIC_ACTIONS[zone];
18272
- if (snapshot.command !== void 0) return snapshotAction(zone, snapshot.command);
18273
- if (mode !== "handoff") return GENERIC_ACTIONS[zone];
18302
+ function isResetDue(reading, snapshot) {
18303
+ if (snapshot.triggerPercentage !== void 0) return reading.usagePercentage >= snapshot.triggerPercentage;
18304
+ return reading.zone !== "GREEN" && isAtOrAbove(reading.zone, snapshot.triggerZone);
18305
+ }
18306
+ function zoneAction(zone, snapshot, options = {}) {
18307
+ if (!isResetDue({ zone, usagePercentage: options.usagePercentage ?? 0 }, snapshot)) return GENERIC_ACTIONS[zone];
18308
+ if (snapshot.command !== void 0) return snapshotAction(zone, (options.invoke ?? sentenceInvocation)(snapshot.command));
18309
+ if ((options.mode ?? "off") !== "handoff") return GENERIC_ACTIONS[zone];
18274
18310
  return `save handoff to ${HANDOFF_PATH}, end reply with ${SESSION_RESET_SIGNAL}`;
18275
18311
  }
18276
- function snapshotAction(zone, command) {
18312
+ function snapshotAction(zone, invocation) {
18277
18313
  const now = zone === "CRITICAL" ? " now" : "";
18278
- return `run "${command}"${now}, then end reply with ${SESSION_RESET_SIGNAL}`;
18314
+ return `run "${invocation}"${now}, then end reply with ${SESSION_RESET_SIGNAL}`;
18279
18315
  }
18280
18316
  function handoffResumeText(archivedPath) {
18281
18317
  return `${RESUME_PREFIX} Read "${archivedPath}" and continue the previous work from it.`;
18282
18318
  }
18283
- function resumeText(snapshot) {
18284
- return snapshot.resumeCommand === void 0 ? null : `${RESUME_PREFIX} Run "${snapshot.resumeCommand}" before continuing.`;
18319
+ function resumeText(snapshot, invoke = sentenceInvocation) {
18320
+ return snapshot.resumeCommand === void 0 ? null : `${RESUME_PREFIX} Run "${invoke(snapshot.resumeCommand)}" before continuing.`;
18285
18321
  }
18286
18322
  function isAtOrAbove(zone, trigger) {
18287
18323
  return ZONES.indexOf(zone) >= ZONES.indexOf(trigger);
@@ -18326,7 +18362,7 @@ async function resolveFailure(input2) {
18326
18362
  return { kind: "neutral" };
18327
18363
  }
18328
18364
  function deadlineBootDecision(input2) {
18329
- const text = resumeText((input2.config ?? DEFAULT_CONFIG).snapshot);
18365
+ const text = resumeText((input2.config ?? DEFAULT_CONFIG).snapshot, input2.descriptor?.skillInvocation);
18330
18366
  return text === null ? { kind: "neutral" } : { kind: "context", block: text };
18331
18367
  }
18332
18368
  async function recordRuntimeFailure(errors, record2) {
@@ -18474,7 +18510,20 @@ async function listRuntimeStateFiles(root, readdirFn = readdir) {
18474
18510
  import { createHash } from "node:crypto";
18475
18511
  import { mkdir, writeFile } from "node:fs/promises";
18476
18512
  import { join as join2, resolve as resolve2 } from "node:path";
18477
- var RUNTIME_GITIGNORE_CONTENT = "*\n";
18513
+
18514
+ // src/core/contracts/context-reading.ts
18515
+ var RUNTIME_IGNORE_CONTENT = "*\n";
18516
+ var PERCENTAGE_MAXIMUM = 100;
18517
+ var contextReadingSchema = external_exports.strictObject({
18518
+ v: external_exports.literal(1),
18519
+ at: external_exports.string().check(external_exports.minLength(1)),
18520
+ windowTokens: external_exports.nullable(external_exports.int().check(external_exports.positive())),
18521
+ inputTokens: external_exports.nullable(external_exports.int().check(external_exports.nonnegative())),
18522
+ usedPercentage: external_exports.nullable(external_exports.number().check(external_exports.gte(0), external_exports.lte(PERCENTAGE_MAXIMUM)))
18523
+ });
18524
+
18525
+ // src/infrastructure/runtime/runtime-paths.ts
18526
+ var RUNTIME_GITIGNORE_CONTENT = RUNTIME_IGNORE_CONTENT;
18478
18527
  var SESSIONS_DIR_NAME = "sessions";
18479
18528
  var LEDGER_FILE_EXTENSION = ".jsonl";
18480
18529
  var SESSIONS_RELATIVE_PREFIX = `${RUNTIME_STATE_RELATIVE_DIR}/${SESSIONS_DIR_NAME}/`;
@@ -18582,7 +18631,7 @@ function isDebugModeInEffect(config2) {
18582
18631
 
18583
18632
  // src/core/services/injection-policy.ts
18584
18633
  function decideInjection(input2) {
18585
- if (input2.debug || input2.telemetry.injectionMode === "always") return true;
18634
+ if (input2.debug || input2.resetDue === true || input2.telemetry.injectionMode === "always") return true;
18586
18635
  return input2.zone !== "GREEN" || input2.usagePercentage >= input2.telemetry.activationThresholdPercentage;
18587
18636
  }
18588
18637
 
@@ -18733,7 +18782,7 @@ async function handleSessionReset(options, event, hooks = {}) {
18733
18782
  if (!options.descriptor.capabilities.some((entry) => entry.id === "session_boot" && entry.state === "supported")) return NEUTRAL;
18734
18783
  if (event.reason === "compact" && !COMPACTION_BOOT_HARNESSES.includes(options.descriptor.harness)) return NEUTRAL;
18735
18784
  hooks.onPhase?.("guidance");
18736
- const text = resumeText(options.config.snapshot) ?? await claimHandoff(options, event.reason, hooks.deadline);
18785
+ const text = resumeText(options.config.snapshot, options.descriptor.skillInvocation) ?? await claimHandoff(options, event.reason, hooks.deadline);
18737
18786
  return text === null ? NEUTRAL : { kind: "context", block: text };
18738
18787
  }
18739
18788
  async function claimHandoff(options, reason, deadline) {
@@ -18775,8 +18824,9 @@ async function handlePreInvocation(options, event, input2) {
18775
18824
  return telemetryDecision(options, summary.turns, readZone(options, { summary, turns: summary.turns, observedCharacters: 0, measured: input2.measured }));
18776
18825
  }
18777
18826
  async function telemetryDecision(options, turn, view) {
18778
- if (!decideInjection({ telemetry: options.config.telemetry, zone: view.zone, usagePercentage: view.percentage, debug: isDebugModeInEffect(options.config) })) return NEUTRAL2;
18779
- const action = zoneAction(view.zone, options.config.snapshot, restartMode(options.config));
18827
+ const resetDue = isResetDue({ zone: view.zone, usagePercentage: view.percentage }, options.config.snapshot);
18828
+ if (!decideInjection({ telemetry: options.config.telemetry, zone: view.zone, usagePercentage: view.percentage, debug: isDebugModeInEffect(options.config), resetDue })) return NEUTRAL2;
18829
+ const action = zoneAction(view.zone, options.config.snapshot, { mode: restartMode(options.config), invoke: options.descriptor.skillInvocation, usagePercentage: view.percentage });
18780
18830
  return { kind: "context", block: renderTelemetryBlock({ turn, turnCeiling: redStartTurn(options.config.telemetry.zones), usagePercentage: view.percentage, usage: view.reading, zone: view.zone, action, debug: isDebugModeInEffect(options.config) }) };
18781
18831
  }
18782
18832
  async function handleResponseEnd(options, event) {
@@ -19166,7 +19216,7 @@ function renderPiToolResult(payload, block) {
19166
19216
  // src/infrastructure/harnesses/pi/runtime.ts
19167
19217
  var HARNESS = "pi";
19168
19218
  var ESTIMATION = { baselineTokens: 15e3, tokensPerTurn: 150 };
19169
- var piDescriptor = { harness: HARNESS, capabilities: PI_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: "/new" };
19219
+ var piDescriptor = { harness: HARNESS, capabilities: PI_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: "/new", skillInvocation: PI_SKILL_INVOCATION };
19170
19220
  function rootOf(context) {
19171
19221
  return textValue(context.cwd);
19172
19222
  }