@deftai/directive-runtime 0.129.5 → 0.129.7

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 (222) hide show
  1. package/Taskfile.yml +316 -0
  2. package/UPGRADING.md +1 -0
  3. package/commands.md +5 -3
  4. package/contracts/design-critique.md +1 -1
  5. package/contracts/path-write-fence.md +6 -0
  6. package/dist/applying-host-release.d.ts +16 -0
  7. package/dist/applying-host-release.js +98 -0
  8. package/dist/authz.d.ts +1 -1
  9. package/dist/authz.js +1 -1
  10. package/dist/check.d.ts +1 -0
  11. package/dist/check.js +27 -4
  12. package/dist/design-critique-ensure-arc-dest.d.ts +16 -0
  13. package/dist/design-critique-ensure-arc-dest.js +168 -0
  14. package/dist/dispatch.d.ts +1 -1
  15. package/dist/dispatch.js +8 -0
  16. package/dist/hook-dispatch.js +3 -2
  17. package/dist/human-presence-mint.d.ts +6 -1
  18. package/dist/human-presence-mint.js +20 -6
  19. package/dist/session-start.d.ts +8 -2
  20. package/dist/session-start.js +70 -15
  21. package/dist/setup-wiring.d.ts +36 -0
  22. package/dist/setup-wiring.js +169 -14
  23. package/dist/vbrief-preflight.d.ts +4 -0
  24. package/dist/vbrief-preflight.js +34 -0
  25. package/dist/verify-scope-provenance.d.ts +13 -1
  26. package/dist/verify-scope-provenance.js +44 -1
  27. package/dist/verify-source-cli/verify-deposit-closure.js +5 -5
  28. package/dist/windows-bin-ps1.d.ts +2 -2
  29. package/dist/windows-bin-ps1.js +5 -11
  30. package/docs/deft-directive-disable.md +3 -3
  31. package/docs/getting-started.md +2 -0
  32. package/docs/scope-provenance.md +15 -1
  33. package/package.json +2 -3
  34. package/packs/lessons/lessons-pack-0.1.json +15 -0
  35. package/packs/skills/skills-pack-0.1.json +3 -3
  36. package/skills/deft-directive-design-critique/SKILL.md +1 -1
  37. package/skills/deft-directive-release/SKILL.md +31 -1
  38. package/skills/deft-directive-review-cycle/SKILL.md +15 -6
  39. package/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  40. package/vendor/directive-content/Taskfile.yml +316 -0
  41. package/vendor/directive-content/UPGRADING.md +1 -0
  42. package/vendor/directive-content/commands.md +5 -3
  43. package/vendor/directive-content/contracts/design-critique.md +1 -1
  44. package/vendor/directive-content/contracts/path-write-fence.md +6 -0
  45. package/vendor/directive-content/docs/deft-directive-disable.md +3 -3
  46. package/vendor/directive-content/docs/getting-started.md +2 -0
  47. package/vendor/directive-content/docs/scope-provenance.md +15 -1
  48. package/vendor/directive-content/packs/lessons/lessons-pack-0.1.json +15 -0
  49. package/vendor/directive-content/packs/skills/skills-pack-0.1.json +3 -3
  50. package/vendor/directive-content/skills/deft-directive-design-critique/SKILL.md +1 -1
  51. package/vendor/directive-content/skills/deft-directive-release/SKILL.md +31 -1
  52. package/vendor/directive-content/skills/deft-directive-review-cycle/SKILL.md +15 -6
  53. package/vendor/directive-content/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  54. package/vendor/directive-core/dist/.tsbuildinfo +1 -1
  55. package/vendor/directive-core/dist/authz/classify.d.ts +12 -0
  56. package/vendor/directive-core/dist/authz/classify.d.ts.map +1 -1
  57. package/vendor/directive-core/dist/authz/classify.js +40 -12
  58. package/vendor/directive-core/dist/authz/classify.js.map +1 -1
  59. package/vendor/directive-core/dist/authz/templates.d.ts +2 -2
  60. package/vendor/directive-core/dist/authz/templates.d.ts.map +1 -1
  61. package/vendor/directive-core/dist/authz/templates.js +1 -0
  62. package/vendor/directive-core/dist/authz/templates.js.map +1 -1
  63. package/vendor/directive-core/dist/authz/types.d.ts +1 -1
  64. package/vendor/directive-core/dist/authz/types.d.ts.map +1 -1
  65. package/vendor/directive-core/dist/authz/types.js +2 -0
  66. package/vendor/directive-core/dist/authz/types.js.map +1 -1
  67. package/vendor/directive-core/dist/authz/verb-classification.d.ts.map +1 -1
  68. package/vendor/directive-core/dist/authz/verb-classification.js +30 -3
  69. package/vendor/directive-core/dist/authz/verb-classification.js.map +1 -1
  70. package/vendor/directive-core/dist/cache/fetch.d.ts.map +1 -1
  71. package/vendor/directive-core/dist/cache/fetch.js +50 -4
  72. package/vendor/directive-core/dist/cache/fetch.js.map +1 -1
  73. package/vendor/directive-core/dist/design-critique/arc-dest.d.ts +22 -5
  74. package/vendor/directive-core/dist/design-critique/arc-dest.d.ts.map +1 -1
  75. package/vendor/directive-core/dist/design-critique/arc-dest.js +61 -0
  76. package/vendor/directive-core/dist/design-critique/arc-dest.js.map +1 -1
  77. package/vendor/directive-core/dist/design-critique/run-posture.d.ts +7 -2
  78. package/vendor/directive-core/dist/design-critique/run-posture.d.ts.map +1 -1
  79. package/vendor/directive-core/dist/design-critique/run-posture.js +6 -1
  80. package/vendor/directive-core/dist/design-critique/run-posture.js.map +1 -1
  81. package/vendor/directive-core/dist/fs/contained-write.d.ts +8 -2
  82. package/vendor/directive-core/dist/fs/contained-write.d.ts.map +1 -1
  83. package/vendor/directive-core/dist/fs/contained-write.js +22 -6
  84. package/vendor/directive-core/dist/fs/contained-write.js.map +1 -1
  85. package/vendor/directive-core/dist/hooks/dispatcher.d.ts +1 -1
  86. package/vendor/directive-core/dist/hooks/dispatcher.d.ts.map +1 -1
  87. package/vendor/directive-core/dist/hooks/dispatcher.js +290 -16
  88. package/vendor/directive-core/dist/hooks/dispatcher.js.map +1 -1
  89. package/vendor/directive-core/dist/hooks/shell-write-targets.d.ts +5 -0
  90. package/vendor/directive-core/dist/hooks/shell-write-targets.d.ts.map +1 -1
  91. package/vendor/directive-core/dist/hooks/shell-write-targets.js +18 -1
  92. package/vendor/directive-core/dist/hooks/shell-write-targets.js.map +1 -1
  93. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts +26 -1
  94. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts.map +1 -1
  95. package/vendor/directive-core/dist/init-deposit/agent-hooks.js +52 -3
  96. package/vendor/directive-core/dist/init-deposit/agent-hooks.js.map +1 -1
  97. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts +48 -0
  98. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts.map +1 -1
  99. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js +170 -9
  100. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js.map +1 -1
  101. package/vendor/directive-core/dist/init-deposit/scaffold.d.ts +8 -1
  102. package/vendor/directive-core/dist/init-deposit/scaffold.d.ts.map +1 -1
  103. package/vendor/directive-core/dist/init-deposit/scaffold.js +38 -4
  104. package/vendor/directive-core/dist/init-deposit/scaffold.js.map +1 -1
  105. package/vendor/directive-core/dist/install-health/cheap.d.ts +2 -1
  106. package/vendor/directive-core/dist/install-health/cheap.d.ts.map +1 -1
  107. package/vendor/directive-core/dist/install-health/cheap.js +4 -3
  108. package/vendor/directive-core/dist/install-health/cheap.js.map +1 -1
  109. package/vendor/directive-core/dist/install-health/constants.d.ts +18 -2
  110. package/vendor/directive-core/dist/install-health/constants.d.ts.map +1 -1
  111. package/vendor/directive-core/dist/install-health/constants.js +18 -2
  112. package/vendor/directive-core/dist/install-health/constants.js.map +1 -1
  113. package/vendor/directive-core/dist/policy/deft-directive-disable.d.ts +42 -5
  114. package/vendor/directive-core/dist/policy/deft-directive-disable.d.ts.map +1 -1
  115. package/vendor/directive-core/dist/policy/deft-directive-disable.js +71 -41
  116. package/vendor/directive-core/dist/policy/deft-directive-disable.js.map +1 -1
  117. package/vendor/directive-core/dist/pr-closeout-attestable/evaluate.d.ts +6 -1
  118. package/vendor/directive-core/dist/pr-closeout-attestable/evaluate.d.ts.map +1 -1
  119. package/vendor/directive-core/dist/pr-closeout-attestable/evaluate.js +24 -12
  120. package/vendor/directive-core/dist/pr-closeout-attestable/evaluate.js.map +1 -1
  121. package/vendor/directive-core/dist/pr-closeout-attestable/pr-head-assert.d.ts +68 -14
  122. package/vendor/directive-core/dist/pr-closeout-attestable/pr-head-assert.d.ts.map +1 -1
  123. package/vendor/directive-core/dist/pr-closeout-attestable/pr-head-assert.js +278 -47
  124. package/vendor/directive-core/dist/pr-closeout-attestable/pr-head-assert.js.map +1 -1
  125. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts +10 -5
  126. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts.map +1 -1
  127. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js +41 -18
  128. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js.map +1 -1
  129. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts +3 -2
  130. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts.map +1 -1
  131. package/vendor/directive-core/dist/preflight-gh/pre-push.js +19 -18
  132. package/vendor/directive-core/dist/preflight-gh/pre-push.js.map +1 -1
  133. package/vendor/directive-core/dist/release/gh.d.ts.map +1 -1
  134. package/vendor/directive-core/dist/release/gh.js +2 -1
  135. package/vendor/directive-core/dist/release/gh.js.map +1 -1
  136. package/vendor/directive-core/dist/release/version.d.ts +4 -2
  137. package/vendor/directive-core/dist/release/version.d.ts.map +1 -1
  138. package/vendor/directive-core/dist/release/version.js +4 -2
  139. package/vendor/directive-core/dist/release/version.js.map +1 -1
  140. package/vendor/directive-core/dist/release-publish/gh-api.d.ts.map +1 -1
  141. package/vendor/directive-core/dist/release-publish/gh-api.js +4 -2
  142. package/vendor/directive-core/dist/release-publish/gh-api.js.map +1 -1
  143. package/vendor/directive-core/dist/release-publish/main.d.ts.map +1 -1
  144. package/vendor/directive-core/dist/release-publish/main.js +15 -4
  145. package/vendor/directive-core/dist/release-publish/main.js.map +1 -1
  146. package/vendor/directive-core/dist/release-publish/pipeline.d.ts +11 -0
  147. package/vendor/directive-core/dist/release-publish/pipeline.d.ts.map +1 -1
  148. package/vendor/directive-core/dist/release-publish/pipeline.js +143 -2
  149. package/vendor/directive-core/dist/release-publish/pipeline.js.map +1 -1
  150. package/vendor/directive-core/dist/release-rollback/gh.d.ts.map +1 -1
  151. package/vendor/directive-core/dist/release-rollback/gh.js +2 -1
  152. package/vendor/directive-core/dist/release-rollback/gh.js.map +1 -1
  153. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts +202 -19
  154. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts.map +1 -1
  155. package/vendor/directive-core/dist/scope/delivery-evidence.js +948 -107
  156. package/vendor/directive-core/dist/scope/delivery-evidence.js.map +1 -1
  157. package/vendor/directive-core/dist/scope/transition.d.ts +4 -1
  158. package/vendor/directive-core/dist/scope/transition.d.ts.map +1 -1
  159. package/vendor/directive-core/dist/scope/transition.js +2 -2
  160. package/vendor/directive-core/dist/scope/transition.js.map +1 -1
  161. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts +11 -2
  162. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts.map +1 -1
  163. package/vendor/directive-core/dist/scope-provenance/base-fence.js +16 -1
  164. package/vendor/directive-core/dist/scope-provenance/base-fence.js.map +1 -1
  165. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts +12 -0
  166. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts.map +1 -1
  167. package/vendor/directive-core/dist/scope-provenance/evaluate.js +41 -5
  168. package/vendor/directive-core/dist/scope-provenance/evaluate.js.map +1 -1
  169. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts +106 -0
  170. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts.map +1 -0
  171. package/vendor/directive-core/dist/scope-provenance/forge-approval.js +382 -0
  172. package/vendor/directive-core/dist/scope-provenance/forge-approval.js.map +1 -0
  173. package/vendor/directive-core/dist/session/git.d.ts +21 -2
  174. package/vendor/directive-core/dist/session/git.d.ts.map +1 -1
  175. package/vendor/directive-core/dist/session/git.js +21 -4
  176. package/vendor/directive-core/dist/session/git.js.map +1 -1
  177. package/vendor/directive-core/dist/session/occupancy.d.ts +93 -6
  178. package/vendor/directive-core/dist/session/occupancy.d.ts.map +1 -1
  179. package/vendor/directive-core/dist/session/occupancy.js +215 -52
  180. package/vendor/directive-core/dist/session/occupancy.js.map +1 -1
  181. package/vendor/directive-core/dist/session/orientation-compression.d.ts +18 -2
  182. package/vendor/directive-core/dist/session/orientation-compression.d.ts.map +1 -1
  183. package/vendor/directive-core/dist/session/orientation-compression.js +38 -5
  184. package/vendor/directive-core/dist/session/orientation-compression.js.map +1 -1
  185. package/vendor/directive-core/dist/session/session-start.d.ts +47 -2
  186. package/vendor/directive-core/dist/session/session-start.d.ts.map +1 -1
  187. package/vendor/directive-core/dist/session/session-start.js +154 -3
  188. package/vendor/directive-core/dist/session/session-start.js.map +1 -1
  189. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts +18 -4
  190. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts.map +1 -1
  191. package/vendor/directive-core/dist/swarm/approach1-babysitter.js +28 -4
  192. package/vendor/directive-core/dist/swarm/approach1-babysitter.js.map +1 -1
  193. package/vendor/directive-core/dist/swarm/dest-place.d.ts +1 -1
  194. package/vendor/directive-core/dist/swarm/dest-place.d.ts.map +1 -1
  195. package/vendor/directive-core/dist/swarm/dest-place.js +36 -41
  196. package/vendor/directive-core/dist/swarm/dest-place.js.map +1 -1
  197. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts +29 -0
  198. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts.map +1 -1
  199. package/vendor/directive-core/dist/swarm/finalize-cohort.js +122 -11
  200. package/vendor/directive-core/dist/swarm/finalize-cohort.js.map +1 -1
  201. package/vendor/directive-core/dist/swarm/finalize-owed.d.ts +14 -0
  202. package/vendor/directive-core/dist/swarm/finalize-owed.d.ts.map +1 -1
  203. package/vendor/directive-core/dist/swarm/finalize-owed.js +214 -48
  204. package/vendor/directive-core/dist/swarm/finalize-owed.js.map +1 -1
  205. package/vendor/directive-core/dist/swarm/launch.d.ts.map +1 -1
  206. package/vendor/directive-core/dist/swarm/launch.js +15 -4
  207. package/vendor/directive-core/dist/swarm/launch.js.map +1 -1
  208. package/vendor/directive-core/dist/swarm/worktree-pm-install.d.ts +49 -2
  209. package/vendor/directive-core/dist/swarm/worktree-pm-install.d.ts.map +1 -1
  210. package/vendor/directive-core/dist/swarm/worktree-pm-install.js +207 -12
  211. package/vendor/directive-core/dist/swarm/worktree-pm-install.js.map +1 -1
  212. package/vendor/directive-core/dist/swarm/worktrees-cli.d.ts +6 -0
  213. package/vendor/directive-core/dist/swarm/worktrees-cli.d.ts.map +1 -1
  214. package/vendor/directive-core/dist/swarm/worktrees-cli.js +79 -2
  215. package/vendor/directive-core/dist/swarm/worktrees-cli.js.map +1 -1
  216. package/vendor/directive-core/dist/swarm/worktrees.d.ts +12 -1
  217. package/vendor/directive-core/dist/swarm/worktrees.d.ts.map +1 -1
  218. package/vendor/directive-core/dist/swarm/worktrees.js +34 -26
  219. package/vendor/directive-core/dist/swarm/worktrees.js.map +1 -1
  220. package/vendor/directive-core/dist/triage/evaluate/worktrees.d.ts.map +1 -1
  221. package/vendor/directive-core/dist/triage/evaluate/worktrees.js +219 -60
  222. package/vendor/directive-core/dist/triage/evaluate/worktrees.js.map +1 -1
@@ -9,6 +9,7 @@ import { fileURLToPath } from "node:url";
9
9
  import { containedChmod, containedRemove, containedWrite, } from "../vendor/directive-core/dist/fs/contained-write.js";
10
10
  import { DIRECT_WRITE_HOOK_MATCHER, KILL_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../vendor/directive-core/dist/hooks/tools.js";
11
11
  import { GROK_PRETOOLUSE_WRAPPER_REL_PATH, HOST_HOOK_MISSING_PIN_PROJECT_PM_GUIDANCE, hostHookOuterCommand, hostHookOuterRelPath, hostHookOuterSha8, hostHookOuterSource, isGrokWrapperCommand, isHostHookOuterCommand, } from "../vendor/directive-core/dist/init-deposit/agent-hooks.js";
12
+ import { DESIRED_GITATTRIBUTES_LINES, ensureGitattributes, LEGACY_CORE_TEXT_EOL_LF, } from "../vendor/directive-core/dist/init-deposit/scaffold.js";
12
13
  import { INSTALLATION_RECOVERY_GUIDANCE, RUNTIME_PACKAGE_PATH_PREFIX, } from "../vendor/directive-core/dist/install-health/constants.js";
13
14
  import { MANAGED_SECTION_CLOSE, MANAGED_SECTION_OPEN_PREFIX, } from "../vendor/directive-core/dist/install-health/index.js";
14
15
  import { applyAgentsRefresh } from "../vendor/directive-core/dist/platform/agents-md.js";
@@ -31,6 +32,25 @@ function readText(path) {
31
32
  return null;
32
33
  }
33
34
  }
35
+ /** Normalize CRLF/CR to LF for ownership/wrapper equality (#5995). */
36
+ export function normalizeEol(text) {
37
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
38
+ }
39
+ /** True when two texts differ only by end-of-line convention (#5995). */
40
+ export function contentsEqualIgnoringEol(a, b) {
41
+ return normalizeEol(a) === normalizeEol(b);
42
+ }
43
+ /**
44
+ * `.githooks` shell entrypoints must be LF on disk for `sh` (#5995 / PR #6151 P1).
45
+ * Ownership stays EOL-insensitive; these paths still rewrite CRLF-only drift.
46
+ */
47
+ export function isShellHookEntrypoint(rel) {
48
+ const posix = rel.replace(/\\/g, "/");
49
+ if (!posix.startsWith(".githooks/"))
50
+ return false;
51
+ const base = posix.slice(posix.lastIndexOf("/") + 1);
52
+ return base.endsWith(".sh") || !base.includes(".");
53
+ }
34
54
  function writeContained(projectRoot, relPath, body) {
35
55
  containedWrite({
36
56
  root: projectRoot,
@@ -47,6 +67,11 @@ function trackChange(projectRoot, rel, next) {
47
67
  const prior = readText(join(projectRoot, rel));
48
68
  if (prior === next)
49
69
  return { created: false, modified: false };
70
+ // CRLF-vs-LF-only diffs are still Directive-managed; skip rewrite for non-shell
71
+ // paths (#5995). Shell hooks still need LF bytes so `sh` can exec them (#6151 P1).
72
+ if (prior !== null && contentsEqualIgnoringEol(prior, next) && !isShellHookEntrypoint(rel)) {
73
+ return { created: false, modified: false };
74
+ }
50
75
  writeContained(projectRoot, rel, next);
51
76
  return { created: prior === null, modified: prior !== null };
52
77
  }
@@ -227,16 +252,126 @@ function ovprSlashWrapperContents(file) {
227
252
  * (legacy .deft/core teaching or package-relative OVPR form).
228
253
  */
229
254
  function isManagedSlashWrapper(raw, file, next) {
230
- if (raw === next)
255
+ const rawN = normalizeEol(raw);
256
+ const nextN = normalizeEol(next);
257
+ if (rawN === nextN)
231
258
  return true;
232
- if (isThinWrapperMarkdown(raw, file.dispatchPath))
259
+ if (isThinWrapperMarkdown(rawN, file.dispatchPath))
233
260
  return true;
234
261
  // Already-rewritten OVPR wrapper: still thin + points at this dispatch path.
235
- return (raw.startsWith("---\n") &&
236
- raw.includes(file.dispatchPath) &&
237
- (raw.includes(RUNTIME_PREFIX) || raw.includes(".deft/core/")) &&
238
- raw.includes("Read and follow") &&
239
- raw.includes("Do not inline"));
262
+ return (rawN.startsWith("---\n") &&
263
+ rawN.includes(file.dispatchPath) &&
264
+ (rawN.includes(RUNTIME_PREFIX) || rawN.includes(".deft/core/")) &&
265
+ rawN.includes("Read and follow") &&
266
+ rawN.includes("Do not inline"));
267
+ }
268
+ /**
269
+ * True when `rel` has unstaged worktree edits vs the index (#6151 P1).
270
+ * No repo / no git / clean path → false (safe to report modified for staging).
271
+ */
272
+ export function pathHasUnstagedChanges(projectRoot, rel) {
273
+ try {
274
+ const out = execFileSync("git", ["-C", projectRoot, "diff", "--name-only", "--", rel], {
275
+ encoding: "utf8",
276
+ stdio: ["ignore", "pipe", "ignore"],
277
+ });
278
+ return out.trim().length > 0;
279
+ }
280
+ catch {
281
+ return false;
282
+ }
283
+ }
284
+ /** Left-unstaged disclosure when LF pins land on a dirty `.gitattributes` (#6151 P1). */
285
+ export const GITATTRIBUTES_STAGING_SKIPPED_DIRTY = ".gitattributes had unstaged local edits; LF pins were written but left unstaged so install/upgrade does not git add unrelated user changes (#6151 P1)";
286
+ const DESIRED_GITATTRIBUTES_LINE_SET = new Set([
287
+ ...DESIRED_GITATTRIBUTES_LINES,
288
+ LEGACY_CORE_TEXT_EOL_LF,
289
+ ]);
290
+ /**
291
+ * True when every attribute line is a known Directive core/managed marker (or
292
+ * Deft deposit comment). Foreign rules mean the file is not pure Directive-owned
293
+ * (#6151 P1 dirty-skip heuristic).
294
+ */
295
+ export function isDirectiveManagedGitattributes(content) {
296
+ for (const raw of normalizeEol(content).split("\n")) {
297
+ const line = raw.trim();
298
+ if (line.length === 0)
299
+ continue;
300
+ if (line.startsWith("#")) {
301
+ if (line.startsWith("# Deft framework:") ||
302
+ line.startsWith("# Deft managed wiring:") ||
303
+ line.startsWith("# consumer source.") ||
304
+ line.startsWith("# byte identity") ||
305
+ line.startsWith("# to LF so Windows")) {
306
+ continue;
307
+ }
308
+ return false;
309
+ }
310
+ if (!DESIRED_GITATTRIBUTES_LINE_SET.has(line))
311
+ return false;
312
+ }
313
+ return true;
314
+ }
315
+ /** True when `rel` exists in the worktree but is not tracked (git repo only). */
316
+ function pathIsUntrackedInRepo(projectRoot, rel) {
317
+ try {
318
+ execFileSync("git", ["-C", projectRoot, "rev-parse", "--is-inside-work-tree"], {
319
+ encoding: "utf8",
320
+ stdio: ["ignore", "pipe", "ignore"],
321
+ });
322
+ }
323
+ catch {
324
+ return false;
325
+ }
326
+ try {
327
+ execFileSync("git", ["-C", projectRoot, "ls-files", "--error-unmatch", "--", rel], {
328
+ encoding: "utf8",
329
+ stdio: ["ignore", "pipe", "ignore"],
330
+ });
331
+ return false;
332
+ }
333
+ catch {
334
+ return existsSync(join(projectRoot, rel));
335
+ }
336
+ }
337
+ /**
338
+ * Deposit narrow LF .gitattributes for managed wiring paths (#5995).
339
+ * Reuses scaffold ensureGitattributes (core markers + managed wiring lines).
340
+ * Reports created/modified so install/upgrade staging commits the pins (#6151 P1).
341
+ * When the path already has unstaged user edits (or an untracked foreign file),
342
+ * still appends missing pins but does not report modified (avoids staging WIP).
343
+ */
344
+ export function ensureManagedPathGitattributes(projectRoot) {
345
+ const attrsRel = ".gitattributes";
346
+ const prior = readText(join(projectRoot, attrsRel));
347
+ // Compute dirty signal before write — post-write `git diff` is true for clean
348
+ // pin appends too, so it cannot alone decide staging (#6151 P1).
349
+ const hadUnstagedEdits = prior !== null && pathHasUnstagedChanges(projectRoot, attrsRel);
350
+ const untrackedForeign = prior !== null &&
351
+ !isDirectiveManagedGitattributes(prior) &&
352
+ pathIsUntrackedInRepo(projectRoot, attrsRel);
353
+ const wrote = ensureGitattributes(projectRoot, { printf: () => undefined });
354
+ if (!wrote)
355
+ return { created: false, modified: false, wrote: false };
356
+ if (prior === null)
357
+ return { created: true, modified: false, wrote: true };
358
+ const next = readText(join(projectRoot, attrsRel)) ?? "";
359
+ // After write: confirm unstaged still, or foreign prior that changed and was
360
+ // already dirty/untracked — never stage unfinished user WIP with the pins.
361
+ const stillUnstaged = pathHasUnstagedChanges(projectRoot, attrsRel);
362
+ const priorForeignChanged = !isDirectiveManagedGitattributes(prior) && !contentsEqualIgnoringEol(prior, next);
363
+ const treatAsDirty = hadUnstagedEdits ||
364
+ untrackedForeign ||
365
+ (stillUnstaged && priorForeignChanged && (hadUnstagedEdits || untrackedForeign));
366
+ if (treatAsDirty) {
367
+ return {
368
+ created: false,
369
+ modified: false,
370
+ wrote: true,
371
+ stagingSkippedNotice: GITATTRIBUTES_STAGING_SKIPPED_DIRTY,
372
+ };
373
+ }
374
+ return { created: false, modified: true, wrote: true };
240
375
  }
241
376
  /**
242
377
  * Deposit host slash/prompt thin wrappers with package-relative targets (AC8.1 / AC8.10).
@@ -244,9 +379,22 @@ function isManagedSlashWrapper(raw, file, next) {
244
379
  * Preserves consumer-custom command files and reports conflicts instead of clobbering.
245
380
  */
246
381
  export function ensureSlashCommands(projectRoot) {
382
+ // Pin managed wiring paths to LF before compare/deposit (#5995).
383
+ const attrs = ensureManagedPathGitattributes(projectRoot);
247
384
  const created = [];
248
385
  const modified = [];
249
386
  const conflicts = [];
387
+ const notices = [];
388
+ if (attrs.created) {
389
+ created.push(".gitattributes");
390
+ }
391
+ else if (attrs.stagingSkippedNotice) {
392
+ notices.push(attrs.stagingSkippedNotice);
393
+ // deliberately omit from modified — dirty user WIP must not be git-added
394
+ }
395
+ else if (attrs.modified && attrs.wrote) {
396
+ modified.push(".gitattributes");
397
+ }
250
398
  const policy = loadHostSlashCommandsPolicyFromProject(projectRoot);
251
399
  for (const hostId of listSlashEmitterHosts()) {
252
400
  if (!isHostSlashCommandDepositEnabled(hostId, policy))
@@ -255,7 +403,9 @@ export function ensureSlashCommands(projectRoot) {
255
403
  const body = ovprSlashWrapperContents(file);
256
404
  const next = body.endsWith("\n") ? body : `${body}\n`;
257
405
  const prior = readText(join(projectRoot, file.relativePath));
258
- if (prior !== null && prior !== next && !isManagedSlashWrapper(prior, file, next)) {
406
+ if (prior !== null &&
407
+ !contentsEqualIgnoringEol(prior, next) &&
408
+ !isManagedSlashWrapper(prior, file, next)) {
259
409
  conflicts.push(`${file.relativePath}: existing command is not a Directive-managed wrapper; refusing to overwrite`);
260
410
  continue;
261
411
  }
@@ -266,7 +416,7 @@ export function ensureSlashCommands(projectRoot) {
266
416
  modified.push(file.relativePath);
267
417
  }
268
418
  }
269
- return { created, modified, conflicts };
419
+ return { created, modified, conflicts, notices };
270
420
  }
271
421
  /**
272
422
  * Verified managed slash/prompt paths from the emitter inventory (AC8.1 / AC11.10).
@@ -692,7 +842,7 @@ export function ensureHostHooks(projectRoot, hostHooksPolicy = loadHostHooksPoli
692
842
  continue;
693
843
  const stripped = spec.strip(readJsonObject(abs));
694
844
  const next = `${JSON.stringify(stripped, null, 2)}\n`;
695
- if (prior === next)
845
+ if (prior === next || contentsEqualIgnoringEol(prior, next))
696
846
  continue;
697
847
  writeContained(projectRoot, spec.rel, next);
698
848
  modified.push(spec.rel);
@@ -701,7 +851,7 @@ export function ensureHostHooks(projectRoot, hostHooksPolicy = loadHostHooksPoli
701
851
  const base = prior === null ? {} : readJsonObject(abs);
702
852
  const merged = spec.merge(base);
703
853
  const next = `${JSON.stringify(merged, null, 2)}\n`;
704
- if (prior === next)
854
+ if (prior === next || (prior !== null && contentsEqualIgnoringEol(prior, next)))
705
855
  continue;
706
856
  writeContained(projectRoot, spec.rel, next);
707
857
  if (prior === null)
@@ -717,7 +867,7 @@ export function ensureHostHooks(projectRoot, hostHooksPolicy = loadHostHooksPoli
717
867
  const enabled = isHostHookDepositEnabled(host, hostHooksPolicy);
718
868
  if (enabled) {
719
869
  const prior = readText(join(projectRoot, rel));
720
- if (prior !== outerSource) {
870
+ if (prior === null || !contentsEqualIgnoringEol(prior, outerSource)) {
721
871
  writeContained(projectRoot, rel, outerSource);
722
872
  if (prior === null)
723
873
  created.push(rel);
@@ -855,13 +1005,18 @@ export function ensureGitHooks(projectRoot, options = {}) {
855
1005
  const rawNext = override ?? file.body;
856
1006
  const next = rawNext.endsWith("\n") ? rawNext : `${rawNext}\n`;
857
1007
  const prior = readText(join(projectRoot, file.rel));
858
- if (prior !== null && prior !== next && !isOurGitHookBody(file.rel, prior)) {
1008
+ if (prior !== null &&
1009
+ !contentsEqualIgnoringEol(prior, next) &&
1010
+ !isOurGitHookBody(file.rel, prior)) {
859
1011
  conflicts.push(`${file.rel}: existing hook is not Directive-managed; refusing to overwrite (chain or remove manually)`);
860
1012
  continue;
861
1013
  }
862
1014
  // Tracked divergent preserve (#5837): never overwrite a committed hook body,
863
1015
  // even a Directive-managed one, unless the operator forced the replace.
864
- if (prior !== null && prior !== next && !force && isGitTrackedPath(projectRoot, file.rel)) {
1016
+ if (prior !== null &&
1017
+ !contentsEqualIgnoringEol(prior, next) &&
1018
+ !force &&
1019
+ isGitTrackedPath(projectRoot, file.rel)) {
865
1020
  preserved.push(formatTrackedHookPreserved(file.rel));
866
1021
  continue;
867
1022
  }
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ /** One remediation line for a code-bearing brief with no issue origin (#5710 P2). */
3
+ export declare const ISSUELESS_CODE_BEARING_REMEDIATION: string;
4
+ /** The P2 remediation line when the brief is code-bearing with no issue origin, else null. */
5
+ export declare function issuelessCodeBearingNote(briefPath: string): string | null;
2
6
  interface ParsedArgs {
3
7
  vbriefPath: string | null;
4
8
  emitJson: boolean;
@@ -1,9 +1,38 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
2
3
  import { dirname, resolve } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { emitJson, evaluate, PREFLIGHT_USAGE_HINT } from "../vendor/directive-core/dist/preflight/index.js";
5
6
  import { scanWorkClaimForBriefPath } from "../vendor/directive-core/dist/scm/index.js";
7
+ import { isCodeBearingScope, resolvePlanGithubIssueRef } from "../vendor/directive-core/dist/scope/index.js";
6
8
  import { refuseIfWslOwnershipBlocked } from "./wsl-ownership-guard.js";
9
+ /** One remediation line for a code-bearing brief with no issue origin (#5710 P2). */
10
+ export const ISSUELESS_CODE_BEARING_REMEDIATION = "[deft preflight] note: code-bearing brief has no x-xbrief/github-issue origin (#5710). " +
11
+ "PR-only complete: stamp plan.metadata.productPullRequest, then " +
12
+ "scope:complete -- --pr <that PR> --merge-commit <sha>. " +
13
+ "Issue-first: add an x-xbrief/github-issue reference before the product PR.";
14
+ /** The P2 remediation line when the brief is code-bearing with no issue origin, else null. */
15
+ export function issuelessCodeBearingNote(briefPath) {
16
+ let plan;
17
+ try {
18
+ const raw = JSON.parse(readFileSync(briefPath, "utf8"));
19
+ plan =
20
+ raw !== null && typeof raw === "object" && !Array.isArray(raw)
21
+ ? raw.plan
22
+ : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ if (plan === null || typeof plan !== "object" || Array.isArray(plan)) {
28
+ return null;
29
+ }
30
+ const record = plan;
31
+ if (!isCodeBearingScope(record) || resolvePlanGithubIssueRef(record) !== null) {
32
+ return null;
33
+ }
34
+ return ISSUELESS_CODE_BEARING_REMEDIATION;
35
+ }
7
36
  const HELP_TEXT = `usage: xbrief:preflight [--vbrief-path PATH | PATH] [--json] [--help]
8
37
 
9
38
  Implementation-intent gate (#810): exits 0 only when the xBRIEF is in
@@ -117,6 +146,11 @@ export function run(argv, scan = scanWorkClaimForBriefPath) {
117
146
  `[deft work-claim] warning: scan failed (${message}). Warn is success; this is not a GitHub lock.`,
118
147
  ];
119
148
  }
149
+ // #5710 P2: advisory only; the exit code is unchanged.
150
+ const issuelessNote = issuelessCodeBearingNote(vbriefPath);
151
+ if (issuelessNote !== null) {
152
+ scanLines = [issuelessNote, ...scanLines];
153
+ }
120
154
  if (args.emitJson) {
121
155
  const message = scanLines.length > 0 ? `${result.message}\n${scanLines.join("\n")}` : result.message;
122
156
  process.stdout.write(`${emitJson(vbriefPath, result.exitCode, message)}\n`);
@@ -8,9 +8,21 @@ interface ParsedArgs {
8
8
  /** Raw `--enforce` token present, even when a later `--warn` wins `enforce`. */
9
9
  enforcePresent: boolean;
10
10
  quiet: boolean;
11
+ /** PR number for the forge-approval admit (#6203); null when absent. */
12
+ prNumber: number | null;
13
+ /** OWNER/NAME for the forge-approval admit; empty means resolve. */
14
+ repo: string;
11
15
  error?: string;
12
16
  }
13
17
  export declare function parseArgs(argv: string[]): ParsedArgs;
14
- export declare function run(argv: string[]): number;
18
+ /**
19
+ * PR context for the forge-approval admit (#6203): flags win, then
20
+ * GITHUB_PR_NUMBER (digits only) / GITHUB_REPOSITORY. Null without a PR.
21
+ */
22
+ export declare function resolveForgeContext(args: Pick<ParsedArgs, "prNumber" | "repo">, env: NodeJS.ProcessEnv): {
23
+ prNumber: number;
24
+ repo: string | null;
25
+ } | null;
26
+ export declare function run(argv: string[], env?: NodeJS.ProcessEnv): number;
15
27
  export {};
16
28
  //# sourceMappingURL=verify-scope-provenance.d.ts.map
@@ -6,12 +6,21 @@
6
6
  * evaluated inside evaluateScopeProvenance. This CLI forwards --base-ref.
7
7
  * `--enforce` / `--warn` stay parsed for compatibility; `--enforce` is a
8
8
  * no-op after #4956 / #5002 and prints a stderr notice when present.
9
+ *
10
+ * `--pr N` / `--repo OWNER/NAME` (env fallback GITHUB_PR_NUMBER /
11
+ * GITHUB_REPOSITORY) supply PR context for the forge-approved head-brief
12
+ * membership admit (#6203). Without a PR number the admit is never consulted.
9
13
  */
10
14
  import { resolve } from "node:path";
11
15
  import { scopeProvenance } from "../vendor/directive-core/dist/index.js";
12
16
  import { isDirectEntrypoint } from "./entrypoint.js";
13
17
  /** Stderr line when argv includes `--enforce`. Exit code is unchanged. */
14
18
  export const ENFORCE_NOOP_NOTICE = "verify_scope_provenance: --enforce has no effect after #4956 / #5002";
19
+ const PR_DIGITS = /^[1-9][0-9]*$/;
20
+ function parsePr(value) {
21
+ const t = value.trim();
22
+ return PR_DIGITS.test(t) ? Number(t) : null;
23
+ }
15
24
  export function parseArgs(argv) {
16
25
  const parsed = {
17
26
  projectRoot: ".",
@@ -20,6 +29,8 @@ export function parseArgs(argv) {
20
29
  enforce: false,
21
30
  enforcePresent: false,
22
31
  quiet: false,
32
+ prNumber: null,
33
+ repo: "",
23
34
  };
24
35
  for (let i = 0; i < argv.length; i += 1) {
25
36
  const arg = argv[i];
@@ -55,22 +66,54 @@ export function parseArgs(argv) {
55
66
  else if (arg?.startsWith("--base-ref=")) {
56
67
  parsed.baseRef = arg.slice("--base-ref=".length);
57
68
  }
69
+ else if (arg === "--pr" || arg?.startsWith("--pr=")) {
70
+ const value = arg === "--pr" ? argv[i + 1] : arg.slice("--pr=".length);
71
+ if (arg === "--pr")
72
+ i += 1;
73
+ const pr = value === undefined ? null : parsePr(value);
74
+ if (pr === null) {
75
+ return { ...parsed, error: "argument --pr: expected a positive PR number" };
76
+ }
77
+ parsed.prNumber = pr;
78
+ }
79
+ else if (arg === "--repo" || arg?.startsWith("--repo=")) {
80
+ const value = arg === "--repo" ? argv[i + 1] : arg.slice("--repo=".length);
81
+ if (arg === "--repo")
82
+ i += 1;
83
+ if (value === undefined || value.trim().length === 0) {
84
+ return { ...parsed, error: "argument --repo: expected OWNER/NAME" };
85
+ }
86
+ parsed.repo = value.trim();
87
+ }
58
88
  else {
59
89
  return { ...parsed, error: `unrecognized argument: ${arg}` };
60
90
  }
61
91
  }
62
92
  return parsed;
63
93
  }
64
- export function run(argv) {
94
+ /**
95
+ * PR context for the forge-approval admit (#6203): flags win, then
96
+ * GITHUB_PR_NUMBER (digits only) / GITHUB_REPOSITORY. Null without a PR.
97
+ */
98
+ export function resolveForgeContext(args, env) {
99
+ const prNumber = args.prNumber ?? parsePr(env.GITHUB_PR_NUMBER ?? "");
100
+ if (prNumber === null)
101
+ return null;
102
+ const repo = args.repo.length > 0 ? args.repo : (env.GITHUB_REPOSITORY ?? "").trim();
103
+ return { prNumber, repo: repo.length > 0 ? repo : null };
104
+ }
105
+ export function run(argv, env = process.env) {
65
106
  const args = parseArgs(argv);
66
107
  if (args.error !== undefined) {
67
108
  process.stderr.write(`verify_scope_provenance: ${args.error}\n`);
68
109
  return 2;
69
110
  }
70
111
  const projectRoot = resolve(args.projectRoot);
112
+ const forgeApproval = resolveForgeContext(args, env);
71
113
  const result = scopeProvenance.evaluateScopeProvenance(projectRoot, {
72
114
  ...(args.baseRef.length > 0 ? { baseRef: args.baseRef } : {}),
73
115
  enforce: args.enforce,
116
+ ...(forgeApproval !== null ? { forgeApproval } : {}),
74
117
  });
75
118
  if (args.enforcePresent) {
76
119
  process.stderr.write(`${ENFORCE_NOOP_NOTICE}\n`);
@@ -10,7 +10,7 @@ export function parseArgs(argv) {
10
10
  if (arg === "--project-root" || arg === "--pack-root") {
11
11
  const value = argv[i + 1];
12
12
  if (value === undefined) {
13
- return { ...parsed, error: "argument " + arg + ": expected one argument" };
13
+ return { ...parsed, error: `argument ${arg}: expected one argument` };
14
14
  }
15
15
  parsed.projectRoot = value;
16
16
  parsed.packRootExplicit = arg === "--pack-root";
@@ -24,7 +24,7 @@ export function parseArgs(argv) {
24
24
  parsed.packRootExplicit = true;
25
25
  }
26
26
  else {
27
- return { ...parsed, error: "unrecognized argument: " + String(arg) };
27
+ return { ...parsed, error: `unrecognized argument: ${String(arg)}` };
28
28
  }
29
29
  }
30
30
  return parsed;
@@ -33,16 +33,16 @@ export function parseArgs(argv) {
33
33
  export function run(argv) {
34
34
  const args = parseArgs(argv);
35
35
  if (args.error !== undefined) {
36
- process.stderr.write("verify_deposit_closure: " + args.error + "\n");
36
+ process.stderr.write(`verify_deposit_closure: ${args.error}\n`);
37
37
  return 2;
38
38
  }
39
39
  const root = resolve(args.projectRoot);
40
40
  const result = evaluateDepositClosureAtRoot(root, args.packRootExplicit);
41
41
  if (result.stream === "stdout") {
42
- process.stdout.write(result.message + "\n");
42
+ process.stdout.write(`${result.message}\n`);
43
43
  }
44
44
  else {
45
- process.stderr.write(result.message + "\n");
45
+ process.stderr.write(`${result.message}\n`);
46
46
  }
47
47
  return result.code;
48
48
  }
@@ -1,5 +1,5 @@
1
- /** Fallback when package.json cannot be read. Keep aligned with the bin map. */
2
- export declare const PACKAGE_BIN_NAMES: readonly ["directive", "deft", "deft-ts", "deft-hook", "deft-verify-encoding"];
1
+ /** Fallback when package.json cannot be read. Keep aligned with launcher bin map (#5997). */
2
+ export declare const PACKAGE_BIN_NAMES: readonly ["directive", "deft", "deft-hook"];
3
3
  /** Node owns these shims. This package must not delete them. */
4
4
  export declare const NODE_OWNED_SHIM_NAMES: readonly ["npx", "npm"];
5
5
  export interface Ps1ShimRemoval {
@@ -1,24 +1,18 @@
1
1
  /**
2
2
  * npm's Windows bin linker (cmd-shim) always writes a .ps1 beside the .cmd.
3
3
  * PowerShell selects that .ps1 and stops when script execution is refused, so
4
- * the bare name never reaches the .cmd. This runs from postinstall, after
4
+ * the bare name never reaches the .cmd. Launcher postinstall runs this after
5
5
  * that link, and removes only this package's generated .ps1 files.
6
6
  * A .ps1 is removed only when its sibling .cmd records a target inside this
7
7
  * install, so an ambient PNPM_HOME from another install is left alone.
8
8
  * It does not change npx.ps1, npm.ps1, or the operator's execution policy.
9
- * Codex deposits bare deft-hook (#4659); this postinstall is what makes Restricted
10
- * Get-Command select deft-hook.cmd without ExecutionPolicy Bypass.
9
+ * Codex deposits bare deft-hook (#4659). Launcher postinstall (#5997) runs this
10
+ * cleanup so Restricted Get-Command selects deft-hook.cmd without Bypass.
11
11
  */
12
12
  import { existsSync, readFileSync, realpathSync, unlinkSync } from "node:fs";
13
13
  import { basename, dirname, join, win32 } from "node:path";
14
- /** Fallback when package.json cannot be read. Keep aligned with the bin map. */
15
- export const PACKAGE_BIN_NAMES = [
16
- "directive",
17
- "deft",
18
- "deft-ts",
19
- "deft-hook",
20
- "deft-verify-encoding",
21
- ];
14
+ /** Fallback when package.json cannot be read. Keep aligned with launcher bin map (#5997). */
15
+ export const PACKAGE_BIN_NAMES = ["directive", "deft", "deft-hook"];
22
16
  /** Node owns these shims. This package must not delete them. */
23
17
  export const NODE_OWNED_SHIM_NAMES = ["npx", "npm"];
24
18
  export function packageIsUnderNodeModules(pkgDir) {
@@ -82,11 +82,11 @@ rm .deft-directive-disable
82
82
  # Start a NEW agent session
83
83
  ```
84
84
 
85
- ## Agent self-serve ban under UAT (#3186)
85
+ ## Agent self-serve ban (always-on, grant-gated) (#3186 / #6136)
86
86
 
87
- Operators plant and remove this flag **outside** an agent session (or with a prior human-origin grant). Under **active UAT**, PreToolUse classifies Shell writes that create `.deft-directive-disable` (and permanent opt-out `.no-deft-directive`) as **settings** and **denies** them without a human grant — same posture as `authz:grant` (#3110). Agents must not self-serve the kill-switch to bypass Write / push / PR / merge gates.
87
+ Operators plant and remove this flag **outside** an agent session (or with a prior human-origin grant covering **settings**). PreToolUse **always** denies agent Write / Edit / Shell that creates or renames-to `.deft-directive-disable` (and permanent opt-out `.no-deft-directive`) without that grant — not only under active UAT. Shell write reissue checks the protected basename **before** skipping out-of-tree dests, so a plant outside the repo tree cannot fail-open. Agents must not self-serve the kill-switch to bypass Write / push / PR / merge gates.
88
88
 
89
- Related: `policy:allow-bot-merge`, `policy:allow-direct-commits`, and `policy:disable-directive` (peers) are also settings under UAT and require a human grant.
89
+ Related: `policy:allow-bot-merge`, `policy:allow-direct-commits`, and `policy:disable-directive` (peers) are settings under UAT and require a human grant.
90
90
 
91
91
  ## Non-goals (v1)
92
92
 
@@ -58,6 +58,8 @@ directive doctor
58
58
 
59
59
  After clone or a new worktree: run the project package manager (`npm install` / `pnpm install`), then the first eligible runtime command or registered hook completes local activation.
60
60
 
61
+ **Dependabot / pin-only bumps (AC14.10):** a PR that changes only the `@deftai/directive-runtime` pin + lockfile is valid. After `pnpm install` / `npm install` brings `node_modules` to the new pin, the first eligible skill, hook, or ordinary runtime command runs local automatic setup (wiring / `GENERATION` / readiness). `directive doctor` stays read-only — dirty doctor output before that eligible invocation is expected. CI may either rely on that first eligible command or run explicit `directive install --repair --yes`. Do not treat "deposit bytes must land in the same pin PR" as product law. Human-test mirror: HT-17 in `docs/installer-refactor-ovpr/install-ovpr-human-test-plan.md`. Schema/data behind the new pin still needs `directive upgrade` (AC14.11), not repair alone.
62
+
61
63
  **Repair (pin unchanged):** `npm i -g @deftai/directive`, then `directive install --repair`.
62
64
 
63
65
  **Upgrade version or migrate data:** `directive upgrade` (alias: `update`).
@@ -45,10 +45,24 @@ Allowance = clamp(count of **concrete** production files on the merge-base `file
45
45
  When the bound story is in the change set:
46
46
 
47
47
  1. A human-stamped merge-base mint for the **continuity-resolved** story is the allowlist when present (planId match ignores stale `xbriefRelPath`; no-`plan.id` is path-first + basename-keyed mint with matching `xbriefRelPath`).
48
- 2. Otherwise membership evaluates against the merge-base brief's **concrete** `file_scope` as agent precommitment. Head brief cannot widen.
48
+ 2. Otherwise membership evaluates against the merge-base brief's **concrete** `file_scope` as agent precommitment. Head brief cannot widen, except under a qualifying forge approval (#6203, below).
49
49
  3. Free paths: bound brief, verified peer briefs, `CHANGELOG.md`, and continuity lifecycle paths of the bound identity. An xBRIEF-only change set may omit a mint.
50
50
  4. Empty / invalid / unreadable mint records fail closed (they are not absence).
51
51
 
52
+ #### Forge-approved head brief (#6203)
53
+
54
+ A PR can carry a new or widened brief plus its code when a human approves it on GitHub. With PR context (`--pr N` / `--repo OWNER/NAME`, or `GITHUB_PR_NUMBER` / `GITHUB_REPOSITORY` in CI), a failing membership check under precommitment or missing authority consults the forge. When a qualifying approval exists, membership evaluates against the **head** brief's concrete `file_scope` instead of the merge-base brief.
55
+
56
+ - **Mint wins.** A present mint keeps the mint path. The forge is never consulted.
57
+ - **Auth-hash v1** (`sha256:` over canonical JSON; strings byte-exact, no Unicode normalization) covers `plan.id`, the xbrief path, the concrete `file_scope`, and the acceptanceCriteria array: `plan.acceptance`, `plan.narratives.AcceptanceCriteria`, and each item's `narrative.Acceptance`, in document order. `productPullRequest` and other delivery metadata stay out, so a PPR stamp keeps the approval. Any change to the covered fields drops it.
58
+ - **Qualifying approval:** the reviewer's latest deciding review (APPROVED / CHANGES_REQUESTED / DISMISSED) is APPROVED; the reviewer is a `User` (not Bot or App), not the PR author, and has `write`, `maintain` or `admin` on the repo; the repo is the checkout's `origin` (a `--repo` value may only confirm it, so a fork's approval never counts) and is the PR's base repo; the evaluated HEAD is the PR head (or GitHub's merge commit whose second parent is the PR head); and the brief at the reviewed commit hashes the same as the head brief.
59
+ - **Fallback:** missing PR context, no qualifying approval, a hash mismatch, or an unavailable forge keeps today's Path B or mint finding unchanged. The result message adds one `forge approval (#6203)` line with the reason.
60
+ - **Membership only.** The production fence still reads the merge-base brief. A widened brief can pass membership and still fail `production-scope-over-budget`. A brand-new brief has no production fence (as today).
61
+ - **Re-run after approval.** Approval does not re-trigger CI. Re-run the merge gate (or push) after the approval lands.
62
+ - **Residual (#6207):** an agent that holds a second human forge token can satisfy every predicate above. The hash binds the approved bytes; it does not prove independent human intent and does not close that confused-deputy risk. That posture stays open on #6207.
63
+
64
+ Code: `packages/core/src/scope-provenance/forge-approval.ts`.
65
+
52
66
  #### Path A planning split (#5551)
53
67
 
54
68
  When an active implementation brief is **outside** the change set, membership is a no-op for that identity (Path A). Supported planning publication is two change sets, not one:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-runtime",
3
- "version": "0.129.5",
3
+ "version": "0.129.7",
4
4
  "description": "Directive runtime — pinned product commands, hooks, content, and engine.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://deftai.github.io/directive/",
@@ -82,7 +82,6 @@
82
82
  "typescript": "^5.9.3"
83
83
  },
84
84
  "scripts": {
85
- "build": "tsc -b",
86
- "postinstall": "node ./scripts/remove-windows-ps1-shims.mjs"
85
+ "build": "tsc -b"
87
86
  }
88
87
  }
@@ -711,6 +711,21 @@
711
711
  ],
712
712
  "source": "Issue #4888 Prefer-A Bound 6050812821 (parent #964). conflict_group mergeable-state-semantic-policy-invisibility.",
713
713
  "body": "**Source:** Issue #4888 Prefer-A Bound 6050812821 (split from parent #964).\n\n**Failure mode:** GitHub `mergeable_state` / `mergeable` report only textual (axis-1) conflicts. After a PR opens, behavioral (CI/test) and policy (`task check` / required contexts) gates can land on the base without a textual conflict. Agents read dirty/clean as readiness or gap-size and miss the invisible policy miss until `task check` runs on the merge result.\n\n**Rule:** Use the three-axis model (textual / behavioral / policy). On conflict-resolve and behind-rebase (PR open; local HEAD matches live remote PR head), require machine-checkable candidate validation keyed to `(pr_head_sha, target_head_sha)` via `evaluateNonCascadeCandidateGreen` / `evaluateSemanticGreen({ requireCandidateValidation: true })`. Attestation is a named allowlisted probe exit 0 (typically pre-pr `task check` / `deft check`); prose stamps are not success. On pre-push, run that local merge-chokepoint gate before push; require the SHA-pair attestation only after push when local HEAD matches the live remote PR head. Do not weaken `--cascade` absolute stale-base refusal. Keep #4889 out; do not satisfy with a withdrawn Python semantic-drift script SoT. Forge supersession is #633/#1517 required Merge gate aggregator + up-to-date-required; local probe is interim.\n\n**Canonical encoding (strongest-applicable layer):** probe in `packages/core/src/pr-wait-mergeable/semantic-green.ts` (`requireCandidateValidation` / `validateCandidateAttestation`); operative MUST in `content/scm/github.md` § Cascade / Three-axis (#4888) and `skills/deft-directive-pre-pr/SKILL.md` merge-chokepoint attach; forge supersession in `content/scm/github.md` § Merge-gate aggregator (#633). AGENTS/agents-entry pointer only.\n\n**Cross-references:** #4888, #964, #2385, #1369, #633, #1517, #4883 (CLEAN = axis-1 necessity), #4889 (kept out)."
714
+ },
715
+ {
716
+ "id": "edit-files-search-strings-must-capture-full-units-not-substrings-2026-05",
717
+ "title": "edit_files search strings MUST capture full units, not substrings (2026-05)",
718
+ "date": "2026-05",
719
+ "issue_refs": [
720
+ "#1037"
721
+ ],
722
+ "tags": [
723
+ "debugging",
724
+ "agent-orchestration",
725
+ "github"
726
+ ],
727
+ "source": "Issue #1037 Prefer-A Bound 6689115138. Recurrence: PR #1034 (2026-05-11); #911 cascade (2026-05-04).",
728
+ "body": "**Source:** Issue #1037 Prefer-A Bound 6689115138. Recurrence: PR #1034 (2026-05-11) Greptile P1 on truncated CHANGELOG #441 close-out bullet; sibling outcome class in the #911 cascade (2026-05-04).\n\n**Failure mode:** Exact-string replace tools (`edit_files` / StrReplace-class) replace only the matched `search` bytes. When the agent anchors on a substring of a longer single-line or multi-line entry (CHANGELOG bullet, ROADMAP list item, section body), the unmatched overflow past the search anchor is deleted with the match. The tool did what it was asked; the agent chose too short a search.\n\n**Rule:** When using exact-string-match replacement to modify a single-line or multi-line entry, the `search` value MUST match the FULL line / FULL entry being targeted, including any trailing prose / continuation the agent intends to preserve.\n\n**Anti-pattern:** Pick the first N words of a long bullet as the search anchor; the replacement silently truncates the bullet at the end of the matched substring.\n\n**Mitigation:**\n- ! When targeting a markdown bullet, the `search` value MUST extend to the next blank line or the next top-level list marker, whichever comes first. Re-read the file and capture ALL bytes that belong to the targeted entry.\n- ! When adding a new bullet ABOVE an existing one, an even safer pattern is to anchor `search` on the section heading (e.g. `### Refinement`) and re-emit the heading in `replace` followed by the new bullet and the existing first bullet -- but you MUST still capture every byte of that existing first bullet.\n- ! After every exact-string replace that touches CHANGELOG.md, ROADMAP.md, or any other append-only narrative file, re-read the surrounding 20 lines of the edited region and verify nothing was truncated. This is part of the standard pre-commit file review.\n\n**Recurrence record:**\n- PR #1034 (2026-05-11) -- CHANGELOG.md `### Refinement` first bullet truncated when adding a new bullet above it; Greptile caught it as a P1 in review-cycle.\n- PR #911 cascade (2026-05-04) -- different mechanism (HEAD-only slice resolution), same outcome class: CHANGELOG `[Unreleased]` branch entries dropped on conflict resolution.\n\n**Canonical encoding:** lessons-pack-0.1 entry (this body); projected via `task packs:render` into `meta/lessons.md`. Lesson-tier only -- no new AGENTS.md always-pin; optional `verify:changelog-bullets` stays out of Bound (#1037 Prefer-A Reject).\n\n**Cross-references:** Issue #1037, Prefer-A Bound 6689115138, PR #1034, PR #911 cascade, `skills/deft-directive-build/SKILL.md` / `skills/deft-directive-refinement/SKILL.md` pre-commit file review."
714
729
  }
715
730
  ]
716
731
  }