@deftai/directive-content 0.87.0 → 0.88.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.
- package/.agents/skills/deft-directive-swarm/SKILL.md +1 -996
- package/QUICK-START.md +4 -0
- package/Taskfile.yml +1 -0
- package/UPGRADING.md +24 -2
- package/coding/coding.md +5 -1
- package/commands.md +1 -1
- package/contracts/runtime-authority.md +34 -8
- package/docs/getting-started.md +4 -0
- package/docs/no-deft-directive.md +87 -0
- package/docs/openclaw-agent-host.md +34 -1
- package/docs/product-signal.md +2 -0
- package/docs/writing-ste100.md +53 -0
- package/glossary.md +37 -39
- package/package.json +2 -1
- package/packs/rules/rules-pack-0.1.json +25 -1
- package/packs/skills/skills-pack-0.1.json +24 -24
- package/packs/strategies/strategies-pack-0.1.json +4 -4
- package/skills/deft-directive-setup/SKILL.md +35 -8
- package/skills/deft-directive-swarm/SKILL.md +81 -978
- package/skills/deft-directive-swarm/references/core-ops.md +144 -0
- package/skills/deft-directive-swarm/references/core-phase-0.md +200 -0
- package/skills/deft-directive-swarm/references/core-phase-1-2.md +73 -0
- package/skills/deft-directive-swarm/references/core-phase-3.md +145 -0
- package/skills/deft-directive-swarm/references/core-phase-4.md +71 -0
- package/skills/deft-directive-swarm/references/core-phase-5-6.md +317 -0
- package/skills/deft-directive-swarm/references/host-cursor.md +25 -0
- package/skills/deft-directive-swarm/references/host-generic.md +27 -0
- package/skills/deft-directive-swarm/references/host-grok-build.md +37 -0
- package/skills/deft-directive-swarm/references/host-openclaw.md +70 -0
- package/skills/deft-directive-swarm/references/host-warp.md +37 -0
- package/skills/deft-directive-write-skill/SKILL.md +17 -0
- package/strategies/artifact-guards.md +24 -14
- package/strategies/discuss.md +40 -1
- package/strategies/interview.md +103 -30
- package/strategies/probe.md +27 -1
- package/tasks/engine-invoke.cjs +69 -13
- package/tasks/engine-invoke.test.cjs +188 -0
- package/tasks/verify.yml +7 -0
- package/templates/agent-prompt-preamble.md +6 -0
- package/vbrief/vbrief.md +4 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Host adapter: Warp
|
|
2
|
+
|
|
3
|
+
Legend (RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
4
|
+
|
|
5
|
+
Descriptor: `warp-orchestrated` (`start_agent`) or `warp-manual` (`WARP_*` without `start_agent`).
|
|
6
|
+
|
|
7
|
+
Load this file only after detect selects Warp. Do not load other host adapters.
|
|
8
|
+
|
|
9
|
+
### Step 2a: Orchestrated Launch (start_agent available)
|
|
10
|
+
|
|
11
|
+
! When `start_agent` is detected in the tool set, use it directly to launch each agent.
|
|
12
|
+
|
|
13
|
+
- ! Launch one agent per worktree using `start_agent` with the generated prompt and worktree path as the working directory
|
|
14
|
+
- ! Agents inherit the current environment's MCP servers, Warp Drive rules, and codebase index — equivalent to interactive Warp tabs but without manual tab management
|
|
15
|
+
- ! No user intervention needed — launch is fully automated
|
|
16
|
+
- ~ This is the preferred path: richest context with zero manual overhead
|
|
17
|
+
|
|
18
|
+
### Step 2b: Interactive Warp Tabs (start_agent unavailable, Warp detected)
|
|
19
|
+
|
|
20
|
+
! When `start_agent` is not available but Warp is detected (via `WARP_*` environment variables), fall back to manual Warp tab launch — briefly note that orchestrated launch is not available in this session, then proceed with the tab instructions below.
|
|
21
|
+
|
|
22
|
+
! **Warp tabs cannot be opened programmatically.** There is no API or CLI command to open a new Warp terminal tab from an agent or script.
|
|
23
|
+
|
|
24
|
+
Ask the user to open N new Warp terminal tabs. For each tab, the user:
|
|
25
|
+
1. Navigates to the worktree: `cd <worktree>`
|
|
26
|
+
2. Pastes the prompt directly into the **Warp agent chat input** (not the terminal)
|
|
27
|
+
|
|
28
|
+
**Context advantages of Warp tabs:**
|
|
29
|
+
- Global Warp Drive rules (personal rules auto-injected)
|
|
30
|
+
- MCP servers via UUID (GitHub, etc. — zero-config)
|
|
31
|
+
- Warp Drive notebooks, workflows, and other auto-injected context
|
|
32
|
+
- Warm codebase index from the active Warp session (no cold-start delay)
|
|
33
|
+
- Agent is interruptible and steerable mid-run
|
|
34
|
+
|
|
35
|
+
**Tradeoff:** Requires the user to manually open and manage one Warp tab per agent.
|
|
36
|
+
|
|
37
|
+
? If not running inside Warp at all (no `WARP_*` variables, no `start_agent`), use the same tab approach but with any terminal emulator — the user pastes prompts into their preferred terminal or agent interface.
|
|
@@ -162,6 +162,23 @@ The description is **the only thing the agent sees** when deciding whether to lo
|
|
|
162
162
|
|
|
163
163
|
- ⊗ Omitting RFC2119 notation — deft skills use it consistently
|
|
164
164
|
- ⊗ Putting all content in SKILL.md when it exceeds 150 lines — split into `references/*.md` or `REFERENCE.md`
|
|
165
|
+
|
|
166
|
+
- ⊗ Putting every agent-host branch (Warp, Cursor, OpenClaw, Grok Build, generic) in one always-loaded SKILL body when a host-neutral core + per-host adapter split is feasible (#2928)
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Host-adapter skills (multi-host) (#2928)
|
|
171
|
+
|
|
172
|
+
Large skills that branch on **agent host** (the runtime: Warp, Cursor, OpenClaw, Grok Build, generic-terminal) SHOULD use progressive host adapters:
|
|
173
|
+
|
|
174
|
+
1. ! Keep a **thin host-neutral core** in `SKILL.md` (triggers, phase overview, detect, route table, cross-host MUST gates).
|
|
175
|
+
2. ! Put shared depth in `references/core-*.md`.
|
|
176
|
+
3. ! Put each host path in `references/host-<id>.md` (spawn primitive, cwd rules, monitor liveness, host MUST NOT list).
|
|
177
|
+
4. ! Default load path: detect host → read core + **one** adapter.
|
|
178
|
+
5. ⊗ Load all host adapters “just in case.”
|
|
179
|
+
|
|
180
|
+
Exemplar: `skills/deft-directive-swarm/` (route table in SKILL + `references/host-openclaw.md` etc.).
|
|
181
|
+
|
|
165
182
|
- ⊗ Vague trigger phrases — use phrases the user would actually type
|
|
166
183
|
- ⊗ Overlapping triggers without negative triggers — add `Do NOT trigger on …` to the description
|
|
167
184
|
- ⊗ Naming a GitHub-integrated skill without `gh` in the name
|
|
@@ -18,7 +18,7 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
|
18
18
|
|
|
19
19
|
This file is not a standalone strategy — it is a shared guard referenced by other strategies. It activates automatically when a strategy writes to vBRIEF artifacts.
|
|
20
20
|
|
|
21
|
-
- ! Referenced by spec-generating strategies: [speckit](./speckit.md), [enterprise](./enterprise.md), [rapid](./rapid.md)
|
|
21
|
+
- ! Referenced by spec-generating strategies: [speckit](./speckit.md), [enterprise](./enterprise.md), [rapid](./rapid.md), [interview](./interview.md), [yolo](./yolo.md)
|
|
22
22
|
- ! Referenced by preparatory strategies: [bdd](./bdd.md), [discuss](./discuss.md), [research](./research.md), [map](./map.md), [probe](./probe.md)
|
|
23
23
|
|
|
24
24
|
## Workflow
|
|
@@ -32,11 +32,16 @@ This file is not a standalone strategy — it is a shared guard referenced by ot
|
|
|
32
32
|
|
|
33
33
|
## Spec-Generating Guard (Full)
|
|
34
34
|
|
|
35
|
-
Applies to strategies that write
|
|
35
|
+
Applies to strategies that write project identity as `PROJECT-DEFINITION` (⊗ never `specification.vbrief.json` / `specification.xbrief.json` per [v0-20-contract.md](./v0-20-contract.md)): **speckit**, **enterprise**, **rapid**, **interview**, **yolo**.
|
|
36
36
|
|
|
37
|
-
!
|
|
37
|
+
! **Target resolution (xbrief-first)**: guard the file that is the live project identity:
|
|
38
|
+
1. `./xbrief/PROJECT-DEFINITION.xbrief.json` when it exists (canonical current layout)
|
|
39
|
+
2. else `./vbrief/PROJECT-DEFINITION.vbrief.json` when it exists (legacy layout)
|
|
40
|
+
3. else the intended write path for a greenfield create (usually `./xbrief/PROJECT-DEFINITION.xbrief.json`)
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
! Before writing or updating that target, the strategy MUST perform the following checks (⊗ Never target the legacy `specification.*.json`):
|
|
43
|
+
|
|
44
|
+
1. ! **Check existence**: Does the resolved target file already exist?
|
|
40
45
|
- If NO: proceed with the write — no guard needed.
|
|
41
46
|
- If YES: continue to step 2.
|
|
42
47
|
|
|
@@ -51,35 +56,40 @@ Applies to strategies that write to `PROJECT-DEFINITION.vbrief.json` (⊗ never
|
|
|
51
56
|
- ! Display: "A `{status}` version of this artifact already exists. Replace it? (yes/no)"
|
|
52
57
|
- ~ If the user declines, offer to enrich (merge narratives) instead of replacing
|
|
53
58
|
|
|
54
|
-
5. ! **Narrative preservation**: When overwriting `PROJECT-DEFINITION
|
|
55
|
-
- ⊗ Overwrite all `PROJECT-DEFINITION
|
|
59
|
+
5. ! **Narrative preservation**: When overwriting `PROJECT-DEFINITION`, read existing `narratives` keys first. If the strategy only writes a subset of narratives (e.g. speckit Phase 1 writes only `Principles`), merge the new narrative into the existing set — do NOT replace all narratives with only the new key.
|
|
60
|
+
- ⊗ Overwrite all `PROJECT-DEFINITION` narratives when the strategy only produces a single narrative key
|
|
61
|
+
- ⊗ Guard only the legacy `vbrief/` path while an `xbrief/PROJECT-DEFINITION.xbrief.json` is the live identity
|
|
56
62
|
|
|
57
63
|
---
|
|
58
64
|
|
|
59
65
|
## Preparatory Guard (Light)
|
|
60
66
|
|
|
61
|
-
Applies to strategies that write scoped
|
|
67
|
+
Applies to strategies that write scoped records to `proposed/`: **bdd**, **discuss**, **research**, **map**, **probe**, and brownfield **Add scope** paths from interview/setup.
|
|
68
|
+
|
|
69
|
+
! **Target folder resolution (xbrief-first)**: scan `./xbrief/proposed/` when it exists; else legacy `./vbrief/proposed/`. Prefer writing new scopes under the same root the project already uses.
|
|
62
70
|
|
|
63
|
-
! Before writing a scope
|
|
71
|
+
! Before writing a scope record to `proposed/`, the strategy MUST check for existing files with the same scope or feature name:
|
|
64
72
|
|
|
65
|
-
1. ! **Check for existing file**: Scan `
|
|
73
|
+
1. ! **Check for existing file**: Scan the resolved `proposed/` folder for files matching the target scope/feature slug (e.g. `*-{feature}-bdd.xbrief.json`, `*-{scope}-context.vbrief.json`).
|
|
66
74
|
- If NO match: proceed with the write — no guard needed.
|
|
67
75
|
- If a match exists: continue to step 2.
|
|
68
76
|
|
|
69
|
-
2. ! **Warn and ask**: Display: "A
|
|
77
|
+
2. ! **Warn and ask**: Display: "A scope record for this feature already exists at `{path}`. Replace it, or create a new version? (replace/new)"
|
|
70
78
|
- ! If replace: overwrite the existing file
|
|
71
79
|
- ! If new: append a numeric suffix or use today's date to create a distinct file
|
|
72
80
|
|
|
73
81
|
3. ~ **Read existing decisions**: If the existing file contains a `LockedDecisions` or similar narrative, surface those decisions to the agent context so they are not lost even if the user chooses to replace.
|
|
74
82
|
|
|
75
|
-
⊗ Silently overwrite an existing scope
|
|
83
|
+
⊗ Silently overwrite an existing scope record with the same feature/scope name without warning the user.
|
|
84
|
+
⊗ Scan only legacy `vbrief/proposed/` when `xbrief/proposed/` is the live lifecycle root.
|
|
76
85
|
|
|
77
86
|
---
|
|
78
87
|
|
|
79
88
|
## Anti-Patterns
|
|
80
89
|
|
|
81
|
-
- ⊗ Writing to `PROJECT-DEFINITION
|
|
90
|
+
- ⊗ Writing to `PROJECT-DEFINITION` without checking the file's current status (⊗ never write `specification.vbrief.json` / `specification.xbrief.json` at all per v0-20-contract.md)
|
|
91
|
+
- ⊗ Guarding only `vbrief/PROJECT-DEFINITION.vbrief.json` while `xbrief/PROJECT-DEFINITION.xbrief.json` is the live identity
|
|
82
92
|
- ⊗ Silently overwriting an `approved` or `completed` artifact — this discards locked decisions and approval state
|
|
83
|
-
- ⊗ Replacing all `PROJECT-DEFINITION
|
|
84
|
-
- ⊗ Overwriting a scope
|
|
93
|
+
- ⊗ Replacing all `PROJECT-DEFINITION` narratives when the strategy only contributes one narrative key (e.g. `Principles`)
|
|
94
|
+
- ⊗ Overwriting a scope record in `proposed/` without checking if one already exists for the same scope
|
|
85
95
|
- ⊗ Accepting vague confirmation (`proceed`, `ok`) when the guard requires explicit confirmation for approved/completed artifacts
|
package/strategies/discuss.md
CHANGED
|
@@ -72,6 +72,7 @@ Adapt question focus to what's being built:
|
|
|
72
72
|
|
|
73
73
|
- ! Produce a `vbrief/proposed/{scope}-context.vbrief.json` scope vBRIEF with a `LockedDecisions` narrative
|
|
74
74
|
- ! Each decision includes: **what** was decided, **why**, and **alternatives considered**
|
|
75
|
+
- ! When the lock is an intentional under-build (weaker Now + decided end-product Later), the decision MUST also include dual-path graduation fields: `now`, `later`, `graduationRef`, `trigger`, and `status` (`open` | `shipped` | `cancelled`) — see [Graduation (Now+Later)](#graduation-nowlater-dual-path-locks-2899)
|
|
75
76
|
- ! This vBRIEF is injected into all downstream work: planning, execution, verification
|
|
76
77
|
- ! Persist decisions as vBRIEF narratives on the relevant plan items
|
|
77
78
|
- ⊗ Write decisions to a hand-authored markdown context file -- use vBRIEF narratives for token-efficient agent consumption
|
|
@@ -85,6 +86,44 @@ Adapt question focus to what's being built:
|
|
|
85
86
|
- ⊗ Silently making a different choice because the agent forgot what was decided
|
|
86
87
|
- ⊗ Re-debating a settled decision without explicit user approval
|
|
87
88
|
|
|
89
|
+
## Graduation (Now+Later) dual-path locks (#2899)
|
|
90
|
+
|
|
91
|
+
**Graduation** (alias: Now+Later) is a first-class decision shape for intentional under-builds:
|
|
92
|
+
|
|
93
|
+
> We *decided* to ship a weaker path **now**, and we also decided what the end-product path is **later**.
|
|
94
|
+
|
|
95
|
+
It is **not** the same as other "later" concepts:
|
|
96
|
+
|
|
97
|
+
| Concept | Meaning |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `DeferredDecisions` (probe) | Not decided yet; open question with justification |
|
|
100
|
+
| deferred plan item / `triage:defer` | Out of scope, or not accepted into the workspace backlog |
|
|
101
|
+
| rapid **graduate** | Spike / prototype → fresh full interview/spec cycle |
|
|
102
|
+
| **Graduation** | Decided weaker **Now** + decided end-product **Later** |
|
|
103
|
+
|
|
104
|
+
Glossary naming for this term is owned by sibling work (#2907). Strategy prose here is the Wave A contract agents must follow until the glossary entry lands.
|
|
105
|
+
|
|
106
|
+
### When a lock is an under-build
|
|
107
|
+
|
|
108
|
+
- ! When a `LockedDecisions` entry chooses a temporary, weaker, MVP, or shortcut approach **and** a stronger end-product approach is also decided, the entry MUST record a dual-path graduation shape with all of:
|
|
109
|
+
- `now` — what this scope ships
|
|
110
|
+
- `later` — the end-product approach
|
|
111
|
+
- `graduationRef` — GitHub issue URL and/or scope xBRIEF path that tracks Later work
|
|
112
|
+
- `trigger` — free-text condition that makes Later required (examples are illustrative only, e.g. "before multi-tenant customers", "when latency SLO is adopted")
|
|
113
|
+
- `status` — `open` | `shipped` | `cancelled` (cancellation MUST include justification)
|
|
114
|
+
- ! A permanent approach lock (no weaker temporary path) does **not** require graduation fields
|
|
115
|
+
- ! Chat-only "we'll harden this later" is insufficient — same anti-pattern as decisions that exist only in conversation history
|
|
116
|
+
- ~ Emitting `graduationRef` SHOULD use existing [emit-hints](./emit-hints.md) patterns (none / `--umbrella` / `--per-vbrief`) rather than a parallel SCM ontology
|
|
117
|
+
- ⊗ Routing a *decided* under-build into `DeferredDecisions`, a deferred plan item, or `triage:defer` — those surfaces mean undecided or out-of-scope, not dual-path delivery
|
|
118
|
+
- ⊗ Treating rapid prototype **graduate** as Graduation dual-path tracking inside a normal production build
|
|
119
|
+
|
|
120
|
+
### Lifecycle: Now complete ≠ Later closed
|
|
121
|
+
|
|
122
|
+
- ! `task scope:complete` on the Now story MUST NOT imply closure of linked graduation work (`graduationRef` issue and/or Later scope xBRIEF stay open until Later ships or is explicitly cancelled)
|
|
123
|
+
- ⊗ Closing a graduation ticket solely because the MVP / Now story completed
|
|
124
|
+
- ~ Wave A (soft): when completing a story whose `LockedDecisions` contain an open graduation, warn and audit if `graduationRef` is missing or invalid — hard fail / policy flag is Wave B follow-on
|
|
125
|
+
- ! Durable surfaces for graduation are: (1) strategy output narratives (this contract), (2) optional always-loadable ProjectRules for stricter consumer policy — ⊗ Cursor-rule-only as the sole persistence path
|
|
126
|
+
|
|
88
127
|
---
|
|
89
128
|
|
|
90
129
|
## Then: Chaining Gate
|
|
@@ -115,7 +154,7 @@ run additional preparatory strategies or proceed to spec generation.
|
|
|
115
154
|
|
|
116
155
|
1. **Open** -- Start with the user's goal statement; restate it in your own words
|
|
117
156
|
2. **Explore** -- Follow energy, challenge vagueness, ask domain-sensitive questions
|
|
118
|
-
3. **Lock** -- Record each decision in `vbrief/proposed/{scope}-context.vbrief.json` `LockedDecisions` narrative with what/why/alternatives
|
|
157
|
+
3. **Lock** -- Record each decision in `vbrief/proposed/{scope}-context.vbrief.json` `LockedDecisions` narrative with what/why/alternatives (and dual-path graduation fields when the lock is an under-build; #2899)
|
|
119
158
|
4. **Verify** -- Explain the full picture back to the user (Feynman check)
|
|
120
159
|
5. **Chain** -- Return to [interview.md Chaining Gate](./interview.md#chaining-gate), or -- if invoked from a standalone strategy (e.g. map's standalone next-step menu) -- return to the invoking strategy's menu per the [standalone-context rule](#then-chaining-gate) above
|
|
121
160
|
|
package/strategies/interview.md
CHANGED
|
@@ -13,7 +13,7 @@ follow this strategy.
|
|
|
13
13
|
|
|
14
14
|
Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
15
15
|
|
|
16
|
-
**⚠️ See also**: [strategies/discuss.md](./discuss.md) | [strategies/yolo.md](./yolo.md) | [core/glossary.md](../glossary.md)
|
|
16
|
+
**⚠️ See also**: [strategies/discuss.md](./discuss.md) | [strategies/yolo.md](./yolo.md) | [strategies/artifact-guards.md](./artifact-guards.md) | [core/glossary.md](../glossary.md)
|
|
17
17
|
|
|
18
18
|
## When to Use
|
|
19
19
|
|
|
@@ -42,13 +42,37 @@ and wait for the user to choose before proceeding.
|
|
|
42
42
|
- ! After each completed preparatory strategy (recursive — the gate reappears)
|
|
43
43
|
- ! After the [Acceptance Gate](#acceptance-gate) when the user chooses "Revise" or "Switch"
|
|
44
44
|
|
|
45
|
+
### Brownfield Detector
|
|
46
|
+
|
|
47
|
+
Before rendering menu options, classify the repo (align with setup Phase 3):
|
|
48
|
+
|
|
49
|
+
- **Brownfield** when **either**:
|
|
50
|
+
- `PROJECT-DEFINITION` exists under `./xbrief/` or legacy `./vbrief/` (`PROJECT-DEFINITION.xbrief.json` or `PROJECT-DEFINITION.vbrief.json`), **or**
|
|
51
|
+
- any lifecycle folder (`proposed/`, `pending/`, `active/`, `completed/`, `cancelled/` under `xbrief/` or legacy `vbrief/`) has scope records
|
|
52
|
+
- **Greenfield** otherwise
|
|
53
|
+
|
|
54
|
+
! The detector MUST run on every Chaining Gate presentation.
|
|
55
|
+
⊗ Offer only the greenfield-framed **Proceed to specification** default on a brownfield repo.
|
|
56
|
+
⊗ Treat brownfield as a full create path without an explicit Replace/scrap confirm.
|
|
57
|
+
|
|
45
58
|
### Options
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
! Numbered Chaining Gate menus (greenfield and brownfield examples below) MUST end with `Discuss` and `Back` as the final two options per [deterministic-questions.md](../contracts/deterministic-questions.md).
|
|
61
|
+
|
|
62
|
+
Present groups sourced from the `Type` column in
|
|
63
|
+
[strategies/README.md](./README.md#strategy-types). The **default path** depends
|
|
64
|
+
on the brownfield detector:
|
|
65
|
+
|
|
66
|
+
**Default path (greenfield):**
|
|
67
|
+
1. **Proceed to specification** (default) — continue to the [Sizing Gate](#sizing-gate) for a full create path
|
|
49
68
|
|
|
50
|
-
**Default:**
|
|
51
|
-
1. **
|
|
69
|
+
**Default path (brownfield) — create-vs-update menu:**
|
|
70
|
+
1. **Add scope to this project** (default) — load existing project identity + preparatory artifacts; **skip** the greenfield "what are we building?" interview; gather only the new scope; emit **one** proposed scope record; apply the [Preparatory Guard](./artifact-guards.md#preparatory-guard-light) on write
|
|
71
|
+
2. **Update project definition** — run a **delta** interview against existing identity; apply the [Spec-Generating Guard](./artifact-guards.md#spec-generating-guard-full); **merge** narratives (prefer enrich/merge when the user declines replace)
|
|
72
|
+
3. **Replace specification (scrap)** — full recreate equivalent to greenfield Proceed; **only** after explicit affirmative (`yes` / `confirmed`); vague replies (`proceed`, `ok`, `go ahead`) are **not** acceptance
|
|
73
|
+
- ! On confirmed scrap, continue to the [Sizing Gate](#sizing-gate) as a full create path
|
|
74
|
+
- ⊗ Auto-delete PROJECT-DEFINITION without scrap confirm
|
|
75
|
+
- ⊗ Rename Proceed to "Update" without changing behavior
|
|
52
76
|
|
|
53
77
|
**Preparatory strategies** (type: `preparatory` — loops back to this gate on completion):
|
|
54
78
|
- Research — investigate the domain, find libraries, identify pitfalls
|
|
@@ -79,9 +103,28 @@ See `strategies/map.md` for standalone behavior.
|
|
|
79
103
|
- ! Append all new artifact paths to the flat `artifacts` array
|
|
80
104
|
- ! The next strategy and eventual spec generation MUST load all artifacts
|
|
81
105
|
listed in `plan.vbrief.json`
|
|
106
|
+
- ! On brownfield **Add scope** or **Update project definition**, load existing
|
|
107
|
+
PROJECT-DEFINITION identity and preparatory artifacts before asking questions
|
|
108
|
+
|
|
109
|
+
### Write Guards (interview)
|
|
110
|
+
|
|
111
|
+
! Interview is a **spec-generating** strategy and MUST follow
|
|
112
|
+
[artifact-guards.md](./artifact-guards.md) before emission (Light or Full path).
|
|
113
|
+
|
|
114
|
+
- ! **Before writing** scope records to `proposed/`: [Preparatory Guard](./artifact-guards.md#preparatory-guard-light) (xbrief-first: `./xbrief/proposed/`, else legacy `./vbrief/proposed/`)
|
|
115
|
+
- ! **Before writing** or updating `PROJECT-DEFINITION`: [Spec-Generating Guard](./artifact-guards.md#spec-generating-guard-full) against the **single live** identity file only — `./xbrief/PROJECT-DEFINITION.xbrief.json` when present (or after `deft migrate:xbrief`); else, until migrated, the existing legacy `./vbrief/PROJECT-DEFINITION.vbrief.json`. ⊗ Dual-write both roots
|
|
116
|
+
- ! Prefer enrich/merge when the user declines replace
|
|
117
|
+
- ⊗ Silently overwrite PROJECT-DEFINITION or same-slug proposed scopes
|
|
118
|
+
- ⊗ Guard only the legacy `vbrief/` path while an `xbrief/` PROJECT-DEFINITION is the live identity
|
|
119
|
+
- ⊗ Rely on write-time guards alone without the brownfield Chaining Gate menu above
|
|
120
|
+
|
|
121
|
+
Surface this obligation **inside `interview.md`** so agents that load only this
|
|
122
|
+
file still hit the guards (#2925; incomplete closeout of #82/#387).
|
|
82
123
|
|
|
83
124
|
### Example Prompt
|
|
84
125
|
|
|
126
|
+
**Greenfield:**
|
|
127
|
+
|
|
85
128
|
```
|
|
86
129
|
Ready to generate the specification. Before we proceed, would you like to:
|
|
87
130
|
|
|
@@ -96,8 +139,32 @@ Ready to generate the specification. Before we proceed, would you like to:
|
|
|
96
139
|
--- Switch strategy ---
|
|
97
140
|
6. Switch to yolo — auto-pilot picks all answers
|
|
98
141
|
7. Switch to speckit — formal spec process with story readiness before implementation
|
|
99
|
-
|
|
100
142
|
8. Other (specify)
|
|
143
|
+
9. Discuss
|
|
144
|
+
10. Back
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Brownfield:**
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
This repo already has a project definition and/or scopes. Before we proceed:
|
|
151
|
+
|
|
152
|
+
1. Add scope to this project (default) — keep identity; add one new proposed scope
|
|
153
|
+
2. Update project definition — delta interview; merge narratives (Spec-Generating Guard)
|
|
154
|
+
3. Replace specification (scrap) — requires explicit yes/confirmed; full recreate
|
|
155
|
+
|
|
156
|
+
--- Preparatory (loops back) ---
|
|
157
|
+
4. Run a research phase — investigate the domain, find libraries, identify pitfalls
|
|
158
|
+
5. Run a discuss phase — lock key decisions using Feynman technique
|
|
159
|
+
6. Run a probe phase — adversarially stress-test the plan; surface assumptions, edge cases, and risks
|
|
160
|
+
7. Run a map phase — analyze existing codebase conventions
|
|
161
|
+
|
|
162
|
+
--- Switch strategy ---
|
|
163
|
+
8. Switch to yolo — auto-pilot picks all answers
|
|
164
|
+
9. Switch to speckit — formal spec process with story readiness before implementation
|
|
165
|
+
10. Other (specify)
|
|
166
|
+
11. Discuss
|
|
167
|
+
12. Back
|
|
101
168
|
```
|
|
102
169
|
|
|
103
170
|
---
|
|
@@ -188,19 +255,22 @@ flowchart LR
|
|
|
188
255
|
|
|
189
256
|
## Light Path (small/medium projects)
|
|
190
257
|
|
|
191
|
-
Interview → scope
|
|
258
|
+
Interview → scope records (date-prefixed in proposed/) + live PROJECT-DEFINITION + rendered SPECIFICATION (v0.20 contract). Paths are **xbrief-first**: use `./xbrief/` when that root is the live project layout; else legacy `./vbrief/`.
|
|
192
259
|
|
|
193
260
|
### Flow
|
|
194
261
|
|
|
195
262
|
1. Sizing gate selects Light
|
|
196
263
|
2. Interview (rules above)
|
|
197
|
-
3. Write scope
|
|
198
|
-
4. Run `task project:render` to create/update `./
|
|
264
|
+
3. Write scope record(s) to the live lifecycle root — `./xbrief/proposed/YYYY-MM-DD-<slug>.xbrief.json` when `./xbrief/` is live; else legacy `./vbrief/proposed/YYYY-MM-DD-<slug>.vbrief.json` — date-prefixed with `status: proposed`
|
|
265
|
+
4. Run `task project:render` to create/update the **live** PROJECT-DEFINITION under the resolved layout root (`./xbrief/PROJECT-DEFINITION.xbrief.json`). Engine layout resolution requires `xbrief/` (#2112); if the repo is legacy-only `./vbrief/`, run `deft migrate:xbrief` first, then render. Ensure all five lifecycle folders exist under the live root. ⊗ Write a second PROJECT-DEFINITION under legacy `vbrief/` when `xbrief/` is live
|
|
199
266
|
5. Summarize decisions, ask user to review
|
|
200
|
-
6. On approval, use `task scope:promote` (or equivalent) to move scope
|
|
201
|
-
7. Run `task spec:render` (SPECIFICATION.md is a rendered derivative with deprecation sentinel; `specification.vbrief.json`
|
|
267
|
+
6. On approval, use `task scope:promote` (or equivalent) to move scope record(s) to `pending/` under the **same** live root with `status: pending` / `approved`
|
|
268
|
+
7. Run `task project:export-spec` / `task spec:render` as appropriate (SPECIFICATION.md is a rendered derivative with deprecation sentinel; `specification.vbrief.json` / `specification.xbrief.json` are legacy and are NOT written by this strategy on the v0.20 path)
|
|
202
269
|
|
|
203
|
-
!
|
|
270
|
+
! **Before writing** scope records or updating `PROJECT-DEFINITION` on the Light path, follow [artifact-guards.md](./artifact-guards.md): Preparatory Guard for `proposed/` scopes; Spec-Generating Guard against the **live** identity file (xbrief-first). Prefer enrich/merge when the user declines replace.
|
|
271
|
+
! ⊗ Write a second PROJECT-DEFINITION under legacy `vbrief/` when `./xbrief/PROJECT-DEFINITION.xbrief.json` is already the live identity.
|
|
272
|
+
|
|
273
|
+
! At the emission step (step 3 above), after writing the scope record(s) to the live `proposed/`, surface the GitHub-issue tracking hint from [emit-hints.md](./emit-hints.md) — name all three patterns (none / `--umbrella` / `--per-vbrief`).
|
|
204
274
|
|
|
205
275
|
### SPECIFICATION Structure (Light)
|
|
206
276
|
|
|
@@ -252,20 +322,23 @@ How to ship it.
|
|
|
252
322
|
|
|
253
323
|
## Full Path (large/complex projects)
|
|
254
324
|
|
|
255
|
-
Interview → PRD → scope
|
|
325
|
+
Interview → PRD → scope records (date-prefixed in proposed/) + live PROJECT-DEFINITION + rendered SPECIFICATION (v0.20 contract). Paths are **xbrief-first**: use `./xbrief/` when that root is the live project layout; else legacy `./vbrief/`.
|
|
256
326
|
|
|
257
327
|
### Flow
|
|
258
328
|
|
|
259
329
|
1. Sizing gate selects Full
|
|
260
330
|
2. Interview (rules above)
|
|
261
331
|
3. Generate `PRD.md` — user approval gate
|
|
262
|
-
4. Write scope
|
|
263
|
-
5. Run `task project:render` to create/update `./
|
|
332
|
+
4. Write scope record(s) to the live lifecycle root — `./xbrief/proposed/YYYY-MM-DD-<slug>.xbrief.json` when `./xbrief/` is live; else legacy `./vbrief/proposed/YYYY-MM-DD-<slug>.vbrief.json` — date-prefixed with `status: proposed`
|
|
333
|
+
5. Run `task project:render` to create/update the **live** PROJECT-DEFINITION under the resolved layout root (`./xbrief/PROJECT-DEFINITION.xbrief.json`). Engine layout resolution requires `xbrief/` (#2112); if the repo is legacy-only `./vbrief/`, run `deft migrate:xbrief` first, then render. Ensure all five lifecycle folders exist under the live root. ⊗ Write a second PROJECT-DEFINITION under legacy `vbrief/` when `xbrief/` is live
|
|
264
334
|
6. Summarize decisions, ask user to review
|
|
265
|
-
7. On approval, use `task scope:promote` (or equivalent) to move scope
|
|
266
|
-
8. Run `task spec:render` (SPECIFICATION.md is a rendered derivative with deprecation sentinel; `specification.vbrief.json`
|
|
335
|
+
7. On approval, use `task scope:promote` (or equivalent) to move scope record(s) to `pending/` under the **same** live root with `status: pending` / `approved`
|
|
336
|
+
8. Run `task project:export-spec` / `task spec:render` as appropriate (SPECIFICATION.md is a rendered derivative with deprecation sentinel; `specification.vbrief.json` / `specification.xbrief.json` are legacy and are NOT written by this strategy on the v0.20 path)
|
|
337
|
+
|
|
338
|
+
! **Before writing** PRD, scope records, or updating `PROJECT-DEFINITION` on the Full path, follow [artifact-guards.md](./artifact-guards.md): Preparatory Guard for `proposed/` scopes; Spec-Generating Guard against the **live** identity file (xbrief-first). Prefer enrich/merge when the user declines replace.
|
|
339
|
+
! ⊗ Write a second PROJECT-DEFINITION under legacy `vbrief/` when `./xbrief/PROJECT-DEFINITION.xbrief.json` is already the live identity.
|
|
267
340
|
|
|
268
|
-
! At the emission step (step 4 above), after writing the scope
|
|
341
|
+
! At the emission step (step 4 above), after writing the scope record(s) to the live `proposed/`, surface the GitHub-issue tracking hint from [emit-hints.md](./emit-hints.md) — name all three patterns (none / `--umbrella` / `--per-vbrief`).
|
|
269
342
|
|
|
270
343
|
### PRD Structure (Full path only)
|
|
271
344
|
|
|
@@ -417,9 +490,9 @@ Each task SHOULD include:
|
|
|
417
490
|
|
|
418
491
|
- ! All requirements mapped to tasks
|
|
419
492
|
- ! Dependencies form a valid DAG (no cycles)
|
|
420
|
-
- ! Scope
|
|
421
|
-
- ! `./
|
|
422
|
-
- ! `SPECIFICATION.md` has been rendered via
|
|
493
|
+
- ! Scope records exist in the live `proposed/` root (`./xbrief/proposed/` when live; else legacy `./vbrief/proposed/`) with date-prefixed filenames and `status: "proposed"`, or promoted to pending/active (with `status: "approved" / "pending"`)
|
|
494
|
+
- ! Live PROJECT-DEFINITION is present (`./xbrief/PROJECT-DEFINITION.xbrief.json` when that is the identity; else legacy `./vbrief/PROJECT-DEFINITION.vbrief.json`) populated via `task project:render`
|
|
495
|
+
- ! `SPECIFICATION.md` has been rendered via export/`task spec:render`
|
|
423
496
|
- ! Proceed to [Acceptance Gate](#acceptance-gate)
|
|
424
497
|
|
|
425
498
|
---
|
|
@@ -517,20 +590,20 @@ diff only on the second pass or when the user explicitly asks for it.
|
|
|
517
590
|
|
|
518
591
|
| Artifact | Purpose | Created By |
|
|
519
592
|
|----------|---------|------------|
|
|
520
|
-
| `./vbrief/proposed/YYYY-MM-DD-*.vbrief.json` | Scope story
|
|
521
|
-
| `./vbrief/PROJECT-DEFINITION.vbrief.json` | Project identity gestalt + items registry | `task project:render` (triggered by strategy) |
|
|
522
|
-
| `SPECIFICATION.md` | Generated plan with embedded Requirements (rendered derivative; deprecation sentinel) | `task spec:render` |
|
|
523
|
-
| (no `specification
|
|
593
|
+
| `./xbrief/proposed/YYYY-MM-DD-*.xbrief.json` (else legacy `./vbrief/proposed/YYYY-MM-DD-*.vbrief.json`) | Scope story records (date-prefixed, v0.20 contract) | Interview |
|
|
594
|
+
| `./xbrief/PROJECT-DEFINITION.xbrief.json` (else legacy `./vbrief/PROJECT-DEFINITION.vbrief.json`) | Project identity gestalt + items registry | `task project:render` (triggered by strategy) |
|
|
595
|
+
| `SPECIFICATION.md` | Generated plan with embedded Requirements (rendered derivative; deprecation sentinel) | export / `task spec:render` |
|
|
596
|
+
| (no `specification.*.json`) | Legacy artifact — omitted on v0.20 path | — |
|
|
524
597
|
|
|
525
598
|
**Full path:**
|
|
526
599
|
|
|
527
600
|
| Artifact | Purpose | Created By |
|
|
528
601
|
|----------|---------|------------|
|
|
529
602
|
| `PRD.md` | What to build (approval gate) | Interview |
|
|
530
|
-
| `./vbrief/proposed/YYYY-MM-DD-*.vbrief.json` | Scope story
|
|
531
|
-
| `./vbrief/PROJECT-DEFINITION.vbrief.json` | Project identity gestalt + items registry | `task project:render` (triggered by strategy) |
|
|
532
|
-
| `SPECIFICATION.md` | Generated implementation plan (rendered derivative; deprecation sentinel) | `task spec:render` |
|
|
533
|
-
| (no `specification
|
|
603
|
+
| `./xbrief/proposed/YYYY-MM-DD-*.xbrief.json` (else legacy `./vbrief/proposed/YYYY-MM-DD-*.vbrief.json`) | Scope story records (date-prefixed, v0.20 contract) | Post-PRD interview |
|
|
604
|
+
| `./xbrief/PROJECT-DEFINITION.xbrief.json` (else legacy `./vbrief/PROJECT-DEFINITION.vbrief.json`) | Project identity gestalt + items registry | `task project:render` (triggered by strategy) |
|
|
605
|
+
| `SPECIFICATION.md` | Generated implementation plan (rendered derivative; deprecation sentinel) | export / `task spec:render` |
|
|
606
|
+
| (no `specification.*.json`) | Legacy artifact — omitted on v0.20 path | — |
|
|
534
607
|
|
|
535
608
|
## Invoking This Strategy
|
|
536
609
|
|
|
@@ -547,5 +620,5 @@ Use the interview strategy to plan [project].
|
|
|
547
620
|
After completion:
|
|
548
621
|
|
|
549
622
|
```
|
|
550
|
-
implement the scope
|
|
623
|
+
implement the scope records in ./xbrief/active/ (or legacy ./vbrief/active/)
|
|
551
624
|
```
|
package/strategies/probe.md
CHANGED
|
@@ -104,7 +104,7 @@ feature/component name if probing a sub-scope. Use the same value consistently
|
|
|
104
104
|
throughout the session. Examples: `my-app-probe`, `auth-probe`.
|
|
105
105
|
|
|
106
106
|
- ! Produce a `vbrief/proposed/{scope}-probe.vbrief.json` scope vBRIEF with three mandatory narratives:
|
|
107
|
-
- `LockedDecisions` — what was resolved and why
|
|
107
|
+
- `LockedDecisions` — what was resolved and why (when the lock is an intentional under-build, include dual-path graduation fields: `now`, `later`, `graduationRef`, `trigger`, `status` — see [Graduation (Now+Later)](#graduation-nowlater-when-locking-an-under-build-2899))
|
|
108
108
|
- `SurfacedRisks` — concerns raised, even if not fully resolved
|
|
109
109
|
- `DeferredDecisions` — explicitly acknowledged items with justification
|
|
110
110
|
- ! Each entry in a narrative includes: **question asked**, **answer given**, **status** (locked / deferred / risk-accepted)
|
|
@@ -114,6 +114,30 @@ throughout the session. Examples: `my-app-probe`, `auth-probe`.
|
|
|
114
114
|
|
|
115
115
|
! After emitting the probe scope vBRIEF to `vbrief/proposed/`, surface the GitHub-issue tracking hint from [emit-hints.md](./emit-hints.md) — name all three patterns (none / `--umbrella` / `--per-vbrief`).
|
|
116
116
|
|
|
117
|
+
## Graduation (Now+Later) when locking an under-build (#2899)
|
|
118
|
+
|
|
119
|
+
Probe already separates **locked**, **deferred**, and **risk-accepted** branches. **Graduation** is a fourth case that must not collapse into `DeferredDecisions`:
|
|
120
|
+
|
|
121
|
+
| Concept | Probe role |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `LockedDecisions` (permanent) | Approach is decided and is the end-product path |
|
|
124
|
+
| `DeferredDecisions` | *Undecided* open question with justification |
|
|
125
|
+
| **Graduation** on a lock | *Decided* weaker **Now** + *decided* end-product **Later** |
|
|
126
|
+
| rapid **graduate** | Strategy-level spike → fresh spec; not dual-path tracking here |
|
|
127
|
+
|
|
128
|
+
Glossary naming for Graduation is owned by sibling work (#2907); this section is the Wave A probe contract.
|
|
129
|
+
|
|
130
|
+
- ! When probe **locks** a temporary / weaker approach and the end-product approach is also decided, record the dual-path shape on that `LockedDecisions` entry (not as a `DeferredDecisions` open question):
|
|
131
|
+
- `now` — what ships in the near path
|
|
132
|
+
- `later` — end-product approach
|
|
133
|
+
- `graduationRef` — GitHub issue and/or scope xBRIEF path for Later work
|
|
134
|
+
- `trigger` — condition that makes Later required (free text)
|
|
135
|
+
- `status` — `open` | `shipped` | `cancelled` (cancel with justification)
|
|
136
|
+
- ! Permanent locks and true open questions do **not** gain graduation fields — do not ticket-spam every `DeferredDecisions` item into a graduation
|
|
137
|
+
- ! `task scope:complete` on a Now story MUST NOT close linked graduation work; Later stays open until shipped or explicitly cancelled
|
|
138
|
+
- ⊗ Accepting "we'll harden it later" as a locked under-build without dual-path fields, or filing it only as `DeferredDecisions`
|
|
139
|
+
- ⊗ Closing graduation work solely because the MVP / Now story completed
|
|
140
|
+
|
|
117
141
|
---
|
|
118
142
|
|
|
119
143
|
## Then: Chaining Gate
|
|
@@ -143,6 +167,8 @@ written, return to the [chaining gate](./interview.md#chaining-gate).
|
|
|
143
167
|
## Anti-Patterns
|
|
144
168
|
|
|
145
169
|
- ⊗ Accepting "we'll figure it out later" without marking it as explicitly deferred
|
|
170
|
+
- ⊗ Recording a *decided* weaker-Now + stronger-Later path only as `DeferredDecisions` (or chat) instead of dual-path graduation fields on the lock (#2899)
|
|
171
|
+
- ⊗ Treating Now-story `scope:complete` as closure of linked graduation work (#2899)
|
|
146
172
|
- ⊗ Asking generic checklist questions instead of following the decision tree
|
|
147
173
|
- ⊗ Letting vague answers pass without pushing for concrete specifics
|
|
148
174
|
- ⊗ Using codebase exploration as a substitute for asking the user about deliberate design choices
|
package/tasks/engine-invoke.cjs
CHANGED
|
@@ -12,6 +12,29 @@
|
|
|
12
12
|
|
|
13
13
|
const { spawnSync } = require("node:child_process");
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* cmd.exe command separators / metacharacters. Free-text DEFT_ENGINE_CMD_JSON
|
|
17
|
+
* tokens (release --summary text, CLI_ARGS, #2547) may legitimately contain
|
|
18
|
+
* these; double-quoting renders them literal to cmd.exe's parser so a token can
|
|
19
|
+
* never break out of its argv slot (subprocess-scm-01 / #2911).
|
|
20
|
+
*/
|
|
21
|
+
const WIN32_CMD_METACHAR_RE = /[\s"&|<>^()%!]/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Quote a single argument for `cmd.exe /d /s /c` so that shell metacharacters
|
|
25
|
+
* stay inside one argv token. Mirrors tasks/engine-pm-run.cjs quoteWin32Arg but
|
|
26
|
+
* also quotes cmd.exe separators (& | < > ^ ( ) % !) because engine-invoke
|
|
27
|
+
* forwards operator free-text, not an allowlisted command.
|
|
28
|
+
* @param {string} arg
|
|
29
|
+
*/
|
|
30
|
+
function quoteWin32Arg(arg) {
|
|
31
|
+
const s = String(arg);
|
|
32
|
+
if (s.length > 0 && !WIN32_CMD_METACHAR_RE.test(s)) {
|
|
33
|
+
return s;
|
|
34
|
+
}
|
|
35
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
/** Minimal POSIX-ish shell word splitter (double/single quotes, escapes). */
|
|
16
39
|
function shellSplit(input) {
|
|
17
40
|
const out = [];
|
|
@@ -75,15 +98,8 @@ function main() {
|
|
|
75
98
|
process.exit(2);
|
|
76
99
|
}
|
|
77
100
|
|
|
78
|
-
|
|
79
|
-
|
|
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 {
|
|
101
|
+
const plan = buildSpawnPlan(mode, target, argv);
|
|
102
|
+
if (!plan) {
|
|
87
103
|
console.error(`deft: engine-invoke unknown mode ${JSON.stringify(mode)}`);
|
|
88
104
|
process.exit(2);
|
|
89
105
|
}
|
|
@@ -97,11 +113,12 @@ function main() {
|
|
|
97
113
|
// stdio inherit (not pipe): piped stdout/stderr deadlocks when the child emits
|
|
98
114
|
// more than the OS pipe buffer before exit — observed as greenfield smoke
|
|
99
115
|
// hanging then CI SIGTERM exit 143 with no output (#2554 / #2547).
|
|
100
|
-
const result = spawnSync(
|
|
116
|
+
const result = spawnSync(plan.command, plan.args, {
|
|
101
117
|
stdio: "inherit",
|
|
102
118
|
env: childEnv,
|
|
103
|
-
//
|
|
104
|
-
|
|
119
|
+
// Never shell:true — even on win32 global (subprocess-scm-01 / #2911). The
|
|
120
|
+
// win32 .cmd shim is reached through a tightly quoted cmd.exe wrapper below.
|
|
121
|
+
shell: plan.shell,
|
|
105
122
|
// CREATE_NO_WINDOW: hide console windows from Cursor Task / nested shells (#2563).
|
|
106
123
|
windowsHide: true,
|
|
107
124
|
});
|
|
@@ -109,8 +126,47 @@ function main() {
|
|
|
109
126
|
process.exit(code === null ? 1 : code);
|
|
110
127
|
}
|
|
111
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Resolve the concrete spawn command/args for a mode+target without ever using
|
|
131
|
+
* shell:true. On the win32 global path the target is a `.cmd` shim that Node
|
|
132
|
+
* refuses to spawn with shell:false (CVE-2024-27980 / #2415); shell:true would
|
|
133
|
+
* let cmd.exe re-parse free-text DEFT_ENGINE_CMD_JSON tokens (subprocess-scm-01
|
|
134
|
+
* / #2911). Instead route through `cmd.exe /d /s /c` with every token tightly
|
|
135
|
+
* quoted so metacharacters stay inside a single argv token — aligned with
|
|
136
|
+
* tasks/engine-pm-run.cjs executeAllowlisted().
|
|
137
|
+
*
|
|
138
|
+
* @param {string} mode
|
|
139
|
+
* @param {string} target
|
|
140
|
+
* @param {string[]} argv
|
|
141
|
+
* @param {{ platform?: string, nodePath?: string }} [opts]
|
|
142
|
+
* @returns {{ command: string, args: string[], shell: false } | null}
|
|
143
|
+
*/
|
|
144
|
+
function buildSpawnPlan(mode, target, argv, opts = {}) {
|
|
145
|
+
const platform = opts.platform || process.platform;
|
|
146
|
+
const nodePath = opts.nodePath || process.execPath;
|
|
147
|
+
|
|
148
|
+
let execPath;
|
|
149
|
+
let execArgv;
|
|
150
|
+
if (mode === "vendored") {
|
|
151
|
+
execPath = nodePath;
|
|
152
|
+
execArgv = [target, ...argv];
|
|
153
|
+
} else if (mode === "global") {
|
|
154
|
+
execPath = target;
|
|
155
|
+
execArgv = argv;
|
|
156
|
+
} else {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (mode === "global" && platform === "win32") {
|
|
161
|
+
const commandLine = [execPath, ...execArgv].map(quoteWin32Arg).join(" ");
|
|
162
|
+
return { command: "cmd.exe", args: ["/d", "/s", "/c", commandLine], shell: false };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { command: execPath, args: execArgv, shell: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
112
168
|
if (require.main === module) {
|
|
113
169
|
main();
|
|
114
170
|
}
|
|
115
171
|
|
|
116
|
-
module.exports = { shellSplit };
|
|
172
|
+
module.exports = { shellSplit, quoteWin32Arg, buildSpawnPlan, WIN32_CMD_METACHAR_RE };
|