@deftai/directive-content 0.76.0 → 0.78.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.
@@ -25,6 +25,10 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
25
25
  - User says "build this", "implement the spec", or "start building"
26
26
  - Resuming a partially-built project that has story xBRIEFs in `xbrief/active/`
27
27
 
28
+ ## Ordered-plan / cohort exhaustion (#2402)
29
+
30
+ ! When processing an approved multi-story cohort or an active ordered-plan sequence, stop after the final approved entry. Do not promote or dispatch adjacent stories from queue intuition. Continuation language advances only within the approved order; skill-chaining is non-authorizing.
31
+
28
32
  ## Step 0 -- Implementation Preflight (#810)
29
33
 
30
34
  - ! Before starting any new implementation story or switching from one story to another, MUST run `git status --short --branch`.
@@ -35,6 +35,20 @@ or invoke `task verify:branch`. Pre-PR is the last gate before push, so a stale
35
35
 
36
36
  ! Every numbered-menu prompt rendered in this skill (per-finding decision menus in Phase 4 Diff, the Phase 5 Loop restart-vs-exit gate) MUST follow [`../../contracts/deterministic-questions.md`](../../contracts/deterministic-questions.md): the final two numbered options MUST be `Discuss` and `Back`, in that order. The Discuss-pause semantic is documented verbatim there -- implicit resumption is forbidden.
37
37
 
38
+
39
+ ## Ordered-plan target gate (#2402)
40
+
41
+ ! Before opening or pushing a PR, when an ordered-plan sequence may be active, verify the PR target matches the current authorized entry:
42
+
43
+ ```
44
+ task verify:plan-sequence -- --target-kind pr --target <issue-or-entry-id>
45
+ ```
46
+
47
+ - ! Exit non-zero → fail closed; do not open an unauthorized PR.
48
+ - ! Exit 0 with "skipped (no active ordered-plan sequence)" → proceed under normal pre-PR rules.
49
+ - ! After the PR's review cycle completes successfully, run `task plan-sequence:advance` so "next" resolves to the following entry (or exhausted).
50
+ - ⊗ Treat skill-chaining or "what's next?" as permission to open a PR outside the current sequence entry.
51
+
38
52
  ## When to Use
39
53
 
40
54
  - ! Before pushing a branch for PR creation
@@ -1,9 +1,11 @@
1
1
  ---
2
2
  name: deft-directive-review-cycle
3
3
  description: >-
4
- Greptile bot reviewer response workflow. Use when running a review cycle on
5
- a PR -- to audit process prerequisites, fetch bot findings, fix all issues
6
- in a single batch commit, and exit cleanly when no P0 or P1 issues remain.
4
+ Greptile / bot reviewer response and PR-shepherding workflow. Use when
5
+ running a review cycle or babysitting a PR to merge-ready -- audit process
6
+ prerequisites, fetch bot findings, fix all issues in a single batch commit,
7
+ and exit cleanly when no P0 or P1 issues remain. Supersedes Cursor global
8
+ babysit on Deft-managed repos (#2261).
7
9
  ---
8
10
  <!-- AUTO-GENERATED by task packs:render -- DO NOT EDIT MANUALLY -->
9
11
  <!-- Purpose: rendered skill -->
@@ -21,11 +23,32 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
21
23
 
22
24
  ! This skill requires **GitHub** as the SCM platform and the **GitHub CLI (`gh`)** to be installed and authenticated. PR comment fetching, check-run queries, and review submission all depend on `gh`.
23
25
 
26
+
27
+ ## Ordered-plan exit / advance (#2402)
28
+
29
+ ! When the review-cycle exit condition is met (no P0/P1 remaining / CLEAN verdict):
30
+
31
+ 1. Return control to the caller — do **not** discover adjacent backlog work.
32
+ 2. If an ordered-plan sequence is active and this PR matched the current entry, run `task plan-sequence:advance`.
33
+ 3. If another entry remains, the caller may continue **only** to that entry (one unit).
34
+ 4. If the sequence is exhausted, stop and ask. Do not open another PR from queue intuition or chaining text.
35
+
36
+ ⊗ Exit the review cycle into `task triage:queue` or an unrelated PR without fresh operator approval.
37
+
24
38
  ## When to Use
25
39
 
26
40
  - User says "review cycle", "check reviews", or "run review cycle" on a PR
41
+ - Operator asks to **babysit**, **shepherd**, or **watch** a PR -- including the Cursor product action **babysit-pull-request-in-cloud** (#2261 / #1862 intent-routing class)
27
42
  - A bot reviewer (Greptile) has posted findings on an open PR
28
- - Dispatching a cloud agent to monitor and resolve PR review findings
43
+ - Dispatching a cloud or background agent to monitor and resolve PR review findings until merge-ready
44
+
45
+ ## Cursor global babysit supersession (#2261)
46
+
47
+ On repos with `.deft/core/` installed, **PR shepherding intent** routes here -- not the Cursor-global `babysit` skill (`~/.cursor/skills-cursor/babysit/SKILL.md`). The product action **babysit-pull-request-in-cloud** shares the babysit name but MUST chain into this review-cycle surface on Deft-managed projects.
48
+
49
+ - ! Orchestrators dispatching cloud/background workers for PR shepherding MUST include `templates/agent-prompt-preamble.md` (or reference it) and mandate reading this skill as the first workflow step.
50
+ - ! Cloud babysit workers use `worker_role: review-monitor` (or `drive-to: merge-ready` implementation workers that own the review cycle) -- not a generic babysit loop without Phase 1 audit or Step 6 exit predicate (#1259).
51
+ - ⊗ Route **babysit-pull-request-in-cloud** to Cursor global babysit when `.deft/core/` is present -- that bypasses xBRIEF preflight, `task check`, Greptile fail-closed exit, and CHANGELOG discipline (#2261 recurrence).
29
52
 
30
53
  ## Branch-Protection Policy Guard
31
54
 
@@ -377,6 +400,8 @@ task lifecycle:event -- emit plan:approved \
377
400
 
378
401
  ## Anti-Patterns
379
402
 
403
+ - ⊗ Route PR shepherding to Cursor global `babysit` on Deft-managed repos when `.deft/core/` is installed -- use this review-cycle skill instead (#2261)
404
+ - ⊗ Treat a passing SLizard/Greptile check run, a non-blocking review comment, or an ad hoc fix commit as the review-cycle exit predicate -- Step 6 fail-closed all-of (#1259) and multi-reviewer registry triage (#769) still apply
380
405
  - ⊗ Push individual fix commits per finding
381
406
  - ⊗ Start fixing before analyzing ALL findings
382
407
  - ⊗ Rely on the bot to catch syntax errors in structured data files
@@ -482,7 +482,11 @@ omit = [
482
482
 
483
483
  - ~ After Phase 2 writes `PROJECT-DEFINITION.xbrief.json`, recommend `task triage:welcome` to the user as the single chained command for picking up the v0.27 triage surface. The N3 ritual (#1143) is the consolidating onboarding step for the #1119 governance swarm verbs (`task triage:bootstrap` / `task triage:scope` / `plan.policy.wipCap` writes / `task scope:demote --batch` relief / `task triage:summary`); without it consumers must learn each verb individually from the v0.27 release notes.
484
484
  - ~ `task triage:welcome` is idempotent and detection-bound -- each phase emits an informational stderr line and skips when its precondition is already satisfied, so a re-run after a partial completion resumes cleanly. The destructive phases (subscription / `wipCap` writes, optional WIP-relief invocation) are gated by numbered-menu prompts per [`../../contracts/deterministic-questions.md`](../../contracts/deterministic-questions.md). See [`../../UPGRADING.md`](../../UPGRADING.md) `## From v0.26.x -> v0.27` for the full walkthrough.
485
- - ? The recommendation is informational, not a hard gate -- consumers who plan to wire triage manually MAY skip the ritual and call the individual verbs in any order; the framework defaults stay fail-open per the umbrella `#1119 §12 framework-vs-consumer-config boundary`.
485
+ ### Consumer AGENTS always-pin recommendations (#2508)
486
+
487
+ ! After Phase 2 completes (or when closing a greenfield bootstrap), tell the consumer that process-critical directive skills should be named in their project's AGENTS.md **unmanaged header** when false-negative risk matters — full tier definitions: [`../../docs/skill-pin-policy.md`](../../docs/skill-pin-policy.md).
488
+ ~ Recommend the default consumer pin set for lifecycle work: `deft-directive-build` (implementation), `deft-directive-pre-pr` (before PR), `deft-directive-review-cycle` (bot review loop), and `deft-directive-swarm` when they plan parallel agents. Each is referenced by skill id + `.deft/core/.agents/skills/<id>/SKILL.md` path — not by pasting skill bodies.
489
+ ⊗ Recommend pinning entire `languages/`, `deployments/`, or other framework doc packs into AGENTS.md (#2508 anti-pattern).
486
490
 
487
491
  ---
488
492
 
@@ -57,6 +57,8 @@ This path became first-class in #1342 (platform adapter slices 1-3) and is fully
57
57
 
58
58
  ~ **Windows + Grok Build (#1353):** When issuing shell commands via `run_terminal_command` on this platform, avoid `|`, `>`, or `2>&1` in the command string — use Python `pathlib`/`subprocess` or plain `task` targets instead to avoid wrapper leakage. See `templates/agent-prompt-preamble.md` §3.5 for the full escape hatch list.
59
59
 
60
+ ! **Windows + Cursor Task-tool console storm (#2563):** Local Cursor Task subagents on Windows have frozen the host by flooding visible `cmd.exe` / `conhost` windows (observed with both parallel and serial Task agents; in-parent work stayed stable). Prefer one of: (1) in-parent / serial monitor-owned implementation, (2) cloud workers, or (3) after the #2563 mitigations (`windowsHide` + warm-dist skip in `engine:_ts-build`) are on the branch under test, keep local Task concurrency at **1** and prefer `pnpm exec` / warm `dist/bin.js` over repeated cold `task` rebuilds. See `templates/agent-prompt-preamble.md` §3.8. ⊗ Launch a wide parallel local Cursor Task cohort on Windows without that mitigation or an explicit operator acceptance of host-freeze risk.
61
+
60
62
  ## Prerequisites
61
63
 
62
64
  - ! `xbrief/active/` contains one or more story-level xBRIEFs with status `running`
@@ -76,6 +78,13 @@ This path became first-class in #1342 (platform adapter slices 1-3) and is fully
76
78
  ⊗ Re-prompt the operator for per-phase batching approval, or run the interactive promote-fill loop (Step 0a -- 0d), when a pre-approved cohort is supplied via `task swarm:launch` -- the headless path's single #1378 consent already authorizes the batch, and re-prompting mid-cohort violates the all-or-nothing dispatch-envelope rule (#954).
77
79
  ? The interactive queue-driven path (Step 0 below) remains the DEFAULT when no pre-approved cohort is supplied; the headless fast-path is the opt-in low-ceremony route for a cohort the operator has already curated and approved upstream.
78
80
 
81
+
82
+ ### Ordered-plan / cohort exhaustion (#2402)
83
+
84
+ ! When the approved cohort (or an active `plan-sequence` of kind `swarm`/`cohort`/`delivery`) is exhausted, stop. Do not promote, queue, open, or dispatch adjacent work after the final approved entry unless the operator explicitly authorizes a new cohort or queue-driven selection.
85
+
86
+ ! Continuation language ("next", "proceed") advances only within the approved cohort order — not into triage-queue remainder.
87
+
79
88
  ### Step 0: Queue-driven cohort selection (#1142 / N2)
80
89
 
81
90
  ! Phase 0 is queue-driven: consult the triage cache (D2 / #1122 + D11 / #1128) for the ranked promotion candidates, then fill the WIP cap. Do NOT pick the cohort by hand from `xbrief/pending/` or `xbrief/active/` -- the queue is the canonical record of "what's next?" per AGENTS.md `## Cache-as-authoritative work selection (#1149)`. The four sub-phases below run in canonical order; existing Step 0.5 (lifecycle bridge) and Steps 1-5 (readiness / blockers / allocation / present / approval) proceed unchanged after Phase 0d.
@@ -59,9 +59,14 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
59
59
  5. ⊗ Re-classify items already terminally decided (accept / reject / mark-duplicate) without explicit operator approval -- the audit log is append-only and supersession runs through Layer 5 (`task triage:reset <N>`), not through silent re-walks.
60
60
  6. ⊗ Block issue creation solely because no label was selected, or invent ad hoc labels outside the repository's existing label set.
61
61
 
62
+
63
+ ## Ordered-plan precedence (#2402)
64
+
65
+ ! Before queue selection on bare "what's next?", run `task plan-sequence:current`. Active sequence → that entry only; exhausted → fail closed and ask. Explicit "what's the queue?" / "build a cohort" still use Phase 2. Chaining is non-authorizing. ⊗ Reuse triage `continuationNumbers`/`continuationOrder` for ordered-plan state.
66
+
62
67
  ## Phase 2 -- Present
63
68
 
64
- ! Render the ranked queue before suggesting any specific issue. The cache-as-authoritative rule in AGENTS.md (`## Cache-as-authoritative work selection (#1149)`) is binding: the agent ! MUST consult `task triage:queue` and surface the result before proposing work from memory.
69
+ ! When no ordered-plan is active, render `task triage:queue` before suggesting work (#1149). Active sequence yields to the ordered-plan entry (#2402).
65
70
 
66
71
  1. ! Run `task triage:queue --limit=N` (D11 / #1128) -- default `N=10` per the umbrella Current Shape v3 WIP cap. Output is grouped `[RESUME]` -> `[URGENT]` -> untriaged -> other; within-group ordering follows the consumer-supplied `plan.policy.triageRankingLabels[]` (framework default empty per §12 boundary), tiebroken by `updated_at` descending.
67
72
  2. ! For per-item detail, run `task triage:show <N>` -- prints the cached upstream payload, the latest triage decision, the audit timeline, and the active-xBRIEF reference flag. Exit 0 on hit, 1 on cache miss (re-sync per Phase 0).
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Spawn the deft CLI from DEFT_ENGINE_CMD without shell-interpolating operator
6
+ * text (#2547). go-task forwards user args into ENGINE_CMD; apostrophes in
7
+ * --summary and similar free-text flags must not break mvdan/sh parsing.
8
+ *
9
+ * Lives under tasks/ (not repo-root scripts/) so @deftai/directive-content
10
+ * prepack ships it beside tasks/engine.yml (#2022 Phase 3).
11
+ */
12
+
13
+ const { spawnSync } = require("node:child_process");
14
+
15
+ /** Minimal POSIX-ish shell word splitter (double/single quotes, escapes). */
16
+ function shellSplit(input) {
17
+ const out = [];
18
+ let cur = "";
19
+ let quote = null;
20
+ for (let i = 0; i < input.length; i++) {
21
+ const c = input[i];
22
+ if (quote) {
23
+ if (c === quote) {
24
+ quote = null;
25
+ continue;
26
+ }
27
+ if (c === "\\" && quote === '"' && i + 1 < input.length) {
28
+ cur += input[++i];
29
+ continue;
30
+ }
31
+ cur += c;
32
+ continue;
33
+ }
34
+ if (c === '"' || c === "'") {
35
+ quote = c;
36
+ continue;
37
+ }
38
+ if (/\s/.test(c)) {
39
+ if (cur) {
40
+ out.push(cur);
41
+ cur = "";
42
+ }
43
+ continue;
44
+ }
45
+ cur += c;
46
+ }
47
+ if (cur) {
48
+ out.push(cur);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ function main() {
54
+ const mode = process.argv[2];
55
+ const target = process.argv[3];
56
+ let cmdLine = "";
57
+ if (process.env.DEFT_ENGINE_CMD_JSON) {
58
+ try {
59
+ cmdLine = JSON.parse(process.env.DEFT_ENGINE_CMD_JSON);
60
+ } catch {
61
+ console.error("deft: DEFT_ENGINE_CMD_JSON is not valid JSON");
62
+ process.exit(2);
63
+ }
64
+ } else {
65
+ cmdLine = String(process.env.DEFT_ENGINE_CMD || "");
66
+ }
67
+ cmdLine = cmdLine.trim();
68
+ const argv = shellSplit(cmdLine);
69
+ if (argv.length === 0) {
70
+ console.error("deft: DEFT_ENGINE_CMD is empty");
71
+ process.exit(2);
72
+ }
73
+ if (!mode || !target) {
74
+ console.error("deft: engine-invoke usage: engine-invoke.cjs <vendored|global> <bin-or-cli>");
75
+ process.exit(2);
76
+ }
77
+
78
+ let execPath;
79
+ let execArgv;
80
+ if (mode === "vendored") {
81
+ execPath = process.execPath;
82
+ execArgv = [target, ...argv];
83
+ } else if (mode === "global") {
84
+ execPath = target;
85
+ execArgv = argv;
86
+ } else {
87
+ console.error(`deft: engine-invoke unknown mode ${JSON.stringify(mode)}`);
88
+ process.exit(2);
89
+ }
90
+
91
+ // Command transport is one-hop: a spawned CLI may invoke Task again with a
92
+ // different ENGINE_CMD, which must not be shadowed by this inherited value.
93
+ const childEnv = { ...process.env };
94
+ delete childEnv.DEFT_ENGINE_CMD_JSON;
95
+ delete childEnv.DEFT_ENGINE_CMD;
96
+
97
+ // stdio inherit (not pipe): piped stdout/stderr deadlocks when the child emits
98
+ // more than the OS pipe buffer before exit — observed as greenfield smoke
99
+ // hanging then CI SIGTERM exit 143 with no output (#2554 / #2547).
100
+ const result = spawnSync(execPath, execArgv, {
101
+ stdio: "inherit",
102
+ env: childEnv,
103
+ // Global deft/directive on Windows are .cmd shims; shell:false cannot spawn them (#2415).
104
+ shell: mode === "global" && process.platform === "win32",
105
+ // CREATE_NO_WINDOW: hide console windows from Cursor Task / nested shells (#2563).
106
+ windowsHide: true,
107
+ });
108
+ const code = result.status;
109
+ process.exit(code === null ? 1 : code);
110
+ }
111
+
112
+ if (require.main === module) {
113
+ main();
114
+ }
115
+
116
+ module.exports = { shellSplit };
package/tasks/engine.yml CHANGED
@@ -35,13 +35,16 @@ tasks:
35
35
  // Windows native Task often has no `sh` on PATH, so Corepack.cmd was
36
36
  // invisible after #2411. Prefer a direct spawn (POSIX / real binaries);
37
37
  // fall back to shell:true so PATHEXT resolves .cmd/.exe on win32.
38
+ // windowsHide (#2563): CREATE_NO_WINDOW so Cursor Task shells do not
39
+ // flood visible cmd.exe/conhost windows on every probe/build.
40
+ const spawnOpts=(extra)=>({stdio:'ignore',windowsHide:true,...extra});
38
41
  const hasCmd=(name)=>{
39
42
  try{
40
- execFileSync(name,['--version'],{stdio:'ignore'});
43
+ execFileSync(name,['--version'],spawnOpts());
41
44
  return true;
42
45
  }catch{
43
46
  try{
44
- execFileSync(name,['--version'],{stdio:'ignore',shell:true});
47
+ execFileSync(name,['--version'],spawnOpts({shell:true}));
45
48
  return true;
46
49
  }catch{
47
50
  return false;
@@ -49,7 +52,7 @@ tasks:
49
52
  }
50
53
  };
51
54
  const run=(cmd,args)=>{
52
- execFileSync(cmd,args,{cwd:root,stdio:'inherit',shell:true});
55
+ execFileSync(cmd,args,{cwd:root,stdio:'inherit',shell:true,windowsHide:true});
53
56
  };
54
57
  const trySteps=(steps)=>{
55
58
  for(const [cmd,args] of steps){
@@ -98,6 +101,10 @@ tasks:
98
101
  # ships no packages/ tree (guard false -> no-op, #2126). Git-vendored-payload
99
102
  # repos may carry a stray packages/ under .deft/core without a root `build`
100
103
  # script — source-only presence must NOT run `pnpm run build` there.
104
+ #
105
+ # Warm-dist skip (#2563): when packages/cli/dist/bin.js is newer than
106
+ # packages/{cli,core,types} sources, skip rebuild. Override with
107
+ # DEFT_FORCE_TS_BUILD=1; force-skip with DEFT_SKIP_TS_BUILD=1.
101
108
  dir: '{{.USER_WORKING_DIR}}'
102
109
  cmds:
103
110
  - |
@@ -105,6 +112,9 @@ tasks:
105
112
  if [ -f "{{.DEFT_ROOT}}/packages/cli/package.json" ] \
106
113
  && [ -f "{{.DEFT_ROOT}}/package.json" ] \
107
114
  && node -e "const fs=require('fs');const j=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.exit(j.scripts&&j.scripts.build?0:1)" "{{.DEFT_ROOT}}/package.json"; then
115
+ if node "{{.TASKFILE_DIR}}/ts-build-fresh.cjs" "{{.DEFT_ROOT}}"; then
116
+ exit 0
117
+ fi
108
118
  node -e "
109
119
  const {execFileSync}=require('child_process');
110
120
  const fs=require('fs');
@@ -116,13 +126,16 @@ tasks:
116
126
  // Windows native Task often has no `sh` on PATH, so Corepack.cmd was
117
127
  // invisible after #2411. Prefer a direct spawn (POSIX / real binaries);
118
128
  // fall back to shell:true so PATHEXT resolves .cmd/.exe on win32.
129
+ // windowsHide (#2563): CREATE_NO_WINDOW so Cursor Task shells do not
130
+ // flood visible cmd.exe/conhost windows on every probe/build.
131
+ const spawnOpts=(extra)=>({stdio:'ignore',windowsHide:true,...extra});
119
132
  const hasCmd=(name)=>{
120
133
  try{
121
- execFileSync(name,['--version'],{stdio:'ignore'});
134
+ execFileSync(name,['--version'],spawnOpts());
122
135
  return true;
123
136
  }catch{
124
137
  try{
125
- execFileSync(name,['--version'],{stdio:'ignore',shell:true});
138
+ execFileSync(name,['--version'],spawnOpts({shell:true}));
126
139
  return true;
127
140
  }catch{
128
141
  return false;
@@ -130,12 +143,20 @@ tasks:
130
143
  }
131
144
  };
132
145
  const run=(cmd,args)=>{
133
- execFileSync(cmd,args,{cwd:root,stdio:'inherit',shell:true});
146
+ execFileSync(cmd,args,{cwd:root,stdio:'inherit',shell:true,windowsHide:true});
147
+ };
148
+ const markWarm=()=>{
149
+ try{
150
+ const dist=root+'/packages/cli/dist';
151
+ fs.mkdirSync(dist,{recursive:true});
152
+ fs.writeFileSync(dist+'/.deft-ts-build-stamp',new Date().toISOString());
153
+ }catch{}
134
154
  };
135
155
  const trySteps=(steps)=>{
136
156
  for(const [cmd,args] of steps){
137
157
  try{
138
158
  run(cmd,args);
159
+ markWarm();
139
160
  process.exit(0);
140
161
  }catch{
141
162
  // fall through to Corepack / next resolver
@@ -145,6 +166,7 @@ tasks:
145
166
  const envPm=String(process.env.DEFT_PACKAGE_MANAGER||'').trim().toLowerCase();
146
167
  if(envPm==='npm'){
147
168
  run('npm',['run',script]);
169
+ markWarm();
148
170
  process.exit(0);
149
171
  }
150
172
  const pin=String(pkg.packageManager||'').trim();
@@ -171,6 +193,11 @@ tasks:
171
193
  # Run from the operator project root so deft verbs resolve USER_WORKING_DIR
172
194
  # correctly; without this, included engine.yml defaults cwd to tasks/ (#2022).
173
195
  dir: '{{.USER_WORKING_DIR}}'
196
+ # Operator free-text (e.g. release --summary with apostrophes) must not be
197
+ # interpolated into the shell script body — JSON env transport + argv-spawn in
198
+ # tasks/engine-invoke.cjs (#2547). Avoids shell parse breaks for `'` and `\`.
199
+ env:
200
+ DEFT_ENGINE_CMD_JSON: '{{ .ENGINE_CMD | toSlash | toJson }}'
174
201
  cmds:
175
202
  - |
176
203
  set -eu
@@ -197,10 +224,9 @@ tasks:
197
224
  }
198
225
  " "$root_pkg" || exit $?
199
226
  fi
200
- node "$bin" {{.ENGINE_CMD}}
227
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" vendored "$bin"
201
228
  elif [ "$is_buildable_source" = 1 ]; then
202
- engine_cmd='{{.ENGINE_CMD}}'
203
- first_token="${engine_cmd%% *}"
229
+ first_token=$(node -e "process.stdout.write(JSON.parse(process.env.DEFT_ENGINE_CMD_JSON||'\"\"').split(/\\s+/)[0]||'')")
204
230
  is_runtime_verb=0
205
231
  case " ${first_token} " in
206
232
  " session:start "|" session-start "|\
@@ -245,7 +271,7 @@ tasks:
245
271
  console.error('deft: using global '+cli+' ('+gv+'); source checkout is v'+srcVer+' — run task build for a local engine match.');
246
272
  }
247
273
  " "{{.DEFT_ROOT}}" "$global_cli" >&2 || true
248
- "$global_cli" {{.ENGINE_CMD}}
274
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global "$global_cli"
249
275
  else
250
276
  echo "deft: CLI artifact missing at {{.DEFT_ROOT}}/packages/cli/dist/bin.js" >&2
251
277
  if [ "$is_runtime_verb" = 1 ]; then
@@ -257,9 +283,9 @@ tasks:
257
283
  exit 2
258
284
  fi
259
285
  elif command -v deft >/dev/null 2>&1; then
260
- deft {{.ENGINE_CMD}}
286
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global deft
261
287
  elif command -v directive >/dev/null 2>&1; then
262
- directive {{.ENGINE_CMD}}
288
+ node "{{.TASKFILE_DIR}}/engine-invoke.cjs" global directive
263
289
  else
264
290
  echo "deft: neither {{.DEFT_ROOT}}/packages/cli/dist/bin.js nor a global deft command is available." >&2
265
291
  echo " Install with: npm i -g @deftai/directive" >&2
package/tasks/eval.yml CHANGED
@@ -30,3 +30,12 @@ tasks:
30
30
  - task: :engine:invoke
31
31
  vars:
32
32
  ENGINE_CMD: 'eval:report --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
33
+
34
+ triggers:
35
+ 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
+ cmds:
39
+ - task: :engine:invoke
40
+ vars:
41
+ ENGINE_CMD: 'eval:triggers --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
@@ -0,0 +1,47 @@
1
+ version: '3'
2
+
3
+ # tasks/plan-sequence.yml -- ordered-plan continuation boundary (#2402)
4
+
5
+ vars:
6
+ DEFT_ROOT: '{{joinPath .TASKFILE_DIR ".."}}'
7
+
8
+ tasks:
9
+ set:
10
+ desc: "Set the active ordered-plan sequence from --file JSON (#2402)"
11
+ dir: '{{.USER_WORKING_DIR}}'
12
+ deps:
13
+ - task: :engine:_ts-build
14
+ cmds:
15
+ - task: :engine:invoke
16
+ vars:
17
+ ENGINE_CMD: 'plan-sequence set --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
18
+
19
+ current:
20
+ desc: "Show the current ordered-plan entry (#2402)"
21
+ dir: '{{.USER_WORKING_DIR}}'
22
+ deps:
23
+ - task: :engine:_ts-build
24
+ cmds:
25
+ - task: :engine:invoke
26
+ vars:
27
+ ENGINE_CMD: 'plan-sequence current --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
28
+
29
+ clear:
30
+ desc: "Clear the active ordered-plan sequence (#2402)"
31
+ dir: '{{.USER_WORKING_DIR}}'
32
+ deps:
33
+ - task: :engine:_ts-build
34
+ cmds:
35
+ - task: :engine:invoke
36
+ vars:
37
+ ENGINE_CMD: 'plan-sequence clear --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
38
+
39
+ advance:
40
+ desc: "Mark current entry complete and advance the ordered-plan index (#2402)"
41
+ dir: '{{.USER_WORKING_DIR}}'
42
+ deps:
43
+ - task: :engine:_ts-build
44
+ cmds:
45
+ - task: :engine:invoke
46
+ vars:
47
+ ENGINE_CMD: 'plan-sequence advance --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Decide whether engine:_ts-build can skip `pnpm run build` (#2563).
6
+ *
7
+ * Exit codes:
8
+ * 0 — dist is fresh enough (or DEFT_SKIP_TS_BUILD=1); skip rebuild
9
+ * 1 — rebuild required (missing dist, newer sources, or DEFT_FORCE_TS_BUILD=1)
10
+ * 2 — usage / config error
11
+ *
12
+ * Warm-dist skip avoids a full `tsc -b` (and its nested shell:true spawns) on
13
+ * every `task <verb>` when packages/cli/dist/bin.js is already up to date.
14
+ */
15
+
16
+ const fs = require("node:fs");
17
+ const path = require("node:path");
18
+
19
+ const SKIP_DIRS = new Set(["node_modules", "dist", "coverage", ".git"]);
20
+
21
+ function walkSourceFiles(dir, out) {
22
+ if (!fs.existsSync(dir)) return;
23
+ let entries;
24
+ try {
25
+ entries = fs.readdirSync(dir, { withFileTypes: true });
26
+ } catch {
27
+ return;
28
+ }
29
+ for (const ent of entries) {
30
+ const full = path.join(dir, ent.name);
31
+ if (ent.isDirectory()) {
32
+ if (SKIP_DIRS.has(ent.name)) continue;
33
+ walkSourceFiles(full, out);
34
+ continue;
35
+ }
36
+ if (ent.isFile() && /\.(?:ts|tsx|json)$/.test(ent.name) && !ent.name.endsWith(".d.ts")) {
37
+ out.push(full);
38
+ }
39
+ }
40
+ }
41
+
42
+ function main() {
43
+ const root = process.argv[2];
44
+ if (!root) {
45
+ console.error("deft: ts-build-fresh usage: ts-build-fresh.cjs <DEFT_ROOT>");
46
+ process.exit(2);
47
+ }
48
+ if (process.env.DEFT_FORCE_TS_BUILD === "1") {
49
+ process.exit(1);
50
+ }
51
+ if (process.env.DEFT_SKIP_TS_BUILD === "1") {
52
+ process.exit(0);
53
+ }
54
+
55
+ const distDir = path.join(root, "packages", "cli", "dist");
56
+ const bin = path.join(distDir, "bin.js");
57
+ const stamp = path.join(distDir, ".deft-ts-build-stamp");
58
+ if (!fs.existsSync(bin)) {
59
+ process.exit(1);
60
+ }
61
+ // Prefer the post-build stamp (#2563): `tsc -b` incremental often leaves
62
+ // bin.js mtime unchanged even after a successful build when sources are
63
+ // newer than a previous emit. The stamp is written only after a completed
64
+ // engine:_ts-build, so it is the authoritative "warm dist" marker.
65
+ const marker = fs.existsSync(stamp) ? stamp : bin;
66
+ const markerMtime = fs.statSync(marker).mtimeMs;
67
+ const candidates = [];
68
+
69
+ for (const pkg of ["cli", "core", "types"]) {
70
+ const pkgRoot = path.join(root, "packages", pkg);
71
+ if (!fs.existsSync(pkgRoot)) continue;
72
+ walkSourceFiles(path.join(pkgRoot, "src"), candidates);
73
+ for (const name of ["package.json", "tsconfig.json", "tsconfig.build.json"]) {
74
+ const fp = path.join(pkgRoot, name);
75
+ if (fs.existsSync(fp)) candidates.push(fp);
76
+ }
77
+ }
78
+ for (const name of ["package.json", "tsconfig.json", "tsconfig.base.json", "pnpm-workspace.yaml"]) {
79
+ const fp = path.join(root, name);
80
+ if (fs.existsSync(fp)) candidates.push(fp);
81
+ }
82
+
83
+ for (const file of candidates) {
84
+ try {
85
+ if (fs.statSync(file).mtimeMs > markerMtime) {
86
+ process.exit(1);
87
+ }
88
+ } catch {
89
+ process.exit(1);
90
+ }
91
+ }
92
+ process.exit(0);
93
+ }
94
+
95
+ if (require.main === module) {
96
+ main();
97
+ }
98
+
99
+ module.exports = { walkSourceFiles };
package/tasks/verify.yml CHANGED
@@ -242,6 +242,16 @@ tasks:
242
242
  vars:
243
243
  ENGINE_CMD: 'verify-session-ritual --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
244
244
 
245
+ plan-sequence:
246
+ desc: "Fail-closed ordered-plan target check (#2402). Flags: --target-kind <kind> --target <id> / --json. Skips (exit 0) when no active sequence."
247
+ dir: '{{.USER_WORKING_DIR}}'
248
+ deps:
249
+ - task: :engine:_ts-build
250
+ cmds:
251
+ - task: :engine:invoke
252
+ vars:
253
+ ENGINE_CMD: 'verify-plan-sequence --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
254
+
245
255
  tools:
246
256
  desc: "Detect required Deft host tools and print install or manual guidance (#1187). Flags: --install / --yes / --json"
247
257
  dir: '{{.USER_WORKING_DIR}}'
@@ -405,6 +415,16 @@ tasks:
405
415
  vars:
406
416
  ENGINE_CMD: 'verify:eval-health-relocation --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
407
417
 
418
+ eval-triggers-relocation:
419
+ desc: "Fail-closed eval:triggers gate when AGENTS.md / REFERENCES.md / trigger cases change (#1586). Skips (exit 0) when the diff does not touch trigger-routing homes. -- task verify:eval-triggers-relocation [-- --base-ref <ref> | --staged] [--quiet]"
420
+ dir: '{{.USER_WORKING_DIR}}'
421
+ deps:
422
+ - task: :engine:_ts-build
423
+ cmds:
424
+ - task: :engine:invoke
425
+ vars:
426
+ ENGINE_CMD: 'verify:eval-triggers-relocation --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
427
+
408
428
  agents-md-advisory:
409
429
  desc: "ADVISORY (never fail-closing) consumer AGENTS.md legibility signal (#2155). Counts the UNMANAGED (project-authored) region only -- the framework-owned managed section is excluded -- and compares it against the SOFT, operator-adjustable budget plan.policy.agentsMdAdvisory.unmanagedSoftMaxLines (generous default when unset). Consumer companion to the maintainer-only #645 ratchet; deliberately NOT a check:consumer dependency (advise->observe->enforce per #1419). Default posture ALWAYS exits 0; the opt-in --enforce flag promotes an over-budget region to a hard cap (exit 1). Raising the field is the documented, no-friction way to accept growth and silence the nudge."
410
430
  dir: '{{.USER_WORKING_DIR}}'