@deftai/directive-content 0.101.0 → 0.103.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/UPGRADING.md +3 -1
- package/commands.md +1 -1
- package/context/context.md +28 -0
- package/context/long-horizon.md +12 -0
- package/context/tool-design.md +3 -2
- package/docs/consumer-check-contract.md +14 -22
- package/docs/decision-log.md +25 -0
- package/main.md +5 -2
- package/package.json +2 -1
- package/packs/patterns/patterns-pack-0.1.json +10 -0
- package/packs/skills/skills-pack-0.1.json +27 -27
- package/patterns/code-mode.md +153 -0
- package/patterns/llm-app.md +4 -2
- package/scm/github.md +3 -3
- package/skills/deft-directive-build/SKILL.md +10 -0
- package/skills/deft-directive-portfolio-priority/SKILL.md +8 -3
- package/skills/deft-directive-pre-pr/SKILL.md +10 -0
- package/skills/deft-directive-release/SKILL.md +1 -1
- package/tasks/engine-invoke.cjs +170 -1
- package/tasks/engine-invoke.test.cjs +146 -1
- package/tasks/engine.yml +10 -8
- package/templates/agent-prompt-preamble.md +5 -0
- package/templates/agents-entry.md +8 -4
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Code Mode — compact search + sandboxed execute (#2593)
|
|
2
|
+
|
|
3
|
+
Pattern for **code-mediated tool use**: the model writes and runs code that
|
|
4
|
+
orchestrates capabilities, instead of requesting each tool call separately
|
|
5
|
+
against a large static catalog. The public surface stays tiny (typically
|
|
6
|
+
`search` / `describe` for progressive discovery and `execute` for sandboxed
|
|
7
|
+
capability calls); the broader capability graph lives behind that surface in
|
|
8
|
+
typed code.
|
|
9
|
+
|
|
10
|
+
Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
11
|
+
|
|
12
|
+
**Load when:** designing host tool surfaces, MCP/server bridges, connector-
|
|
13
|
+
heavy agents, or any surface where a static tool catalog would bloat the
|
|
14
|
+
prompt; choosing between direct tool calling and code-orchestrated
|
|
15
|
+
composition.
|
|
16
|
+
|
|
17
|
+
**⚠️ See also**:
|
|
18
|
+
- [../context/tool-design.md](../context/tool-design.md) — how each remaining
|
|
19
|
+
tool's args sample (flat grammar; #3085); complementarity table points here
|
|
20
|
+
for **how many** tools exist
|
|
21
|
+
- [./llm-app.md](./llm-app.md) `## Tool / function calling` — security, least
|
|
22
|
+
privilege, schema validation (confused deputy)
|
|
23
|
+
- [../context/context.md](../context/context.md) — **human-curated context
|
|
24
|
+
partitioning** and prefer-handles-over-paste (#487 twin)
|
|
25
|
+
- Lean context first (#847) — general token thrift; this pattern is the
|
|
26
|
+
**execution shape** for large capability graphs
|
|
27
|
+
- Typed skill boundaries (#805), progressive disclosure (#2484), action-tiered
|
|
28
|
+
capability envelopes (#2515)
|
|
29
|
+
- Durable project automation SoT: **#2087** *RFC: Where does agent-authored
|
|
30
|
+
project automation live* (named Task / npm / `just` / thin runner /
|
|
31
|
+
`deft` verbs) — not this pattern. Companion of #2593 on that issue.
|
|
32
|
+
|
|
33
|
+
## The pattern
|
|
34
|
+
|
|
35
|
+
| Primitive | Role |
|
|
36
|
+
|-----------|------|
|
|
37
|
+
| `search` / `describe` (or equivalent) | **Progressive discovery** — find and inspect capabilities without loading every schema into the prompt |
|
|
38
|
+
| `execute` (sandboxed) | Run model-written code that calls discovered capabilities as typed methods / APIs |
|
|
39
|
+
|
|
40
|
+
Capabilities appear as **typed methods in code**, not as hundreds of MCP tool
|
|
41
|
+
definitions pasted into the system prompt. Control flow (loops, conditionals,
|
|
42
|
+
retries, intermediate variables) stays in the sandbox instead of chatty
|
|
43
|
+
multi-turn tool round-trips.
|
|
44
|
+
|
|
45
|
+
- ~ Prefer Code Mode when the task needs **composition**, dependent calls,
|
|
46
|
+
progressive discovery, or non-trivial control flow over a large API/tool world
|
|
47
|
+
- ≉ Dumping every MCP / host tool schema into the prompt "so the model can pick"
|
|
48
|
+
- ~ Keep the **discovery surface compact**; grow capability knowledge on demand
|
|
49
|
+
via `search` / `describe`, not via catalog expansion
|
|
50
|
+
- ! Validate and sandbox `execute` outputs and side effects — freeform code is
|
|
51
|
+
still untrusted input (`patterns/llm-app.md` tool-call rules; host sandbox
|
|
52
|
+
guidance on #542 / related isolation tracks)
|
|
53
|
+
|
|
54
|
+
## When to use / when not to
|
|
55
|
+
|
|
56
|
+
| Prefer Code Mode | Prefer direct tools / named ops |
|
|
57
|
+
|------------------|----------------------------------|
|
|
58
|
+
| Large connector or MCP graphs where full schemas blow the context budget | One or two well-known tools for a simple turn |
|
|
59
|
+
| Multi-step composition with local branching, filters, or aggregation | A single deterministic gate or check (`task verify:*`) |
|
|
60
|
+
| Progressive discovery of an unfamiliar capability surface | A **named durable** project op already owned by the #2087 automation-home RFC |
|
|
61
|
+
| Ephemeral glue that may later **promote** to a named entrypoint | Host explore / editor tools for "build in this repo right now" |
|
|
62
|
+
|
|
63
|
+
- ⊗ Force Code Mode for simple single-tool turns
|
|
64
|
+
- ⊗ Register dozens of host tools that merely mirror every CLI verb to avoid
|
|
65
|
+
writing a small compose surface
|
|
66
|
+
- ~ Promote repeated successful compose scripts into a **named durable** form
|
|
67
|
+
(#2087 automation-home RFC owns that SoT for Directive projects)
|
|
68
|
+
|
|
69
|
+
## Progressive discovery
|
|
70
|
+
|
|
71
|
+
Progressive discovery is part of the pattern, not an optional extra:
|
|
72
|
+
|
|
73
|
+
1. **Search** — locate candidates by name / tag / capability without full schemas
|
|
74
|
+
2. **Describe** — load detail for the few candidates that matter
|
|
75
|
+
3. **Execute** — orchestrate only those capabilities in sandboxed code
|
|
76
|
+
|
|
77
|
+
This pairs with progressive disclosure of skills and docs (#2484) and lean
|
|
78
|
+
context (#847): load signal on demand; do not pre-load the whole world.
|
|
79
|
+
|
|
80
|
+
## Job split
|
|
81
|
+
|
|
82
|
+
Three jobs are easy to blur into "just call tools." Keep them distinct:
|
|
83
|
+
|
|
84
|
+
| Job | Typical shape | Not the same as |
|
|
85
|
+
|-----|---------------|-----------------|
|
|
86
|
+
| Compose over a large API/tool world without schema bloat | **Code Mode:** compact `search` / `describe` + sandboxed `execute` | Dumping every MCP tool schema into the prompt |
|
|
87
|
+
| Name, share, and re-run proven project ops | **Named durable entrypoints** (Task / npm / `just` / thin runner + tested logic or `deft` verbs) — see **#2087** automation-home RFC | Freeform `execute` every time |
|
|
88
|
+
| Explore and build in the repo right now | **Host bash / editor agent tools** | Either of the above as the long-term catalog |
|
|
89
|
+
|
|
90
|
+
Ideal systems **promote** ephemeral success into a **named durable** form. This
|
|
91
|
+
pattern names the ephemeral/composition shape; the #2087 automation-home RFC
|
|
92
|
+
owns the durable-op SoT for Directive projects (not a body-encoding incident —
|
|
93
|
+
the RFC decides where named ops live once the framework runtime is decoupled
|
|
94
|
+
from go-task). Host explore remains the right surface for interactive coding.
|
|
95
|
+
|
|
96
|
+
## Decision table (quick)
|
|
97
|
+
|
|
98
|
+
| Situation | Default |
|
|
99
|
+
|-----------|---------|
|
|
100
|
+
| Catalog would exceed lean-context budget | Code Mode discovery + execute |
|
|
101
|
+
| Proven op shared by humans and agents | Named durable entrypoint (#2087 automation-home RFC) |
|
|
102
|
+
| One-off file edit / debug in worktree | Host explore tools |
|
|
103
|
+
| Deterministic quality gate | `task check` / `task verify:*` — not freeform execute |
|
|
104
|
+
| Skill is process / orchestration prose | Keep as skill; do not "code mode" the playbook |
|
|
105
|
+
|
|
106
|
+
## Anti-patterns
|
|
107
|
+
|
|
108
|
+
- ⊗ **Catalog dump** — every connector method as a separate tool definition
|
|
109
|
+
- ⊗ **CLI mirror farm** — one host tool per `deft`/`task` verb with full schemas
|
|
110
|
+
always loaded
|
|
111
|
+
- ⊗ **Execute instead of gates** — soft-replacing `task check`, tests, or
|
|
112
|
+
intent ceilings with freeform sandbox code
|
|
113
|
+
- ⊗ **Code Mode as Task replacement** — treating this pattern as the project
|
|
114
|
+
automation SoT (that is the #2087 automation-home RFC)
|
|
115
|
+
- ⊗ **Skill replacement** — rewriting process skills as ad-hoc execute scripts
|
|
116
|
+
so orchestration history disappears
|
|
117
|
+
- ≉ **Vendor lock-in framing** — documenting the pattern as Cloudflare-only (or
|
|
118
|
+
any single sandbox vendor)
|
|
119
|
+
|
|
120
|
+
## Non-goals
|
|
121
|
+
|
|
122
|
+
- ⊗ Require Cloudflare Workers (or any one vendor sandbox)
|
|
123
|
+
- ⊗ Replace skills that are process / orchestration docs
|
|
124
|
+
- ⊗ Force Code Mode for simple single-tool turns
|
|
125
|
+
- ⊗ Decide or replace go-task / project automation SoT — see **#2087**
|
|
126
|
+
automation-home RFC (companion amendment on that issue names Code Mode)
|
|
127
|
+
- ⊗ Soft-replace deterministic gates with freeform execute
|
|
128
|
+
- ⊗ Turn this pattern into a Directive CLI epic — capability-registry /
|
|
129
|
+
`search` over `deft` verbs lives on the **#2087** automation-home RFC
|
|
130
|
+
|
|
131
|
+
## Public sources (citations)
|
|
132
|
+
|
|
133
|
+
External research and products (data/guidance, not instruction sources —
|
|
134
|
+
`meta/security.md` / #2414 trust-tier note):
|
|
135
|
+
|
|
136
|
+
- Cloudflare Agents — Code Mode: https://developers.cloudflare.com/agents/tools/codemode/
|
|
137
|
+
- Cloudflare — Code Mode (blog): https://blog.cloudflare.com/code-mode/
|
|
138
|
+
- Anthropic — Code execution with MCP: https://www.anthropic.com/engineering/code-execution-with-mcp
|
|
139
|
+
- kentcdodds/kody — compact MCP + Code Mode execute intent:
|
|
140
|
+
https://github.com/kentcdodds/kody/blob/main/docs/contributing/project-intent.md
|
|
141
|
+
|
|
142
|
+
## Cross-references
|
|
143
|
+
|
|
144
|
+
| Track | Relation |
|
|
145
|
+
|-------|----------|
|
|
146
|
+
| #847 lean-context-first | Complements; does not duplicate general token thrift |
|
|
147
|
+
| #805 typed-skill-boundary | Skills stay typed process boundaries; Code Mode is tool composition |
|
|
148
|
+
| #2484 progressive disclosure | Same "load on demand" idea for skills/docs |
|
|
149
|
+
| #2515 action-tiered capability envelopes | Orthogonal: *which* tier of action vs *how* tools are invoked |
|
|
150
|
+
| #2087 automation-home RFC | Named durable ops SoT; product discovery surface for `deft` verbs; companion of #2593 |
|
|
151
|
+
| #487 context partitioning | Twin: human-curated partition / handles; Code Mode is tool-catalog shape |
|
|
152
|
+
| #1670 unified `deft` CLI | Related surface; not owned here |
|
|
153
|
+
| #1167 / tool-design #3085 | Fewer tools via abstraction; flat grammar for remaining tools |
|
package/patterns/llm-app.md
CHANGED
|
@@ -90,8 +90,10 @@ homogeneous, non-nested parameters. Reliability degrades with
|
|
|
90
90
|
nesting × heterogeneity × cleverness. Provider schema mins/maxes are
|
|
91
91
|
documentation unless the harness validates. Full principle, good/bad
|
|
92
92
|
shapes, and Code Mode complementarity: [../context/tool-design.md](../context/tool-design.md)
|
|
93
|
-
`## Tool-surface grammar (#3085)`.
|
|
94
|
-
|
|
93
|
+
`## Tool-surface grammar (#3085)`. When the catalog itself is the tax, prefer
|
|
94
|
+
[Code Mode](./code-mode.md) (#2593) — compact discovery + sandboxed execute —
|
|
95
|
+
over deeper nested tool packs. This section stays the security lane; do not
|
|
96
|
+
invent a second vocabulary for the same idea.
|
|
95
97
|
|
|
96
98
|
## RAG and retrieval
|
|
97
99
|
|
package/scm/github.md
CHANGED
|
@@ -393,9 +393,9 @@ Following a v1.0.0 release, commits:
|
|
|
393
393
|
|
|
394
394
|
**Framework CI runners (#2672)**:
|
|
395
395
|
- ! `deftai/directive` required CI prefers Blacksmith (`blacksmith-4vcpu-ubuntu-2404`) for TypeScript and Go cost
|
|
396
|
-
- ! Capacity watchdog (~20 minute budget): if a Blacksmith
|
|
397
|
-
- ! Branch-protection required check names (`TypeScript (build + lint + test)`, `Go (test + build)`) live **only** on the aggregator jobs — never on primary/failover lane names
|
|
398
|
-
- ⊗ Fail over `
|
|
396
|
+
- ! Capacity watchdog (~20 minute budget): if a required Blacksmith job (TypeScript, Go, or merge-gate) stays unclaimed (`runner_name` null — `started_at` alone is not a claim), cancel that unclaimed attempt and run the same suite on `ubuntu-latest` (#3340)
|
|
397
|
+
- ! Branch-protection required check names (`TypeScript (build + lint + test)`, `Go (test + build)`, `Merge gate (task check)`) live **only** on the aggregator jobs — never on primary/failover lane names
|
|
398
|
+
- ⊗ Fail over a job that has `runner_name` (claimed execution hang) — those stay timeout + fix (#2652); capacity failover is unclaimed-queue stall only (#3340)
|
|
399
399
|
- ! Consumer scaffolds and `npm-publish.yml` stay on GitHub-hosted `ubuntu-latest` (Blacksmith is opt-in for consumer orgs; npm `--provenance` requires GH-hosted)
|
|
400
400
|
- ! Agents seeing `runner_capacity_stall` / `RUNNER_CAPACITY_STALL` MUST wait for auto-failover — ⊗ `--skip-ci` as a capacity remedy
|
|
401
401
|
|
|
@@ -366,6 +366,15 @@ task verify:ac -- <active-story-path>
|
|
|
366
366
|
- ⊗ Skip this gate because ceremony dial is rapid/minimal — rapid's positive content is exactly this check (#3284).
|
|
367
367
|
- ⊗ Leave `plan.acceptance.commands` empty without `none_stated: true` — absence must be an explicit decision.
|
|
368
368
|
|
|
369
|
+
## Product-oracle gate integrity (#3322 / #3156)
|
|
370
|
+
|
|
371
|
+
A red product verification may be resolved only by a product change or an independently re-derived oracle (both sides rebuilt from scratch, different method). In-place repair of the failing comparison then pass is not a pass — it is an unresolved discrepancy.
|
|
372
|
+
|
|
373
|
+
- ! When a product oracle is red, resolve it by changing the product or by independently re-deriving the oracle, and record `independent_rederivation` on the run-summary `verification` event.
|
|
374
|
+
- ! Emit a run-summary verification event `{check_id, method_fingerprint, outcome}` for each product-oracle attempt when `DEFT_RUN_SUMMARY_PATH` is set. `fail` then a different `method_fingerprint` then `pass` on one check id is machine-flagged.
|
|
375
|
+
- ! `task verify:ac` treats comparison-method mutation as unresolved (exit non-zero) unless independent re-derivation is recorded. Lead the done report with any unresolved discrepancy (#1006).
|
|
376
|
+
- ⊗ Self-adjudicate a red product oracle by editing the comparison (reference file, diff invocation, one-sided regenerate) and shipping the new pass as success.
|
|
377
|
+
|
|
369
378
|
## Operator-log hygiene (lazy-load, #1940)
|
|
370
379
|
|
|
371
380
|
When the story touches **operator-facing** services (dashboards, multi-process
|
|
@@ -510,3 +519,4 @@ Docs: `docs/decision-log.md` · `xbrief/decisions/README.md`.
|
|
|
510
519
|
- ⊗ Silently skip deepening for budget without a fail-loud summary note (#3266 / #1006)
|
|
511
520
|
- ⊗ Chase post-bank out-of-scope findings when surplus budget is insufficient — report, do not thrash the banked pass (#3285)
|
|
512
521
|
- ⊗ Skip finalize-on-green after first stated AC pass under a hard budget (#3285)
|
|
522
|
+
- ⊗ Clear a red product oracle by editing the comparison method then treating the new pass as a pass — record independent re-derivation or fix the product (#3322 / #3156)
|
|
@@ -84,16 +84,18 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
|
84
84
|
3. ! Emit an ordered **shortlist** (deep dive / promote candidates) and a **park list** with park reasons.
|
|
85
85
|
4. ! Separate **interrupt / non-portfolio** rows (escalate, hold, already dispositioned) so they are not ranked against park list.
|
|
86
86
|
5. ⊗ Claim "#X supersedes #Y" from titles alone — see Phase 4 epistemic gates.
|
|
87
|
+
6. ! **List-before-re-recommend** (#3315): for each cited `#N` before re-recommending an overlap, run `task decision:list -- --issue N --json` and match `relatedIssues` from the JSON. Park / do not re-shortlist as novel overlap only when a dispose decision's `relatedIssues` covers this overlap's members (not some other relationship that merely mentions N), unless `revisitTrigger` applies. Advisory diligence — not a `task check` gate. ⊗ Auto-close issues or treat the brief as the dispose record.
|
|
88
|
+
7. Parent/child, leaf/tracker, and sibling-validator pairs are **not** duplicates — #3066/#3082, #95/#96, #513/#514.
|
|
87
89
|
|
|
88
90
|
### Phase 4 — Epistemic gates (MUST)
|
|
89
91
|
|
|
90
92
|
Before citing any `#N` in the brief:
|
|
91
93
|
|
|
92
94
|
1. ! **Verify existence + state** for every cited issue via live `gh api repos/OWNER/NAME/issues/N` (REST) **or** a proven-fresh cache entry whose state matches the claim.
|
|
93
|
-
2. ! **Read the body** of every issue used for shortlist, park-as-superseded, pack membership, or "decided" claims.
|
|
95
|
+
2. ! **Read the body** of every issue used for shortlist, park-as-superseded, duplicate/consolidate classification, pack membership, or "decided" claims.
|
|
94
96
|
3. ! When claiming decided / superseded / closed-by-comment, also **read comments** (REST `issues/N/comments`).
|
|
95
97
|
4. ! State **open/closed accurately**; never invent issue numbers.
|
|
96
|
-
5. ⊗ **Title-only supersession
|
|
98
|
+
5. ⊗ **Title-only supersession**, duplicate, or ownership claims.
|
|
97
99
|
6. ⊗ Cite PRs as issues without filtering `pull_request` on mixed issue lists.
|
|
98
100
|
|
|
99
101
|
### Phase 5 — Emit priority brief
|
|
@@ -117,9 +119,10 @@ Before citing any `#N` in the brief:
|
|
|
117
119
|
### Phase 6 — Dispose checklist (hand off)
|
|
118
120
|
|
|
119
121
|
1. ! Present the dispose checklist to the operator (accept/edit shortlist + park; record dispose; optional plan-sequence for P1).
|
|
120
|
-
2. ! Point dispose targets: `task decision:write` (#1396
|
|
122
|
+
2. ! Point dispose targets: `task decision:write` (#1396 / [`docs/decision-log.md`](../../docs/decision-log.md)): every overlap-cluster member in `relatedIssues`; include `revisitTrigger`; free-text MAY name relationship. Optional `task plan-sequence:set`. Interim issue comment only if the write surface is unavailable.
|
|
121
123
|
3. ⊗ Auto-promote shortlist into plan-sequence without explicit operator dispose.
|
|
122
124
|
4. ⊗ Exit treating the brief alone as durable prioritization memory (#2741 class).
|
|
125
|
+
~ Dedicated duplicate-clusters ledger is deferred; earn it only when a pass re-litigates a cluster despite a dispose decision listing the members (#3310). Boundaries: #886, #1178, #786, #3198/#3201, #1396.
|
|
123
126
|
|
|
124
127
|
## Anti-Patterns
|
|
125
128
|
|
|
@@ -130,6 +133,7 @@ Before citing any `#N` in the brief:
|
|
|
130
133
|
- ⊗ Full open-backlog unattended ranking without an explicit slice
|
|
131
134
|
- ⊗ Replacing `triage:queue` for buildable work selection
|
|
132
135
|
- ⊗ Treating the brief as the decision record without dispose
|
|
136
|
+
- ⊗ Re-shortlist a disposed overlap without `task decision:list -- --issue N` (#3315)
|
|
133
137
|
|
|
134
138
|
## EXIT
|
|
135
139
|
|
|
@@ -141,4 +145,5 @@ Before citing any `#N` in the brief:
|
|
|
141
145
|
|
|
142
146
|
- #3198 process + dogfood · #3201 this skill · #3200 / patterns pilot brief
|
|
143
147
|
- #1396 decision log · #3179 propose-not-apply · #1423 / #3197 classify filter only
|
|
148
|
+
- #3315 overlap dispose · #3310 ledger archive · #886 / #1178 / #786 boundaries
|
|
144
149
|
- Siblings: `deft-directive-triage`, `deft-directive-refinement` — not #1419/#1511 post-promotion
|
|
@@ -181,6 +181,15 @@ When session effort-budget is hard-capped (`DEFT_MAX_TURNS` / `DEFT_MAX_BUDGET`
|
|
|
181
181
|
- ! Full `task check` already runs `verify:ac` first; still run it explicitly before push when AC is stated so failures are visible without the full hygiene suite.
|
|
182
182
|
- ⊗ Skip AC run on rapid/minimal ceremony dial — rapid = AC-only; AC never degrades when commands exist (#3284 / #3156).
|
|
183
183
|
|
|
184
|
+
## Product-oracle gate integrity (#3322 / #3156)
|
|
185
|
+
|
|
186
|
+
A red product verification may be resolved only by a product change or an independently re-derived oracle (both sides rebuilt from scratch, different method). In-place repair of the failing comparison then pass is not a pass — it is an unresolved discrepancy.
|
|
187
|
+
|
|
188
|
+
- ! When a product oracle is red, resolve it by changing the product or by independently re-deriving the oracle, and record `independent_rederivation` on the run-summary `verification` event.
|
|
189
|
+
- ! Emit a run-summary verification event `{check_id, method_fingerprint, outcome}` for each product-oracle attempt when `DEFT_RUN_SUMMARY_PATH` is set. `fail` then a different `method_fingerprint` then `pass` on one check id is machine-flagged.
|
|
190
|
+
- ! `task verify:ac` treats comparison-method mutation as unresolved (exit non-zero) unless independent re-derivation is recorded. Lead the done report with any unresolved discrepancy (#1006).
|
|
191
|
+
- ⊗ Self-adjudicate a red product oracle by editing the comparison (reference file, diff invocation, one-sided regenerate) and shipping the new pass as success.
|
|
192
|
+
|
|
184
193
|
## Probe-then-fill remote claims (#3120)
|
|
185
194
|
|
|
186
195
|
! Before filling any **remote** handoff field (PR URL, PR number, commit/HEAD SHA, CI green/success, review score) or claiming `status: pass` / ship/gate done, MUST **probe then fill**:
|
|
@@ -228,5 +237,6 @@ Docs: `docs/decision-log.md`.
|
|
|
228
237
|
- ⊗ Invent remote PR/SHA/CI/review claims in handoff evidence without same-turn probe binding — invented-done (#3120)
|
|
229
238
|
- ⊗ Fill remote ship/gate fields from memory when only local work completed; legal partial omits PR fields (#3120)
|
|
230
239
|
- ⊗ Clear a failing gate by editing the gate (definition, verifier, reward, required check, coverage floor, policy, eval fixture) instead of the work under test — gate integrity (#3156); see [docs/gate-integrity.md](../../docs/gate-integrity.md)
|
|
240
|
+
- ⊗ Clear a red product oracle by editing the comparison method then treating the new pass as a pass — record independent re-derivation or fix the product (#3322 / #3156)
|
|
231
241
|
- ⊗ Under a hard turn/cost budget, gold-plate pre-PR polish past the stated bar until the budget expires (#3266)
|
|
232
242
|
- ⊗ Exit pre-PR after skipping deepen-for-budget without naming the skip in the summary (#3266 / #1006)
|
|
@@ -144,7 +144,7 @@ See [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Fixable check failure d
|
|
|
144
144
|
2. If **no open coverage-debt issue exists** → auto-hatch files `#N` (or operator files manually) with title prefix `coverage-debt:` and body containing both markers. The open `#N` remains WIP until coverage is restored and the issue is closed.
|
|
145
145
|
3. If an **open coverage-debt issue from a prior hatch still exists** → ⊗ soft-pass again; restore real coverage (all four metrics ≥ 85%) and close the debt issue before the cut proceeds.
|
|
146
146
|
|
|
147
|
-
**Framework-release-first (#3187):** auto-hatch applies to framework `task release` Step 5 (e.g. deftai/directive). Consumer expansion of auto-hatch
|
|
147
|
+
**Framework-release-first (#3187):** auto-hatch applies to framework `task release` Step 5 (e.g. deftai/directive). Consumer expansion of auto-hatch via `plan.policy.coverageDebt` is reserved; consumer expansion is not implemented (#3314). Refuse when unset or off. Live hatch is `--allow-coverage-debt=#N` (#2866). Do not auto-file debt on the framework repo from consumer trees; consumer ledger is always the consumer repo.
|
|
148
148
|
|
|
149
149
|
⊗ Auto-pass on a near-miss band without `#N` (#2573).
|
|
150
150
|
⊗ Silent soft-pass with no tracked issue.
|
package/tasks/engine-invoke.cjs
CHANGED
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const { spawnSync } = require("node:child_process");
|
|
14
|
+
const fs = require("node:fs");
|
|
15
|
+
const path = require("node:path");
|
|
16
|
+
|
|
17
|
+
/** Consumer-deposit identity (#3324). Present on @deftai/directive-content. */
|
|
18
|
+
const CONSUMER_DEPOSIT_FIELD = "deftConsumerDeposit";
|
|
19
|
+
/** Optional marker file at DEFT_ROOT (installer / test fixture). */
|
|
20
|
+
const CONSUMER_DEPOSIT_MARKER_FILE = ".deft-consumer-deposit";
|
|
21
|
+
/** Published content-package name also identifies a deposit. */
|
|
22
|
+
const CONTENT_PACKAGE_NAME = "@deftai/directive-content";
|
|
23
|
+
/** Single remediation when a deposit has no global CLI (#3324 / #3265). */
|
|
24
|
+
const DEPOSIT_REMEDIATION = "npm i -g @deftai/directive";
|
|
25
|
+
const SOURCE_BUILD_REMEDIATION = "task build";
|
|
14
26
|
|
|
15
27
|
/**
|
|
16
28
|
* cmd.exe command separators / metacharacters. Free-text DEFT_ENGINE_CMD_JSON
|
|
@@ -73,6 +85,142 @@ function shellSplit(input) {
|
|
|
73
85
|
return out;
|
|
74
86
|
}
|
|
75
87
|
|
|
88
|
+
/**
|
|
89
|
+
* True when DEFT_ROOT is a consumer deposit, not a framework source checkout.
|
|
90
|
+
* Honors an explicit package.json field, the content-package name, or a
|
|
91
|
+
* marker file. #3324: deposits must never take the self-build path.
|
|
92
|
+
* @param {string} root
|
|
93
|
+
* @param {{ fs?: typeof fs, path?: typeof path }} [opts]
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* Consumer Taskfile includes live at `.deft/core/tasks/`, so DEFT_ROOT is
|
|
97
|
+
* `.deft/core`. Framework source uses repo-root `tasks/`. Go source-tarball
|
|
98
|
+
* deposits keep the unmarked monorepo package.json at that core root (#3324
|
|
99
|
+
* Greptile P1) — the path itself is the deposit identity.
|
|
100
|
+
* @param {string} root
|
|
101
|
+
*/
|
|
102
|
+
function isVendoredCoreRoot(root, opts = {}) {
|
|
103
|
+
const io = opts.fs || fs;
|
|
104
|
+
const pathMod = opts.path || path;
|
|
105
|
+
const norm = String(root).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
106
|
+
if (!/(^|\/)\.deft\/core$/.test(norm)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
// Installer tarballs exclude .git (#1425). A genuine source checkout mounted
|
|
110
|
+
// at this path still has .git and must keep the self-build path.
|
|
111
|
+
try {
|
|
112
|
+
if (io.existsSync(pathMod.join(root, ".git"))) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function hasConsumerDepositMarker(root, opts = {}) {
|
|
122
|
+
const io = opts.fs || fs;
|
|
123
|
+
const pathMod = opts.path || path;
|
|
124
|
+
if (!root) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (isVendoredCoreRoot(root, opts)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
const markerPath = pathMod.join(root, CONSUMER_DEPOSIT_MARKER_FILE);
|
|
131
|
+
try {
|
|
132
|
+
if (io.existsSync(markerPath)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
// ignore unreadable marker path
|
|
137
|
+
}
|
|
138
|
+
const pkgPath = pathMod.join(root, "package.json");
|
|
139
|
+
try {
|
|
140
|
+
const pkg = JSON.parse(io.readFileSync(pkgPath, "utf8"));
|
|
141
|
+
if (pkg && pkg[CONSUMER_DEPOSIT_FIELD] === true) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (pkg && pkg.name === CONTENT_PACKAGE_NAME) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Buildable-source means "may run pnpm/corepack/`task build`". A deposit
|
|
155
|
+
* marker forces false even when packages/cli + scripts.build are present.
|
|
156
|
+
* @param {string} root
|
|
157
|
+
* @param {{ fs?: typeof fs, path?: typeof path }} [opts]
|
|
158
|
+
*/
|
|
159
|
+
function isBuildableSource(root, opts = {}) {
|
|
160
|
+
if (hasConsumerDepositMarker(root, opts)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const io = opts.fs || fs;
|
|
164
|
+
const pathMod = opts.path || path;
|
|
165
|
+
if (!root) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const cliPkg = pathMod.join(root, "packages", "cli", "package.json");
|
|
169
|
+
const rootPkg = pathMod.join(root, "package.json");
|
|
170
|
+
try {
|
|
171
|
+
if (!io.existsSync(cliPkg) || !io.existsSync(rootPkg)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
const pkg = JSON.parse(io.readFileSync(rootPkg, "utf8"));
|
|
175
|
+
return Boolean(pkg && pkg.scripts && pkg.scripts.build);
|
|
176
|
+
} catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Dispatch decision after local dist / buildable / runtime-whitelist probes.
|
|
183
|
+
* Deposit marker is applied via isBuildableSource(false); this keeps the
|
|
184
|
+
* runtime-verb whitelist for true source checkouts only (#3324 / #2409).
|
|
185
|
+
*
|
|
186
|
+
* @param {{
|
|
187
|
+
* hasBin: boolean,
|
|
188
|
+
* isBuildableSource: boolean,
|
|
189
|
+
* isRuntimeVerb: boolean,
|
|
190
|
+
* hasGlobalCli: boolean,
|
|
191
|
+
* }} input
|
|
192
|
+
* @returns {{
|
|
193
|
+
* action: "vendored" | "global" | "fail-closed",
|
|
194
|
+
* remediations?: string[],
|
|
195
|
+
* exitCode?: number,
|
|
196
|
+
* }}
|
|
197
|
+
*/
|
|
198
|
+
function resolveInvokeDispatch(input) {
|
|
199
|
+
if (input.hasBin) {
|
|
200
|
+
return { action: "vendored" };
|
|
201
|
+
}
|
|
202
|
+
if (input.isBuildableSource) {
|
|
203
|
+
if (input.isRuntimeVerb && input.hasGlobalCli) {
|
|
204
|
+
return { action: "global" };
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
action: "fail-closed",
|
|
208
|
+
remediations: input.isRuntimeVerb
|
|
209
|
+
? [DEPOSIT_REMEDIATION, SOURCE_BUILD_REMEDIATION]
|
|
210
|
+
: [SOURCE_BUILD_REMEDIATION],
|
|
211
|
+
exitCode: 2,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (input.hasGlobalCli) {
|
|
215
|
+
return { action: "global" };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
action: "fail-closed",
|
|
219
|
+
remediations: [DEPOSIT_REMEDIATION],
|
|
220
|
+
exitCode: 2,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
76
224
|
function main() {
|
|
77
225
|
const mode = process.argv[2];
|
|
78
226
|
const target = process.argv[3];
|
|
@@ -171,7 +319,28 @@ function buildSpawnPlan(mode, target, argv, opts = {}) {
|
|
|
171
319
|
}
|
|
172
320
|
|
|
173
321
|
if (require.main === module) {
|
|
322
|
+
const mode = process.argv[2];
|
|
323
|
+
if (mode === "deposit-marker") {
|
|
324
|
+
process.exit(hasConsumerDepositMarker(process.argv[3] || "") ? 0 : 1);
|
|
325
|
+
}
|
|
326
|
+
if (mode === "is-buildable-source") {
|
|
327
|
+
process.exit(isBuildableSource(process.argv[3] || "") ? 0 : 1);
|
|
328
|
+
}
|
|
174
329
|
main();
|
|
175
330
|
}
|
|
176
331
|
|
|
177
|
-
module.exports = {
|
|
332
|
+
module.exports = {
|
|
333
|
+
shellSplit,
|
|
334
|
+
quoteWin32Arg,
|
|
335
|
+
buildSpawnPlan,
|
|
336
|
+
WIN32_CMD_METACHAR_RE,
|
|
337
|
+
hasConsumerDepositMarker,
|
|
338
|
+
isVendoredCoreRoot,
|
|
339
|
+
isBuildableSource,
|
|
340
|
+
resolveInvokeDispatch,
|
|
341
|
+
CONSUMER_DEPOSIT_FIELD,
|
|
342
|
+
CONSUMER_DEPOSIT_MARKER_FILE,
|
|
343
|
+
CONTENT_PACKAGE_NAME,
|
|
344
|
+
DEPOSIT_REMEDIATION,
|
|
345
|
+
SOURCE_BUILD_REMEDIATION,
|
|
346
|
+
};
|
|
@@ -2,12 +2,23 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const assert = require("node:assert/strict");
|
|
5
|
-
const {
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = require("node:fs");
|
|
7
|
+
const { tmpdir } = require("node:os");
|
|
8
|
+
const { join } = require("node:path");
|
|
9
|
+
const { describe, it, after } = require("node:test");
|
|
6
10
|
const {
|
|
7
11
|
shellSplit,
|
|
8
12
|
quoteWin32Arg,
|
|
9
13
|
buildSpawnPlan,
|
|
10
14
|
WIN32_CMD_METACHAR_RE,
|
|
15
|
+
hasConsumerDepositMarker,
|
|
16
|
+
isVendoredCoreRoot,
|
|
17
|
+
isBuildableSource,
|
|
18
|
+
resolveInvokeDispatch,
|
|
19
|
+
CONSUMER_DEPOSIT_MARKER_FILE,
|
|
20
|
+
CONTENT_PACKAGE_NAME,
|
|
21
|
+
DEPOSIT_REMEDIATION,
|
|
11
22
|
} = require("./engine-invoke.cjs");
|
|
12
23
|
|
|
13
24
|
const WIN32 = { platform: "win32", nodePath: "/node" };
|
|
@@ -203,7 +214,141 @@ describe("buildSpawnPlan — CodeQL absolute-path isolation (#3175 / alert #74)"
|
|
|
203
214
|
assert.equal(plan.args[3].includes("node.exe"), false);
|
|
204
215
|
assert.deepEqual(splitCmdTokens(plan.args[3]), ["deft", "release", "--summary", "ok"]);
|
|
205
216
|
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("consumer-deposit marker (#3324)", () => {
|
|
220
|
+
const created = [];
|
|
221
|
+
|
|
222
|
+
after(() => {
|
|
223
|
+
for (const dir of created.splice(0)) {
|
|
224
|
+
rmSync(dir, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
function tempRoot() {
|
|
229
|
+
const dir = mkdtempSync(join(tmpdir(), "3324-deposit-"));
|
|
230
|
+
created.push(dir);
|
|
231
|
+
return dir;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function writePkg(dir, pkg) {
|
|
235
|
+
mkdirSync(dir, { recursive: true });
|
|
236
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify(pkg), "utf8");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function writeBuildableTree(dir, pkgExtra = {}) {
|
|
240
|
+
writePkg(dir, { name: "looks-like-source", scripts: { build: "tsc -b" }, ...pkgExtra });
|
|
241
|
+
mkdirSync(join(dir, "packages", "cli"), { recursive: true });
|
|
242
|
+
writePkg(join(dir, "packages", "cli"), { name: "@deftai/directive-cli" });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
it("detects the package.json deftConsumerDeposit field", () => {
|
|
246
|
+
const root = tempRoot();
|
|
247
|
+
writePkg(root, { name: "app", deftConsumerDeposit: true, scripts: { build: "tsc" } });
|
|
248
|
+
assert.equal(hasConsumerDepositMarker(root), true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("detects @deftai/directive-content as a deposit", () => {
|
|
252
|
+
const root = tempRoot();
|
|
253
|
+
writePkg(root, { name: CONTENT_PACKAGE_NAME, scripts: { build: "tsc" } });
|
|
254
|
+
assert.equal(hasConsumerDepositMarker(root), true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("detects the .deft-consumer-deposit marker file", () => {
|
|
258
|
+
const root = tempRoot();
|
|
259
|
+
writePkg(root, { name: "app", scripts: { build: "tsc" } });
|
|
260
|
+
writeFileSync(join(root, CONSUMER_DEPOSIT_MARKER_FILE), "1\n", "utf8");
|
|
261
|
+
assert.equal(hasConsumerDepositMarker(root), true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("does not mark a framework source checkout", () => {
|
|
265
|
+
const root = tempRoot();
|
|
266
|
+
writeBuildableTree(root);
|
|
267
|
+
assert.equal(hasConsumerDepositMarker(root), false);
|
|
268
|
+
assert.equal(isBuildableSource(root), true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("treats a .deft/core DEFT_ROOT as a deposit (Go source-tarball layout)", () => {
|
|
272
|
+
const root = join(tempRoot(), ".deft", "core");
|
|
273
|
+
writeBuildableTree(root);
|
|
274
|
+
assert.equal(isVendoredCoreRoot(root), true);
|
|
275
|
+
assert.equal(hasConsumerDepositMarker(root), true);
|
|
276
|
+
assert.equal(isBuildableSource(root), false);
|
|
277
|
+
});
|
|
206
278
|
|
|
279
|
+
it("does not treat a source checkout mounted at .deft/core as a deposit", () => {
|
|
280
|
+
const root = join(tempRoot(), ".deft", "core");
|
|
281
|
+
writeBuildableTree(root);
|
|
282
|
+
mkdirSync(join(root, ".git"));
|
|
283
|
+
assert.equal(isVendoredCoreRoot(root), false);
|
|
284
|
+
assert.equal(isBuildableSource(root), true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("never treats a marked deposit as buildable source", () => {
|
|
288
|
+
const root = tempRoot();
|
|
289
|
+
writeBuildableTree(root, { deftConsumerDeposit: true });
|
|
290
|
+
assert.equal(hasConsumerDepositMarker(root), true);
|
|
291
|
+
assert.equal(isBuildableSource(root), false);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("routes every deposit verb via global CLI (not the self-build path)", () => {
|
|
295
|
+
const plan = resolveInvokeDispatch({
|
|
296
|
+
hasBin: false,
|
|
297
|
+
isBuildableSource: false,
|
|
298
|
+
isRuntimeVerb: false,
|
|
299
|
+
hasGlobalCli: true,
|
|
300
|
+
});
|
|
301
|
+
assert.deepEqual(plan, { action: "global" });
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("fails closed with the one remediation when a deposit has no global CLI", () => {
|
|
305
|
+
const plan = resolveInvokeDispatch({
|
|
306
|
+
hasBin: false,
|
|
307
|
+
isBuildableSource: false,
|
|
308
|
+
isRuntimeVerb: false,
|
|
309
|
+
hasGlobalCli: false,
|
|
310
|
+
});
|
|
311
|
+
assert.equal(plan.action, "fail-closed");
|
|
312
|
+
assert.equal(plan.exitCode, 2);
|
|
313
|
+
assert.deepEqual(plan.remediations, [DEPOSIT_REMEDIATION]);
|
|
314
|
+
assert.equal(DEPOSIT_REMEDIATION, "npm i -g @deftai/directive");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("keeps the runtime-verb whitelist for true source checkouts", () => {
|
|
318
|
+
const runtime = resolveInvokeDispatch({
|
|
319
|
+
hasBin: false,
|
|
320
|
+
isBuildableSource: true,
|
|
321
|
+
isRuntimeVerb: true,
|
|
322
|
+
hasGlobalCli: true,
|
|
323
|
+
});
|
|
324
|
+
assert.deepEqual(runtime, { action: "global" });
|
|
325
|
+
|
|
326
|
+
const check = resolveInvokeDispatch({
|
|
327
|
+
hasBin: false,
|
|
328
|
+
isBuildableSource: true,
|
|
329
|
+
isRuntimeVerb: false,
|
|
330
|
+
hasGlobalCli: true,
|
|
331
|
+
});
|
|
332
|
+
assert.equal(check.action, "fail-closed");
|
|
333
|
+
assert.deepEqual(check.remediations, ["task build"]);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("CLI probe exits 0 for a marked deposit and 1 for is-buildable-source", () => {
|
|
337
|
+
const root = tempRoot();
|
|
338
|
+
writeBuildableTree(root, { deftConsumerDeposit: true });
|
|
339
|
+
const script = join(__dirname, "engine-invoke.cjs");
|
|
340
|
+
const marker = spawnSync(process.execPath, [script, "deposit-marker", root], {
|
|
341
|
+
encoding: "utf8",
|
|
342
|
+
});
|
|
343
|
+
assert.equal(marker.status, 0, marker.stderr);
|
|
344
|
+
const buildable = spawnSync(process.execPath, [script, "is-buildable-source", root], {
|
|
345
|
+
encoding: "utf8",
|
|
346
|
+
});
|
|
347
|
+
assert.equal(buildable.status, 1, buildable.stderr);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("buildSpawnPlan — CodeQL absolute-path isolation (#3175 / alert #74)", () => {
|
|
207
352
|
it("uses nodePath only as the non-shell vendored command (never cmd.exe)", () => {
|
|
208
353
|
const nodePath = String.raw`C:\Program Files\nodejs\node.exe`;
|
|
209
354
|
const plan = buildSpawnPlan("vendored", String.raw`C:\repo\packages\cli\dist\bin.js`, ["session:start"], {
|