@deftai/directive-runtime 0.127.0 → 0.128.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 (230) hide show
  1. package/.githooks/_directive-run.sh +30 -0
  2. package/.githooks/pre-commit +4 -24
  3. package/.githooks/pre-push +11 -17
  4. package/Taskfile.yml +16 -15
  5. package/contracts/design-critique.md +5 -0
  6. package/dist/cli-router/route-argv.js +1 -0
  7. package/dist/coverage-hotspots.js +2 -2
  8. package/dist/dispatch.d.ts +1 -1
  9. package/dist/dispatch.js +7 -0
  10. package/dist/policy.d.ts +4 -2
  11. package/dist/policy.js +101 -2
  12. package/dist/pre-pr-run.d.ts +2 -0
  13. package/dist/pre-pr-run.js +40 -0
  14. package/dist/render-cli/capability-map-cli.d.ts +3 -0
  15. package/dist/render-cli/capability-map-cli.js +6 -0
  16. package/dist/setup-wiring.d.ts +2 -0
  17. package/dist/setup-wiring.js +14 -0
  18. package/docs/getting-started.md +2 -0
  19. package/docs/known-limitations.md +1 -0
  20. package/docs/launcher.md +10 -0
  21. package/package.json +1 -1
  22. package/packs/skills/skills-pack-0.1.json +1 -1
  23. package/skills/deft-directive-pre-pr/SKILL.md +13 -3
  24. package/tasks/agent.yml +0 -2
  25. package/tasks/cache.yml +0 -16
  26. package/tasks/capacity.yml +0 -4
  27. package/tasks/change.yml +0 -4
  28. package/tasks/codebase.yml +0 -10
  29. package/tasks/commit.yml +0 -2
  30. package/tasks/coverage.yml +0 -2
  31. package/tasks/decision.yml +0 -4
  32. package/tasks/directive.yml +0 -1
  33. package/tasks/docs.yml +6 -10
  34. package/tasks/engine-invoke.cjs +79 -0
  35. package/tasks/engine-invoke.test.cjs +84 -0
  36. package/tasks/engine.yml +27 -88
  37. package/tasks/eval.yml +0 -8
  38. package/tasks/feedback.yml +0 -2
  39. package/tasks/install.yml +0 -4
  40. package/tasks/issue.yml +0 -3
  41. package/tasks/lifecycle.yml +0 -1
  42. package/tasks/materialize.cjs +246 -0
  43. package/tasks/materialize.test.cjs +103 -0
  44. package/tasks/materialize.yml +13 -0
  45. package/tasks/migrate.yml +0 -6
  46. package/tasks/packs.yml +0 -16
  47. package/tasks/plan-sequence.yml +0 -8
  48. package/tasks/policy.yml +0 -24
  49. package/tasks/pr.yml +0 -6
  50. package/tasks/prd.yml +0 -2
  51. package/tasks/product-signal.yml +0 -10
  52. package/tasks/project.yml +0 -8
  53. package/tasks/reconcile.yml +0 -2
  54. package/tasks/review-monitor.yml +0 -4
  55. package/tasks/roadmap.yml +0 -4
  56. package/tasks/scm.yml +0 -32
  57. package/tasks/scope.yml +0 -30
  58. package/tasks/slice.yml +0 -4
  59. package/tasks/spec.yml +0 -6
  60. package/tasks/subagent.yml +0 -4
  61. package/tasks/swarm.yml +0 -7
  62. package/tasks/toolchain.yml +0 -4
  63. package/tasks/triage-actions.yml +0 -8
  64. package/tasks/triage-bootstrap.yml +0 -1
  65. package/tasks/triage-bulk.yml +0 -5
  66. package/tasks/triage-classify.yml +0 -2
  67. package/tasks/triage-evaluate.yml +0 -1
  68. package/tasks/triage-metrics.yml +0 -2
  69. package/tasks/triage-queue.yml +0 -3
  70. package/tasks/triage-reconcile.yml +0 -1
  71. package/tasks/triage-scope-drift.yml +0 -1
  72. package/tasks/triage-scope.yml +0 -1
  73. package/tasks/triage-smoketest.yml +0 -1
  74. package/tasks/triage-subscribe.yml +0 -2
  75. package/tasks/tutorial.yml +0 -20
  76. package/tasks/umbrella.yml +0 -2
  77. package/tasks/value.yml +0 -2
  78. package/tasks/vbrief.yml +0 -12
  79. package/tasks/verify.yml +0 -140
  80. package/tasks/xbrief.yml +0 -6
  81. package/templates/design-critique-brief.md +3 -0
  82. package/vendor/directive-content/.githooks/_directive-run.sh +30 -0
  83. package/vendor/directive-content/.githooks/pre-commit +4 -24
  84. package/vendor/directive-content/.githooks/pre-push +11 -17
  85. package/vendor/directive-content/Taskfile.yml +16 -15
  86. package/vendor/directive-content/contracts/design-critique.md +5 -0
  87. package/vendor/directive-content/docs/getting-started.md +2 -0
  88. package/vendor/directive-content/docs/known-limitations.md +1 -0
  89. package/vendor/directive-content/docs/launcher.md +10 -0
  90. package/vendor/directive-content/packs/skills/skills-pack-0.1.json +1 -1
  91. package/vendor/directive-content/skills/deft-directive-pre-pr/SKILL.md +13 -3
  92. package/vendor/directive-content/tasks/agent.yml +0 -2
  93. package/vendor/directive-content/tasks/cache.yml +0 -16
  94. package/vendor/directive-content/tasks/capacity.yml +0 -4
  95. package/vendor/directive-content/tasks/change.yml +0 -4
  96. package/vendor/directive-content/tasks/codebase.yml +0 -10
  97. package/vendor/directive-content/tasks/commit.yml +0 -2
  98. package/vendor/directive-content/tasks/coverage.yml +0 -2
  99. package/vendor/directive-content/tasks/decision.yml +0 -4
  100. package/vendor/directive-content/tasks/directive.yml +0 -1
  101. package/vendor/directive-content/tasks/docs.yml +6 -10
  102. package/vendor/directive-content/tasks/engine-invoke.cjs +79 -0
  103. package/vendor/directive-content/tasks/engine-invoke.test.cjs +84 -0
  104. package/vendor/directive-content/tasks/engine.yml +27 -88
  105. package/vendor/directive-content/tasks/eval.yml +0 -8
  106. package/vendor/directive-content/tasks/feedback.yml +0 -2
  107. package/vendor/directive-content/tasks/install.yml +0 -4
  108. package/vendor/directive-content/tasks/issue.yml +0 -3
  109. package/vendor/directive-content/tasks/lifecycle.yml +0 -1
  110. package/vendor/directive-content/tasks/materialize.cjs +246 -0
  111. package/vendor/directive-content/tasks/materialize.test.cjs +103 -0
  112. package/vendor/directive-content/tasks/materialize.yml +13 -0
  113. package/vendor/directive-content/tasks/migrate.yml +0 -6
  114. package/vendor/directive-content/tasks/packs.yml +0 -16
  115. package/vendor/directive-content/tasks/plan-sequence.yml +0 -8
  116. package/vendor/directive-content/tasks/policy.yml +0 -24
  117. package/vendor/directive-content/tasks/pr.yml +0 -6
  118. package/vendor/directive-content/tasks/prd.yml +0 -2
  119. package/vendor/directive-content/tasks/product-signal.yml +0 -10
  120. package/vendor/directive-content/tasks/project.yml +0 -8
  121. package/vendor/directive-content/tasks/reconcile.yml +0 -2
  122. package/vendor/directive-content/tasks/review-monitor.yml +0 -4
  123. package/vendor/directive-content/tasks/roadmap.yml +0 -4
  124. package/vendor/directive-content/tasks/scm.yml +0 -32
  125. package/vendor/directive-content/tasks/scope.yml +0 -30
  126. package/vendor/directive-content/tasks/slice.yml +0 -4
  127. package/vendor/directive-content/tasks/spec.yml +0 -6
  128. package/vendor/directive-content/tasks/subagent.yml +0 -4
  129. package/vendor/directive-content/tasks/swarm.yml +0 -7
  130. package/vendor/directive-content/tasks/toolchain.yml +0 -4
  131. package/vendor/directive-content/tasks/triage-actions.yml +0 -8
  132. package/vendor/directive-content/tasks/triage-bootstrap.yml +0 -1
  133. package/vendor/directive-content/tasks/triage-bulk.yml +0 -5
  134. package/vendor/directive-content/tasks/triage-classify.yml +0 -2
  135. package/vendor/directive-content/tasks/triage-evaluate.yml +0 -1
  136. package/vendor/directive-content/tasks/triage-metrics.yml +0 -2
  137. package/vendor/directive-content/tasks/triage-queue.yml +0 -3
  138. package/vendor/directive-content/tasks/triage-reconcile.yml +0 -1
  139. package/vendor/directive-content/tasks/triage-scope-drift.yml +0 -1
  140. package/vendor/directive-content/tasks/triage-scope.yml +0 -1
  141. package/vendor/directive-content/tasks/triage-smoketest.yml +0 -1
  142. package/vendor/directive-content/tasks/triage-subscribe.yml +0 -2
  143. package/vendor/directive-content/tasks/tutorial.yml +0 -20
  144. package/vendor/directive-content/tasks/umbrella.yml +0 -2
  145. package/vendor/directive-content/tasks/value.yml +0 -2
  146. package/vendor/directive-content/tasks/vbrief.yml +0 -12
  147. package/vendor/directive-content/tasks/verify.yml +0 -140
  148. package/vendor/directive-content/tasks/xbrief.yml +0 -6
  149. package/vendor/directive-content/templates/design-critique-brief.md +3 -0
  150. package/vendor/directive-core/dist/.tsbuildinfo +1 -1
  151. package/vendor/directive-core/dist/content-root.d.ts.map +1 -1
  152. package/vendor/directive-core/dist/content-root.js +8 -1
  153. package/vendor/directive-core/dist/content-root.js.map +1 -1
  154. package/vendor/directive-core/dist/coverage-hotspots/evaluate.d.ts +13 -5
  155. package/vendor/directive-core/dist/coverage-hotspots/evaluate.d.ts.map +1 -1
  156. package/vendor/directive-core/dist/coverage-hotspots/evaluate.js +60 -2
  157. package/vendor/directive-core/dist/coverage-hotspots/evaluate.js.map +1 -1
  158. package/vendor/directive-core/dist/doctor/checks.d.ts.map +1 -1
  159. package/vendor/directive-core/dist/doctor/checks.js +5 -3
  160. package/vendor/directive-core/dist/doctor/checks.js.map +1 -1
  161. package/vendor/directive-core/dist/index.d.ts +1 -0
  162. package/vendor/directive-core/dist/index.d.ts.map +1 -1
  163. package/vendor/directive-core/dist/index.js +1 -0
  164. package/vendor/directive-core/dist/index.js.map +1 -1
  165. package/vendor/directive-core/dist/init-deposit/scaffold.d.ts +1 -1
  166. package/vendor/directive-core/dist/init-deposit/scaffold.d.ts.map +1 -1
  167. package/vendor/directive-core/dist/init-deposit/scaffold.js +1 -1
  168. package/vendor/directive-core/dist/init-deposit/scaffold.js.map +1 -1
  169. package/vendor/directive-core/dist/policy/coverage-mode.d.ts +84 -0
  170. package/vendor/directive-core/dist/policy/coverage-mode.d.ts.map +1 -0
  171. package/vendor/directive-core/dist/policy/coverage-mode.js +226 -0
  172. package/vendor/directive-core/dist/policy/coverage-mode.js.map +1 -0
  173. package/vendor/directive-core/dist/policy/index.d.ts +2 -0
  174. package/vendor/directive-core/dist/policy/index.d.ts.map +1 -1
  175. package/vendor/directive-core/dist/policy/index.js +60 -40
  176. package/vendor/directive-core/dist/policy/index.js.map +1 -1
  177. package/vendor/directive-core/dist/pr-closing-keywords/main.d.ts.map +1 -1
  178. package/vendor/directive-core/dist/pr-closing-keywords/main.js +137 -7
  179. package/vendor/directive-core/dist/pr-closing-keywords/main.js.map +1 -1
  180. package/vendor/directive-core/dist/pre-pr-controller/controller.d.ts +2 -0
  181. package/vendor/directive-core/dist/pre-pr-controller/controller.d.ts.map +1 -1
  182. package/vendor/directive-core/dist/pre-pr-controller/controller.js +109 -14
  183. package/vendor/directive-core/dist/pre-pr-controller/controller.js.map +1 -1
  184. package/vendor/directive-core/dist/pre-pr-controller/evaluate.d.ts +2 -0
  185. package/vendor/directive-core/dist/pre-pr-controller/evaluate.d.ts.map +1 -1
  186. package/vendor/directive-core/dist/pre-pr-controller/evaluate.js +11 -1
  187. package/vendor/directive-core/dist/pre-pr-controller/evaluate.js.map +1 -1
  188. package/vendor/directive-core/dist/pre-pr-controller/index.d.ts +1 -1
  189. package/vendor/directive-core/dist/pre-pr-controller/index.d.ts.map +1 -1
  190. package/vendor/directive-core/dist/pre-pr-controller/index.js +1 -1
  191. package/vendor/directive-core/dist/pre-pr-controller/index.js.map +1 -1
  192. package/vendor/directive-core/dist/pre-pr-controller/phases.d.ts +10 -0
  193. package/vendor/directive-core/dist/pre-pr-controller/phases.d.ts.map +1 -1
  194. package/vendor/directive-core/dist/pre-pr-controller/phases.js +15 -1
  195. package/vendor/directive-core/dist/pre-pr-controller/phases.js.map +1 -1
  196. package/vendor/directive-core/dist/pre-pr-controller/types.d.ts +20 -1
  197. package/vendor/directive-core/dist/pre-pr-controller/types.d.ts.map +1 -1
  198. package/vendor/directive-core/dist/pre-pr-controller/types.js +2 -0
  199. package/vendor/directive-core/dist/pre-pr-controller/types.js.map +1 -1
  200. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts +40 -3
  201. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts.map +1 -1
  202. package/vendor/directive-core/dist/preflight-gh/pre-push.js +340 -27
  203. package/vendor/directive-core/dist/preflight-gh/pre-push.js.map +1 -1
  204. package/vendor/directive-core/dist/render/project-render.d.ts.map +1 -1
  205. package/vendor/directive-core/dist/render/project-render.js +1 -0
  206. package/vendor/directive-core/dist/render/project-render.js.map +1 -1
  207. package/vendor/directive-core/dist/scope/file-scope-coverage-warn.d.ts +21 -0
  208. package/vendor/directive-core/dist/scope/file-scope-coverage-warn.d.ts.map +1 -0
  209. package/vendor/directive-core/dist/scope/file-scope-coverage-warn.js +95 -0
  210. package/vendor/directive-core/dist/scope/file-scope-coverage-warn.js.map +1 -0
  211. package/vendor/directive-core/dist/swarm/launch-cli.d.ts.map +1 -1
  212. package/vendor/directive-core/dist/swarm/launch-cli.js +12 -0
  213. package/vendor/directive-core/dist/swarm/launch-cli.js.map +1 -1
  214. package/vendor/directive-core/dist/swarm/launch.d.ts +6 -0
  215. package/vendor/directive-core/dist/swarm/launch.d.ts.map +1 -1
  216. package/vendor/directive-core/dist/swarm/launch.js +11 -0
  217. package/vendor/directive-core/dist/swarm/launch.js.map +1 -1
  218. package/vendor/directive-core/dist/swarm/leftover-before-activation.d.ts +27 -0
  219. package/vendor/directive-core/dist/swarm/leftover-before-activation.d.ts.map +1 -0
  220. package/vendor/directive-core/dist/swarm/leftover-before-activation.js +31 -0
  221. package/vendor/directive-core/dist/swarm/leftover-before-activation.js.map +1 -0
  222. package/vendor/directive-core/dist/verify-env/verify-hooks-installed.d.ts +3 -3
  223. package/vendor/directive-core/dist/verify-env/verify-hooks-installed.d.ts.map +1 -1
  224. package/vendor/directive-core/dist/verify-env/verify-hooks-installed.js +7 -11
  225. package/vendor/directive-core/dist/verify-env/verify-hooks-installed.js.map +1 -1
  226. package/vendor/directive-core/dist/verify-source/project-definition-mutation-boundary.d.ts.map +1 -1
  227. package/vendor/directive-core/dist/verify-source/project-definition-mutation-boundary.js +1 -0
  228. package/vendor/directive-core/dist/verify-source/project-definition-mutation-boundary.js.map +1 -1
  229. package/.githooks/_deft-run.sh +0 -47
  230. package/vendor/directive-content/.githooks/_deft-run.sh +0 -47
@@ -533,3 +533,87 @@ describe("buildSpawnPlan — CodeQL absolute-path isolation (#3175 / alert #74)"
533
533
  assert.deepEqual(plan.args, [String.raw`C:\repo\packages\cli\dist\bin.js`, "session:start"]);
534
534
  });
535
535
  });
536
+
537
+ describe("framework pin exclusive invoke (#5805)", () => {
538
+ const {
539
+ resolveFrameworkPinBin,
540
+ hasFrameworkPinBin,
541
+ resolveFrameworkSourceInvoke,
542
+ PIN_INSTALL_REMEDIATION,
543
+ PIN_MATERIALIZE_REMEDIATION,
544
+ } = require("./engine-invoke.cjs");
545
+ const pinRoots = [];
546
+ function pinTempRoot() {
547
+ const root = mkdtempSync(join(tmpdir(), "pin-inv-"));
548
+ pinRoots.push(root);
549
+ return root;
550
+ }
551
+ after(() => {
552
+ for (const root of pinRoots) {
553
+ try {
554
+ rmSync(root, { recursive: true, force: true });
555
+ } catch {
556
+ /* ignore */
557
+ }
558
+ }
559
+ });
560
+
561
+ it("resolveFrameworkPinBin joins checkout pin path", () => {
562
+ const pin = resolveFrameworkPinBin(join("C:", "repo"));
563
+ assert.match(pin.replace(/\\/g, "/"), /node_modules\/@deftai\/directive-runtime\/dist\/bin\.js$/);
564
+ });
565
+
566
+ it("hasFrameworkPinBin is false when missing", () => {
567
+ const root = pinTempRoot();
568
+ assert.equal(hasFrameworkPinBin(root), false);
569
+ });
570
+
571
+ it("hasFrameworkPinBin is true when pin bin exists", () => {
572
+ const root = pinTempRoot();
573
+ const pin = resolveFrameworkPinBin(root);
574
+ mkdirSync(require("node:path").dirname(pin), { recursive: true });
575
+ writeFileSync(pin, "module.exports={}\n");
576
+ assert.equal(hasFrameworkPinBin(root), true);
577
+ });
578
+
579
+ it("framework-source with pin selects vendored pin (not tip dist)", () => {
580
+ const plan = resolveFrameworkSourceInvoke({
581
+ hasBin: true,
582
+ isBuildableSource: true,
583
+ hasPinBin: true,
584
+ });
585
+ assert.deepEqual(plan, { action: "vendored", binKind: "pin" });
586
+ });
587
+
588
+ it("framework-source missing pin fails closed even when tip dist / global exist", () => {
589
+ const plan = resolveFrameworkSourceInvoke({
590
+ hasBin: true,
591
+ isBuildableSource: true,
592
+ hasPinBin: false,
593
+ hasGlobalCli: true,
594
+ isRuntimeVerb: true,
595
+ });
596
+ assert.equal(plan.action, "fail-closed");
597
+ assert.equal(plan.exitCode, 2);
598
+ assert.deepEqual(plan.remediations, [PIN_INSTALL_REMEDIATION, PIN_MATERIALIZE_REMEDIATION]);
599
+ });
600
+
601
+ it("deposit path keeps prior resolveInvokeDispatch behavior", () => {
602
+ const plan = resolveFrameworkSourceInvoke({
603
+ hasBin: false,
604
+ isBuildableSource: false,
605
+ hasPinBin: false,
606
+ hasGlobalCli: true,
607
+ isRuntimeVerb: false,
608
+ });
609
+ assert.deepEqual(plan, { action: "global" });
610
+ });
611
+
612
+ it("pin-bin CLI exits 2 when missing", () => {
613
+ const root = pinTempRoot();
614
+ const script = join(__dirname, "engine-invoke.cjs");
615
+ const r = spawnSync(process.execPath, [script, "pin-bin", root], { encoding: "utf8" });
616
+ assert.equal(r.status, 2);
617
+ assert.match(r.stderr, /missing checkout pin/);
618
+ });
619
+ });
package/tasks/engine.yml CHANGED
@@ -2,11 +2,13 @@ version: '3'
2
2
 
3
3
  # Consumer engine resolution (#2022 Phase 3).
4
4
  #
5
- # Framework-source checkouts invoke the vendored `packages/runtime/dist/bin.js`.
5
+ # Framework-source checkouts invoke the checkout pin
6
+ # `node_modules/@deftai/directive-runtime/dist/bin.js` exclusively (#5805 / AC9.9).
7
+ # Tip dogfood without publish is `task materialize:runtime`, not packages/runtime/dist.
6
8
  # npm consumer deposits copy @deftai/directive-content only — no bundled CLI —
7
- # so tasks fall back to the globally installed `deft` / `directive` command.
9
+ # so tasks fall back to OVPR DEFT_ROOT/dist/bin.js or global `deft` / `directive`.
8
10
  # A deposit marker (package.json deftConsumerDeposit / .deft-consumer-deposit /
9
- # @deftai/directive-content name) forces that fallback for every verb (#3324).
11
+ # @deftai/directive-content name) forces that deposit path for every verb (#3324).
10
12
 
11
13
  vars:
12
14
  DEFT_ROOT: '{{joinPath .TASKFILE_DIR ".."}}'
@@ -59,7 +61,7 @@ tasks:
59
61
 
60
62
  invoke:
61
63
  internal: true
62
- desc: "Run a deft-ts verb from vendored bin.js (source checkout) or global deft (deposit / runtime fallback #2409 / #3324). Runtime/session verbs must not trigger engine:_ts-build / pnpm (#2181)."
64
+ desc: "Run a deft-ts verb from the checkout pin (framework-source #5805) or OVPR/global deposit path (#2409 / #3324). Ordinary framework-source verbs never fall through to packages/runtime/dist."
63
65
  # Run from the operator project root so deft verbs resolve USER_WORKING_DIR
64
66
  # correctly; without this, included engine.yml defaults cwd to tasks/ (#2022).
65
67
  dir: '{{.USER_WORKING_DIR}}'
@@ -71,23 +73,18 @@ tasks:
71
73
  cmds:
72
74
  - |
73
75
  set -eu
74
- # OVPR consumer: DEFT_ROOT is node_modules/@deftai/directive-runtime → dist/bin.js.
75
- # Framework source / legacy .deft/core: DEFT_ROOT/packages/runtime/dist/bin.js.
76
- bin=""
77
- if [ -f "{{.DEFT_ROOT}}/dist/bin.js" ]; then
78
- bin="{{.DEFT_ROOT}}/dist/bin.js"
79
- elif [ -f "{{.DEFT_ROOT}}/packages/runtime/dist/bin.js" ]; then
80
- bin="{{.DEFT_ROOT}}/packages/runtime/dist/bin.js"
81
- fi
82
- root_pkg="{{.DEFT_ROOT}}/package.json"
83
- # #3324: deposit marker (package.json field / file / content-package
84
- # name) forces is_buildable_source=0 so every verb uses global CLI.
76
+ # #3324: deposit marker forces is_buildable_source=0 (consumer / OVPR path).
85
77
  is_buildable_source=0
86
78
  if node "{{.TASKFILE_DIR}}/engine-invoke.cjs" is-buildable-source "{{.DEFT_ROOT}}"; then
87
79
  is_buildable_source=1
88
80
  fi
89
- if [ -n "$bin" ] && [ -f "$bin" ]; then
90
- if [ "$is_buildable_source" = 1 ] && [ -f "$root_pkg" ]; then
81
+ if [ "$is_buildable_source" = 1 ]; then
82
+ # #5805 Prefer-A Bound: framework-source ordinary invoke is pin-exclusive.
83
+ # No packages/runtime/dist fallthrough; missing pin fails closed.
84
+ # Resolve pin from DEFT_ROOT so subdirectory cwd still finds the checkout pin (#5805).
85
+ pin_bin=$(node "{{.TASKFILE_DIR}}/engine-invoke.cjs" pin-bin "{{.DEFT_ROOT}}" ) || exit $?
86
+ root_pkg="{{.DEFT_ROOT}}/package.json"
87
+ if [ -f "$root_pkg" ]; then
91
88
  node -e "
92
89
  const fs=require('fs');
93
90
  const pkg=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));
@@ -101,80 +98,22 @@ tasks:
101
98
  }
102
99
  " "$root_pkg" || exit $?
103
100
  fi
104
- node "{{.TASKFILE_DIR}}/engine-invoke.cjs" vendored "$bin"
105
- elif [ "$is_buildable_source" = 1 ]; then
106
- first_token=$(node -e "process.stdout.write(JSON.parse(process.env.DEFT_ENGINE_CMD_JSON||'\"\"').split(/\\s+/)[0]||'')")
107
- is_runtime_verb=0
108
- case " ${first_token} " in
109
- " session:start "|" session-start "|\
110
- " session:ready "|" session-ready "|\
111
- " occupancy:steal "|" occupancy-steal "|\
112
- " occupancy:release "|" occupancy-release "|\
113
- " occupancy:heartbeat "|" occupancy-heartbeat "|\
114
- " occupancy:grant "|" occupancy-grant "|\
115
- " session:end "|" session-end "|\
116
- " lifecycle:event "|" lifecycle-event "|\
117
- " verify:session-ritual "|" verify-session-ritual "|\
118
- " verify:tools "|" verify-tools "|\
119
- " triage:summary "|" triage-summary "|\
120
- " triage:welcome "|" triage-welcome "|\
121
- " verify:cache-fresh "|" verify-cache-fresh "|\
122
- " preflight-cache "|\
123
- " ownership:doctor "|" ownership-doctor "|\
124
- " ownership:fix "|" ownership-fix "|\
125
- " verify:ownership "|" verify-ownership ")
126
- is_runtime_verb=1
127
- ;;
128
- esac
129
- if [ "${DEFT_USE_GLOBAL_CLI:-}" = "1" ]; then
130
- is_runtime_verb=1
101
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" vendored "$pin_bin"
102
+ else
103
+ # OVPR consumer / deposit: DEFT_ROOT is often the runtime package itself.
104
+ bin=""
105
+ if [ -f "{{.DEFT_ROOT}}/dist/bin.js" ]; then
106
+ bin="{{.DEFT_ROOT}}/dist/bin.js"
131
107
  fi
132
- global_cli=""
133
- if command -v deft >/dev/null 2>&1; then
134
- global_cli=deft
108
+ if [ -n "$bin" ] && [ -f "$bin" ]; then
109
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" vendored "$bin"
110
+ elif command -v deft >/dev/null 2>&1; then
111
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global deft
135
112
  elif command -v directive >/dev/null 2>&1; then
136
- global_cli=directive
137
- fi
138
- if [ "$is_runtime_verb" = 1 ] && [ -n "$global_cli" ]; then
139
- node -e "
140
- const {execFileSync}=require('child_process');
141
- const root=process.argv[1];
142
- const cli=process.argv[2];
143
- let srcVer='0.0.0-dev';
144
- try{
145
- if(process.env.DEFT_RELEASE_VERSION){
146
- srcVer=process.env.DEFT_RELEASE_VERSION;
147
- }else{
148
- srcVer=execFileSync('git',['describe','--tags','--abbrev=0'],{cwd:root,encoding:'utf8'}).trim().replace(/^v/,'');
149
- }
150
- }catch{}
151
- let globalVer='unknown';
152
- try{
153
- globalVer=execFileSync(cli,['--version'],{encoding:'utf8'}).trim();
154
- }catch{}
155
- const m=globalVer.match(/@([0-9]+\\.[0-9]+\\.[0-9]+)/)||globalVer.match(/([0-9]+\\.[0-9]+\\.[0-9]+)/);
156
- const gv=m?m[1]:globalVer;
157
- if(srcVer!=='0.0.0-dev'&&gv!=='unknown'&&gv!==srcVer){
158
- console.error('deft: using global '+cli+' ('+gv+'); source checkout is v'+srcVer+' — run task build for a local engine match.');
159
- }
160
- " "{{.DEFT_ROOT}}" "$global_cli" >&2 || true
161
- node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global "$global_cli"
113
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global directive
162
114
  else
163
- echo "deft: CLI artifact missing under {{.DEFT_ROOT}} (tried dist/bin.js and packages/runtime/dist/bin.js)" >&2
164
- if [ "$is_runtime_verb" = 1 ]; then
165
- echo " Install global deft: npm i -g @deftai/directive" >&2
166
- echo " Or run \`task build\` first (framework source checkout)." >&2
167
- else
168
- echo " Run \`task build\` first (framework source checkout)." >&2
169
- fi
115
+ echo "deft: neither OVPR bin.js under {{.DEFT_ROOT}} nor a global deft command is available." >&2
116
+ echo " Install with: npm i -g @deftai/directive" >&2
170
117
  exit 2
171
118
  fi
172
- elif command -v deft >/dev/null 2>&1; then
173
- node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global deft
174
- elif command -v directive >/dev/null 2>&1; then
175
- node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global directive
176
- else
177
- echo "deft: neither OVPR/legacy bin.js under {{.DEFT_ROOT}} nor a global deft command is available." >&2
178
- echo " Install with: npm i -g @deftai/directive" >&2
179
- exit 2
180
119
  fi
package/tasks/eval.yml CHANGED
@@ -6,8 +6,6 @@ vars:
6
6
  tasks:
7
7
  health:
8
8
  desc: "Tier 0 static self-consistency aggregation -- versioned framework health score (#1703). -- task eval:health [-- --json] [--no-persist] [--project-root PATH]"
9
- deps:
10
- - task: :engine:_ts-build
11
9
  cmds:
12
10
  - task: :engine:invoke
13
11
  vars:
@@ -15,8 +13,6 @@ tasks:
15
13
 
16
14
  run:
17
15
  desc: "Tier 2 golden corpus champion-challenger eval (#1703). -- task eval:run -- --model MODEL [--seed N] [--directive-version V] [--harness NAME] [--json] [--no-persist] [--project-root PATH]"
18
- deps:
19
- - task: :engine:_ts-build
20
16
  cmds:
21
17
  - task: :engine:invoke
22
18
  vars:
@@ -24,8 +20,6 @@ tasks:
24
20
 
25
21
  report:
26
22
  desc: "Tier 2 version-diff report with significance (#1703). -- task eval:report -- --champion V --challenger V --model MODEL [--json] [--project-root PATH]"
27
- deps:
28
- - task: :engine:_ts-build
29
23
  cmds:
30
24
  - task: :engine:invoke
31
25
  vars:
@@ -33,8 +27,6 @@ tasks:
33
27
 
34
28
  triggers:
35
29
  desc: "Trigger routing coverage for AGENTS.md / Skills Index (#1586). Offline skill-pi-trigger-eval compatible eval over evals/trigger-cases.jsonl vs REFERENCES.md. -- task eval:triggers [-- --json] [--project-root PATH]"
36
- deps:
37
- - task: :engine:_ts-build
38
30
  cmds:
39
31
  - task: :engine:invoke
40
32
  vars:
@@ -6,8 +6,6 @@ vars:
6
6
  tasks:
7
7
  file:
8
8
  desc: "Draft or file a deduped framework-gap issue upstream (#1709 / #3713). -- task feedback:file -- [--summary TEXT | positional] [--context ...] [--blocker] [--confirm] [--dry-run] [--json]"
9
- deps:
10
- - task: :engine:_ts-build
11
9
  cmds:
12
10
  - task: :engine:invoke
13
11
  vars:
package/tasks/install.yml CHANGED
@@ -20,8 +20,6 @@ tasks:
20
20
  uninstall:
21
21
  desc: Remove deft entry from AGENTS.md
22
22
  dir: '{{.USER_WORKING_DIR}}'
23
- deps:
24
- - task: :engine:_ts-build
25
23
  cmds:
26
24
  - task: :engine:invoke
27
25
  vars:
@@ -62,8 +60,6 @@ tasks:
62
60
  # `task upgrade` failed on the primary npm consumer workflow. Framework-root is
63
61
  # intentionally omitted: install-upgrade auto-detects the consumer's
64
62
  # `.deft/core/VERSION` (#2053) when run from the global engine.
65
- deps:
66
- - task: :engine:_ts-build
67
63
  cmds:
68
64
  - task: :engine:invoke
69
65
  vars:
package/tasks/issue.yml CHANGED
@@ -22,8 +22,6 @@ tasks:
22
22
  ingest:
23
23
  desc: "Ingest GitHub issues as scope vBRIEFs (single <N> or --all [--label L] [--status S] [--dry-run])"
24
24
  dir: '{{.USER_WORKING_DIR}}'
25
- deps:
26
- - task: :engine:_ts-build
27
25
  cmds:
28
26
  - task: :engine:invoke
29
27
  vars:
@@ -39,7 +37,6 @@ tasks:
39
37
  emit:
40
38
  desc: "File GitHub issue(s) from scope vBRIEF(s) (<path> | --umbrella <glob> | --per-vbrief <glob> [--dry-run])"
41
39
  dir: '{{.USER_WORKING_DIR}}'
42
- deps: [":engine:_ts-build"]
43
40
  env:
44
41
  PYTHONUTF8: "1"
45
42
  cmds:
@@ -27,7 +27,6 @@ tasks:
27
27
  stats:
28
28
  desc: "Local xBRIEF lifecycle folder counts for process rollups (#2995). -- task lifecycle:stats -- --since=7d [--json] [--project-root <path>]"
29
29
  dir: '{{.USER_WORKING_DIR}}'
30
- deps: [':engine:_ts-build']
31
30
  env:
32
31
  PYTHONUTF8: "1"
33
32
  cmds:
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Thin tip→pin materialize (#5805 Prefer-A Bound P3/P4).
6
+ * Stages + packs via existing scripts, then replaces this checkout's
7
+ * node_modules/@deftai/directive-runtime only. No package.json/lockfile
8
+ * location-spec mutation. Serializes via a lockfile under .deft-scratch.
9
+ */
10
+
11
+ const { spawnSync } = require("node:child_process");
12
+ const fs = require("node:fs");
13
+ const os = require("node:os");
14
+ const path = require("node:path");
15
+
16
+ const PIN_PKG = path.join("node_modules", "@deftai", "directive-runtime");
17
+ const LOCK_REL = path.join(".deft-scratch", "materialize-runtime.lock");
18
+ const OVERLAY_REL = path.join(".deft-scratch", "materialize-runtime-overlay.json");
19
+
20
+ function fail(msg, code = 2) {
21
+ console.error(`materialize:runtime: ${msg}`);
22
+ process.exit(code);
23
+ }
24
+
25
+ function runNode(script, args, opts = {}) {
26
+ const r = spawnSync(process.execPath, [script, ...args], {
27
+ cwd: opts.cwd,
28
+ encoding: "utf8",
29
+ stdio: ["ignore", "pipe", "pipe"],
30
+ env: process.env,
31
+ });
32
+ if (r.status !== 0) {
33
+ const err = new Error(
34
+ `${path.basename(script)} failed (exit ${r.status}): ${r.stderr || r.stdout || ""}`.trim(),
35
+ );
36
+ err.code = r.status || 2;
37
+ throw err;
38
+ }
39
+ return (r.stdout || "").trim();
40
+ }
41
+
42
+ function acquireLock(projectRoot) {
43
+ const lockPath = path.join(projectRoot, LOCK_REL);
44
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
45
+ try {
46
+ const fd = fs.openSync(lockPath, "wx");
47
+ fs.writeFileSync(fd, `${process.pid}\n${new Date().toISOString()}\n`);
48
+ fs.closeSync(fd);
49
+ return lockPath;
50
+ } catch (err) {
51
+ if (err && err.code === "EEXIST") {
52
+ const locked = new Error(
53
+ `another materialize is in progress (lock ${lockPath}); reject second writer`,
54
+ );
55
+ locked.code = "EEXIST";
56
+ throw locked;
57
+ }
58
+ throw err;
59
+ }
60
+ }
61
+
62
+ function releaseLock(lockPath) {
63
+ try {
64
+ fs.unlinkSync(lockPath);
65
+ } catch {
66
+ /* ignore */
67
+ }
68
+ }
69
+
70
+ /** True when dir is safe to replace: missing, or a real directory whose realpath is under checkoutRoot. */
71
+ function isCheckoutOwnedDir(dir, checkoutRoot) {
72
+ try {
73
+ const st = fs.lstatSync(dir);
74
+ if (st.isSymbolicLink()) {
75
+ // pnpm pin is often a symlink into the checkout store — allow when realpath stays in-tree.
76
+ if (!checkoutRoot) return false;
77
+ let real;
78
+ try {
79
+ real = fs.realpathSync(dir);
80
+ } catch {
81
+ return false;
82
+ }
83
+ const rootReal = fs.realpathSync(checkoutRoot);
84
+ const rel = path.relative(rootReal, real);
85
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
86
+ }
87
+ if (!st.isDirectory()) return false;
88
+ if (!checkoutRoot) return true;
89
+ let real;
90
+ try {
91
+ real = fs.realpathSync(dir);
92
+ } catch {
93
+ return false;
94
+ }
95
+ const rootReal = fs.realpathSync(checkoutRoot);
96
+ const rel = path.relative(rootReal, real);
97
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
98
+ } catch {
99
+ return true; // missing is fine; we will create
100
+ }
101
+ }
102
+
103
+ function rmrf(target) {
104
+ fs.rmSync(target, { recursive: true, force: true });
105
+ }
106
+
107
+ function movePath(src, dest) {
108
+ try {
109
+ fs.renameSync(src, dest);
110
+ } catch (err) {
111
+ if (err && err.code === "EXDEV") {
112
+ fs.cpSync(src, dest, { recursive: true, force: true });
113
+ rmrf(src);
114
+ return;
115
+ }
116
+ throw err;
117
+ }
118
+ }
119
+
120
+ function materialize(projectRoot, opts = {}) {
121
+ const root = path.resolve(projectRoot);
122
+ const stageScript = path.join(root, "packages", "runtime", "scripts", "stage-bundle.mjs");
123
+ const packScript = path.join(root, "packages", "runtime", "scripts", "pack-staged-runtime.mjs");
124
+ if (!fs.existsSync(stageScript) || !fs.existsSync(packScript)) {
125
+ throw new Error("stage-bundle/pack-staged-runtime scripts missing; run from framework-source checkout");
126
+ }
127
+
128
+ const pinDir = path.join(root, PIN_PKG);
129
+ if (fs.existsSync(pinDir) && !isCheckoutOwnedDir(pinDir, root)) {
130
+ throw new Error(`refusing to mutate linked/shared pin at ${pinDir}`);
131
+ }
132
+
133
+ const lockPath = acquireLock(root);
134
+ const priorPinUsable = fs.existsSync(path.join(pinDir, "dist", "bin.js"));
135
+ let priorBackup = null;
136
+ let stageDir;
137
+ let packDir;
138
+ let extractDir;
139
+ try {
140
+ stageDir = fs.mkdtempSync(path.join(os.tmpdir(), "deft-materialize-stage-"));
141
+ packDir = fs.mkdtempSync(path.join(os.tmpdir(), "deft-materialize-pack-"));
142
+ runNode(stageScript, [stageDir], { cwd: root });
143
+ const tgz = runNode(packScript, [stageDir, packDir], { cwd: root });
144
+ if (!tgz || !fs.existsSync(tgz)) fail(`pack did not emit tgz (got ${JSON.stringify(tgz)})`);
145
+
146
+ extractDir = fs.mkdtempSync(path.join(os.tmpdir(), "deft-materialize-extract-"));
147
+ const tar = spawnSync(
148
+ process.platform === "win32" ? "tar.exe" : "tar",
149
+ ["-xzf", tgz, "-C", extractDir],
150
+ { encoding: "utf8" },
151
+ );
152
+ if (tar.status !== 0) {
153
+ throw Object.assign(new Error(`tar extract failed: ${tar.stderr || tar.stdout || ""}`.trim()), { code: tar.status || 2 });
154
+ }
155
+ const entries = fs.readdirSync(extractDir);
156
+ if (entries.length !== 1) throw new Error(`expected one package folder in tgz, got ${entries.join(",")}`);
157
+ const extracted = path.join(extractDir, entries[0]);
158
+
159
+ const parent = path.dirname(pinDir);
160
+ fs.mkdirSync(parent, { recursive: true });
161
+ const stagingPin = path.join(parent, `.directive-runtime-next-${process.pid}`);
162
+ rmrf(stagingPin);
163
+ movePath(extracted, stagingPin);
164
+ if (!fs.existsSync(path.join(stagingPin, "dist", "bin.js"))) {
165
+ throw new Error("staged pin bin missing before replacement");
166
+ }
167
+ // Keep recoverable backup until replacement succeeds.
168
+ if (fs.existsSync(pinDir)) {
169
+ priorBackup = path.join(parent, `.directive-runtime-prev-${process.pid}`);
170
+ rmrf(priorBackup);
171
+ movePath(pinDir, priorBackup);
172
+ }
173
+ try {
174
+ movePath(stagingPin, pinDir);
175
+ } catch (err) {
176
+ if (priorBackup && fs.existsSync(priorBackup)) {
177
+ try {
178
+ movePath(priorBackup, pinDir);
179
+ priorBackup = null;
180
+ } catch {
181
+ /* best-effort restore */
182
+ }
183
+ }
184
+ throw err;
185
+ }
186
+ if (priorBackup) {
187
+ rmrf(priorBackup);
188
+ priorBackup = null;
189
+ }
190
+
191
+ let head = "unknown";
192
+ try {
193
+ const g = spawnSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" });
194
+ if (g.status === 0) head = g.stdout.trim();
195
+ } catch {
196
+ /* ignore */
197
+ }
198
+ const overlay = {
199
+ schema: "deft.materialize.runtime.overlay.v1",
200
+ writtenAt: new Date().toISOString(),
201
+ projectRoot: root,
202
+ pinPath: pinDir,
203
+ sourceRevision: head,
204
+ tarball: path.basename(tgz),
205
+ note: "Local tip overlay into checkout pin; package.json/lockfile pins unchanged. Normal PM install restores registry pin.",
206
+ };
207
+ fs.writeFileSync(path.join(root, OVERLAY_REL), `${JSON.stringify(overlay, null, 2)}\n`);
208
+ if (!fs.existsSync(path.join(pinDir, "dist", "bin.js"))) {
209
+ throw new Error("materialize completed but pin bin missing");
210
+ }
211
+ process.stdout.write(`materialize:runtime: installed tip into ${pinDir}\n`);
212
+ process.stdout.write(`materialize:runtime: overlay ${path.join(root, OVERLAY_REL)}\n`);
213
+ return overlay;
214
+ } catch (err) {
215
+ if (priorPinUsable) {
216
+ console.error("materialize:runtime: failed after stage/pack; prior pin left in place");
217
+ }
218
+ throw err;
219
+ } finally {
220
+ releaseLock(lockPath);
221
+ if (stageDir) rmrf(stageDir);
222
+ if (packDir) rmrf(packDir);
223
+ if (extractDir) rmrf(extractDir);
224
+ if (priorBackup) rmrf(priorBackup);
225
+ }
226
+ }
227
+
228
+ if (require.main === module) {
229
+ const root = process.argv[2] || process.cwd();
230
+ try {
231
+ materialize(root);
232
+ } catch (err) {
233
+ fail(err && err.message ? err.message : String(err), err && err.code ? Number(err.code) || 2 : 2);
234
+ }
235
+ }
236
+
237
+ module.exports = {
238
+ materialize,
239
+ acquireLock,
240
+ releaseLock,
241
+ isCheckoutOwnedDir,
242
+ movePath,
243
+ PIN_PKG,
244
+ LOCK_REL,
245
+ OVERLAY_REL,
246
+ };
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const assert = require("node:assert/strict");
5
+ const {
6
+ mkdtempSync,
7
+ mkdirSync,
8
+ writeFileSync,
9
+ symlinkSync,
10
+ rmSync,
11
+ existsSync,
12
+ readFileSync,
13
+ } = require("node:fs");
14
+ const { tmpdir } = require("node:os");
15
+ const { join } = require("node:path");
16
+ const { describe, it, after } = require("node:test");
17
+ const {
18
+ acquireLock,
19
+ releaseLock,
20
+ isCheckoutOwnedDir,
21
+ movePath,
22
+ materialize,
23
+ LOCK_REL,
24
+ } = require("./materialize.cjs");
25
+
26
+ const roots = [];
27
+ function tempRoot() {
28
+ const root = mkdtempSync(join(tmpdir(), "mat-test-"));
29
+ roots.push(root);
30
+ return root;
31
+ }
32
+ after(() => {
33
+ for (const root of roots) {
34
+ try {
35
+ rmSync(root, { recursive: true, force: true });
36
+ } catch {
37
+ /* ignore */
38
+ }
39
+ }
40
+ });
41
+
42
+ describe("materialize isolation (#5805 P4)", () => {
43
+ it("acquireLock rejects a second writer", () => {
44
+ const root = tempRoot();
45
+ const lock = acquireLock(root);
46
+ assert.ok(existsSync(join(root, LOCK_REL)));
47
+ assert.throws(() => acquireLock(root), /another materialize|second writer|EEXIST|in progress/i);
48
+ releaseLock(lock);
49
+ });
50
+
51
+ it("isCheckoutOwnedDir is false for a symlink outside the checkout", () => {
52
+ const root = tempRoot();
53
+ const outside = tempRoot();
54
+ const real = join(outside, "real-pkg");
55
+ const link = join(root, "linked-pkg");
56
+ mkdirSync(real, { recursive: true });
57
+ try {
58
+ symlinkSync(real, link, "junction");
59
+ } catch {
60
+ return;
61
+ }
62
+ assert.equal(isCheckoutOwnedDir(link, root), false);
63
+ assert.equal(isCheckoutOwnedDir(real, outside), true);
64
+ });
65
+
66
+ it("isCheckoutOwnedDir allows in-tree symlink (pnpm-style)", () => {
67
+ const root = tempRoot();
68
+ const store = join(root, ".pnpm", "pkg");
69
+ const link = join(root, "node_modules", "@deftai", "directive-runtime");
70
+ mkdirSync(store, { recursive: true });
71
+ mkdirSync(join(root, "node_modules", "@deftai"), { recursive: true });
72
+ try {
73
+ symlinkSync(store, link, "junction");
74
+ } catch {
75
+ return;
76
+ }
77
+ assert.equal(isCheckoutOwnedDir(link, root), true);
78
+ });
79
+
80
+ it("missing destination is treated as checkout-owned (creatable)", () => {
81
+ const root = tempRoot();
82
+ assert.equal(isCheckoutOwnedDir(join(root, "missing"), root), true);
83
+ });
84
+
85
+ it("movePath falls back when rename would need copy semantics", () => {
86
+ const root = tempRoot();
87
+ const src = join(root, "src-dir");
88
+ const dest = join(root, "dest-dir");
89
+ mkdirSync(src, { recursive: true });
90
+ writeFileSync(join(src, "f.txt"), "ok");
91
+ movePath(src, dest);
92
+ assert.equal(readFileSync(join(dest, "f.txt"), "utf8"), "ok");
93
+ assert.equal(existsSync(src), false);
94
+ });
95
+
96
+ it("failed materialize releases the lock so retries can proceed", () => {
97
+ const root = tempRoot();
98
+ assert.throws(() => materialize(root), /stage-bundle|pack-staged|missing/i);
99
+ assert.equal(existsSync(join(root, LOCK_REL)), false);
100
+ const lock = acquireLock(root);
101
+ releaseLock(lock);
102
+ });
103
+ });