@deftai/directive-content 0.81.0 → 0.83.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/Taskfile.yml +3 -0
- package/coding/coding.md +1 -1
- package/coding/security.md +16 -1
- package/commands.md +3 -1
- package/docs/product-signal.md +6 -2
- package/meta/security.md +2 -1
- package/package.json +1 -1
- package/packs/rules/rules-pack-0.1.json +2 -2
- package/packs/skills/skills-pack-0.1.json +17 -17
- package/patterns/agent-skill-supply-chain.md +117 -0
- package/patterns/llm-app.md +3 -0
- package/scm/github.md +37 -0
- package/skills/deft-directive-article-review/SKILL.md +9 -0
- package/skills/deft-directive-debug/SKILL.md +9 -0
- package/skills/deft-directive-product-signal/SKILL.md +3 -1
- package/skills/deft-directive-swarm/SKILL.md +3 -1
- package/tasks/engine-pm-run.cjs +269 -0
- package/tasks/engine-pm-run.test.cjs +201 -0
- package/tasks/engine.yml +2 -135
- package/tasks/scm.yml +10 -0
- package/tasks/verify.yml +20 -0
- package/templates/agent-prompt-preamble.md +5 -0
- package/templates/agents-entry.md +2 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const assert = require("node:assert/strict");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const { describe, it } = require("node:test");
|
|
9
|
+
const {
|
|
10
|
+
buildDispatchSteps,
|
|
11
|
+
executeAllowlisted,
|
|
12
|
+
hasCmd,
|
|
13
|
+
parsePnpmPin,
|
|
14
|
+
runPackageScript,
|
|
15
|
+
validateScriptName,
|
|
16
|
+
} = require("./engine-pm-run.cjs");
|
|
17
|
+
|
|
18
|
+
describe("parsePnpmPin", () => {
|
|
19
|
+
it("accepts stable, prerelease, and build-metadata pins", () => {
|
|
20
|
+
for (const pin of ["pnpm@11.8.0", "pnpm@1.0.0-alpha.1", "pnpm@1.0.0+build.1"]) {
|
|
21
|
+
const result = parsePnpmPin(pin);
|
|
22
|
+
assert.equal(result.ok, true);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("accepts missing/empty pins for unpinned fallback", () => {
|
|
27
|
+
assert.deepEqual(parsePnpmPin(undefined), { ok: true, semver: null, pin: null });
|
|
28
|
+
assert.deepEqual(parsePnpmPin(""), { ok: true, semver: null, pin: null });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("rejects shell metacharacters and malformed pins before spawn", () => {
|
|
32
|
+
for (const pin of [
|
|
33
|
+
"pnpm@9.0.0; echo pwned",
|
|
34
|
+
"pnpm@9.0.0 & echo pwned",
|
|
35
|
+
"pnpm@^1.0.0",
|
|
36
|
+
"npm@1.0.0",
|
|
37
|
+
"pnpm@1.0.0 extra",
|
|
38
|
+
" pnpm@1.0.0",
|
|
39
|
+
"pnpm@1.0.0 ",
|
|
40
|
+
"pnpm@1.0.0\n",
|
|
41
|
+
]) {
|
|
42
|
+
const result = parsePnpmPin(pin);
|
|
43
|
+
assert.equal(result.ok, false, pin);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("validateScriptName", () => {
|
|
49
|
+
it("accepts only declared script keys with safe names", () => {
|
|
50
|
+
const scripts = { build: "tsc", "test:unit": "vitest" };
|
|
51
|
+
assert.equal(validateScriptName("build", scripts), true);
|
|
52
|
+
assert.equal(validateScriptName("test:unit", scripts), true);
|
|
53
|
+
assert.equal(validateScriptName("missing", scripts), false);
|
|
54
|
+
assert.equal(validateScriptName("build;rm", scripts), false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("buildDispatchSteps", () => {
|
|
59
|
+
it("preserves installed pnpm -> pinned corepack -> unpinned corepack order", () => {
|
|
60
|
+
const steps = buildDispatchSteps({
|
|
61
|
+
hasPnpm: true,
|
|
62
|
+
hasCorepack: true,
|
|
63
|
+
semver: "11.8.0",
|
|
64
|
+
script: "build",
|
|
65
|
+
});
|
|
66
|
+
assert.deepEqual(steps, [
|
|
67
|
+
{ cmd: "pnpm", args: ["run", "build"] },
|
|
68
|
+
{ cmd: "corepack", args: ["pnpm@11.8.0", "run", "build"] },
|
|
69
|
+
{ cmd: "corepack", args: ["pnpm", "run", "build"] },
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("runPackageScript security", () => {
|
|
75
|
+
function mkFixture(/** @type {Record<string, unknown>} */ pkgExtra) {
|
|
76
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "deft-engine-pm-run-"));
|
|
77
|
+
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(pkgExtra, null, 2), "utf8");
|
|
78
|
+
return dir;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
it("rejects malicious pins before any execution call", () => {
|
|
82
|
+
const dir = mkFixture({
|
|
83
|
+
packageManager: "pnpm@9.0.0; echo pwned",
|
|
84
|
+
scripts: { build: "node -e \"\"" },
|
|
85
|
+
});
|
|
86
|
+
const sentinel = path.join(dir, "sentinel.txt");
|
|
87
|
+
let execCalls = 0;
|
|
88
|
+
const code = runPackageScript(dir, "build", {
|
|
89
|
+
execFileSync() {
|
|
90
|
+
execCalls += 1;
|
|
91
|
+
fs.writeFileSync(sentinel, "pwned", "utf8");
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
assert.equal(code, 3);
|
|
95
|
+
assert.equal(execCalls, 0);
|
|
96
|
+
assert.equal(fs.existsSync(sentinel), false);
|
|
97
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("records exact argv on execution without shell:true", () => {
|
|
101
|
+
const dir = mkFixture({
|
|
102
|
+
packageManager: "pnpm@11.8.0",
|
|
103
|
+
scripts: { build: "tsc" },
|
|
104
|
+
});
|
|
105
|
+
/** @type {Array<{ cmd: string, args: string[], shell?: boolean, stdio?: string }>} */
|
|
106
|
+
const calls = [];
|
|
107
|
+
const code = runPackageScript(dir, "build", {
|
|
108
|
+
execFileSync(cmd, args, opts) {
|
|
109
|
+
calls.push({ cmd, args: [...args], shell: opts?.shell, stdio: opts?.stdio });
|
|
110
|
+
if (opts?.stdio === "inherit") {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
assert.equal(code, 0);
|
|
116
|
+
const execCalls = calls.filter((c) => c.stdio === "inherit");
|
|
117
|
+
assert.equal(execCalls.length, 1);
|
|
118
|
+
assert.equal(execCalls[0].shell, false);
|
|
119
|
+
assert.equal(execCalls[0].stdio, "inherit");
|
|
120
|
+
if (process.platform === "win32") {
|
|
121
|
+
assert.equal(execCalls[0].cmd, "cmd.exe");
|
|
122
|
+
assert.deepEqual(execCalls[0].args.slice(0, 3), ["/d", "/s", "/c"]);
|
|
123
|
+
assert.match(execCalls[0].args[3], /pnpm.*run.*build/);
|
|
124
|
+
} else {
|
|
125
|
+
assert.deepEqual(execCalls[0], {
|
|
126
|
+
cmd: "pnpm",
|
|
127
|
+
args: ["run", "build"],
|
|
128
|
+
shell: false,
|
|
129
|
+
stdio: "inherit",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("allows probe hasCmd to use shell:true separately from execution", () => {
|
|
136
|
+
/** @type {Array<{ shell?: boolean, stdio?: string }>} */
|
|
137
|
+
const calls = [];
|
|
138
|
+
let probeAttempts = 0;
|
|
139
|
+
const execFn = (/** @type {string} */ _cmd, /** @type {string[]} */ _args, /** @type {{ shell?: boolean, stdio?: string }} */ opts) => {
|
|
140
|
+
if (opts?.stdio === "ignore") {
|
|
141
|
+
probeAttempts += 1;
|
|
142
|
+
if (probeAttempts === 1 && !opts.shell) {
|
|
143
|
+
throw new Error("probe without shell failed");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
calls.push({ shell: opts?.shell, stdio: opts?.stdio });
|
|
147
|
+
};
|
|
148
|
+
assert.equal(hasCmd(execFn, "pnpm"), true);
|
|
149
|
+
assert.ok(calls.some((c) => c.shell === true && c.stdio === "ignore"));
|
|
150
|
+
const execCalls = calls.filter((c) => c.stdio === "inherit");
|
|
151
|
+
assert.equal(execCalls.length, 0);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("rejects invalid package.json before probing PATH", () => {
|
|
155
|
+
const dir = mkFixture({ packageManager: "pnpm@11.8.0", scripts: { build: "tsc" } });
|
|
156
|
+
fs.writeFileSync(path.join(dir, "package.json"), "null", "utf8");
|
|
157
|
+
let execCalls = 0;
|
|
158
|
+
const code = runPackageScript(dir, "build", {
|
|
159
|
+
execFileSync() {
|
|
160
|
+
execCalls += 1;
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
assert.equal(code, 2);
|
|
164
|
+
assert.equal(execCalls, 0);
|
|
165
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("rejects unknown scripts before probing PATH", () => {
|
|
169
|
+
const dir = mkFixture({ packageManager: "pnpm@11.8.0", scripts: { build: "tsc" } });
|
|
170
|
+
let execCalls = 0;
|
|
171
|
+
const code = runPackageScript(dir, "lint", {
|
|
172
|
+
execFileSync() {
|
|
173
|
+
execCalls += 1;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
assert.equal(code, 2);
|
|
177
|
+
assert.equal(execCalls, 0);
|
|
178
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("uses cmd.exe on win32 without shell:true on the Node spawn", () => {
|
|
182
|
+
if (process.platform !== "win32") {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
/** @type {{ cmd?: string, args?: string[], shell?: boolean } | null} */
|
|
186
|
+
let recorded = null;
|
|
187
|
+
executeAllowlisted(
|
|
188
|
+
(cmd, args, opts) => {
|
|
189
|
+
recorded = { cmd, args: [...args], shell: opts?.shell };
|
|
190
|
+
},
|
|
191
|
+
"pnpm",
|
|
192
|
+
["run", "build"],
|
|
193
|
+
{ cwd: process.cwd() },
|
|
194
|
+
);
|
|
195
|
+
assert.ok(recorded);
|
|
196
|
+
assert.equal(recorded.cmd, "cmd.exe");
|
|
197
|
+
assert.equal(recorded.args[0], "/d");
|
|
198
|
+
assert.equal(recorded.shell, false);
|
|
199
|
+
assert.match(recorded.args[3], /^pnpm run build$|^"pnpm" "run" "build"$/);
|
|
200
|
+
});
|
|
201
|
+
});
|
package/tasks/engine.yml
CHANGED
|
@@ -20,71 +20,7 @@ tasks:
|
|
|
20
20
|
cmds:
|
|
21
21
|
- |
|
|
22
22
|
set -eu
|
|
23
|
-
node -
|
|
24
|
-
const {execFileSync}=require('child_process');
|
|
25
|
-
const fs=require('fs');
|
|
26
|
-
const root=process.argv[1];
|
|
27
|
-
const script=process.argv[2];
|
|
28
|
-
const pkgPath=root+'/package.json';
|
|
29
|
-
if(!fs.existsSync(pkgPath)){
|
|
30
|
-
console.error('deft: package.json missing at '+root);
|
|
31
|
-
process.exit(2);
|
|
32
|
-
}
|
|
33
|
-
const pkg=JSON.parse(fs.readFileSync(pkgPath,'utf8'));
|
|
34
|
-
// Cross-platform probe (#2415): do NOT use Unix `sh -c 'command -v'` —
|
|
35
|
-
// Windows native Task often has no `sh` on PATH, so Corepack.cmd was
|
|
36
|
-
// invisible after #2411. Prefer a direct spawn (POSIX / real binaries);
|
|
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});
|
|
41
|
-
const hasCmd=(name)=>{
|
|
42
|
-
try{
|
|
43
|
-
execFileSync(name,['--version'],spawnOpts());
|
|
44
|
-
return true;
|
|
45
|
-
}catch{
|
|
46
|
-
try{
|
|
47
|
-
execFileSync(name,['--version'],spawnOpts({shell:true}));
|
|
48
|
-
return true;
|
|
49
|
-
}catch{
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const run=(cmd,args)=>{
|
|
55
|
-
execFileSync(cmd,args,{cwd:root,stdio:'inherit',shell:true,windowsHide:true});
|
|
56
|
-
};
|
|
57
|
-
const trySteps=(steps)=>{
|
|
58
|
-
for(const [cmd,args] of steps){
|
|
59
|
-
try{
|
|
60
|
-
run(cmd,args);
|
|
61
|
-
process.exit(0);
|
|
62
|
-
}catch{
|
|
63
|
-
// fall through to Corepack / next resolver
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const envPm=String(process.env.DEFT_PACKAGE_MANAGER||'').trim().toLowerCase();
|
|
68
|
-
if(envPm==='npm'){
|
|
69
|
-
run('npm',['run',script]);
|
|
70
|
-
process.exit(0);
|
|
71
|
-
}
|
|
72
|
-
const pin=String(pkg.packageManager||'').trim();
|
|
73
|
-
const match=pin.match(/^pnpm@(.+)$/);
|
|
74
|
-
const steps=[];
|
|
75
|
-
if(hasCmd('pnpm')) steps.push(['pnpm',['run',script]]);
|
|
76
|
-
if(hasCmd('corepack')&&match) steps.push(['corepack',['pnpm@'+match[1],'run',script]]);
|
|
77
|
-
if(hasCmd('corepack')) steps.push(['corepack',['pnpm','run',script]]);
|
|
78
|
-
trySteps(steps);
|
|
79
|
-
console.error('deft: neither pnpm nor corepack is available to run \"'+script+'\".');
|
|
80
|
-
if(pin){
|
|
81
|
-
console.error(' Enable Corepack for the pinned manager: corepack enable && corepack prepare '+pin+' --activate');
|
|
82
|
-
}else{
|
|
83
|
-
console.error(' Install pnpm or enable Corepack (see package.json#packageManager).');
|
|
84
|
-
}
|
|
85
|
-
console.error(' Or set DEFT_PACKAGE_MANAGER=npm for an explicit npm build path.');
|
|
86
|
-
process.exit(127);
|
|
87
|
-
" "{{.DEFT_ROOT}}" "{{.PM_SCRIPT}}"
|
|
23
|
+
node "{{.TASKFILE_DIR}}/engine-pm-run.cjs" "{{.DEFT_ROOT}}" "{{.PM_SCRIPT}}"
|
|
88
24
|
|
|
89
25
|
_ts-build:
|
|
90
26
|
internal: true
|
|
@@ -115,76 +51,7 @@ tasks:
|
|
|
115
51
|
if node "{{.TASKFILE_DIR}}/ts-build-fresh.cjs" "{{.DEFT_ROOT}}"; then
|
|
116
52
|
exit 0
|
|
117
53
|
fi
|
|
118
|
-
node -
|
|
119
|
-
const {execFileSync}=require('child_process');
|
|
120
|
-
const fs=require('fs');
|
|
121
|
-
const root=process.argv[1];
|
|
122
|
-
const script='build';
|
|
123
|
-
const pkgPath=root+'/package.json';
|
|
124
|
-
const pkg=JSON.parse(fs.readFileSync(pkgPath,'utf8'));
|
|
125
|
-
// Cross-platform probe (#2415): do NOT use Unix `sh -c 'command -v'` —
|
|
126
|
-
// Windows native Task often has no `sh` on PATH, so Corepack.cmd was
|
|
127
|
-
// invisible after #2411. Prefer a direct spawn (POSIX / real binaries);
|
|
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});
|
|
132
|
-
const hasCmd=(name)=>{
|
|
133
|
-
try{
|
|
134
|
-
execFileSync(name,['--version'],spawnOpts());
|
|
135
|
-
return true;
|
|
136
|
-
}catch{
|
|
137
|
-
try{
|
|
138
|
-
execFileSync(name,['--version'],spawnOpts({shell:true}));
|
|
139
|
-
return true;
|
|
140
|
-
}catch{
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
const run=(cmd,args)=>{
|
|
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{}
|
|
154
|
-
};
|
|
155
|
-
const trySteps=(steps)=>{
|
|
156
|
-
for(const [cmd,args] of steps){
|
|
157
|
-
try{
|
|
158
|
-
run(cmd,args);
|
|
159
|
-
markWarm();
|
|
160
|
-
process.exit(0);
|
|
161
|
-
}catch{
|
|
162
|
-
// fall through to Corepack / next resolver
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
const envPm=String(process.env.DEFT_PACKAGE_MANAGER||'').trim().toLowerCase();
|
|
167
|
-
if(envPm==='npm'){
|
|
168
|
-
run('npm',['run',script]);
|
|
169
|
-
markWarm();
|
|
170
|
-
process.exit(0);
|
|
171
|
-
}
|
|
172
|
-
const pin=String(pkg.packageManager||'').trim();
|
|
173
|
-
const match=pin.match(/^pnpm@(.+)$/);
|
|
174
|
-
const steps=[];
|
|
175
|
-
if(hasCmd('pnpm')) steps.push(['pnpm',['run',script]]);
|
|
176
|
-
if(hasCmd('corepack')&&match) steps.push(['corepack',['pnpm@'+match[1],'run',script]]);
|
|
177
|
-
if(hasCmd('corepack')) steps.push(['corepack',['pnpm','run',script]]);
|
|
178
|
-
trySteps(steps);
|
|
179
|
-
console.error('deft: neither pnpm nor corepack is available to run \"'+script+'\".');
|
|
180
|
-
if(pin){
|
|
181
|
-
console.error(' Enable Corepack for the pinned manager: corepack enable && corepack prepare '+pin+' --activate');
|
|
182
|
-
}else{
|
|
183
|
-
console.error(' Install pnpm or enable Corepack (see package.json#packageManager).');
|
|
184
|
-
}
|
|
185
|
-
console.error(' Or set DEFT_PACKAGE_MANAGER=npm for an explicit npm build path.');
|
|
186
|
-
process.exit(127);
|
|
187
|
-
" "{{.DEFT_ROOT}}"
|
|
54
|
+
node "{{.TASKFILE_DIR}}/engine-pm-run.cjs" "{{.DEFT_ROOT}}" build --mark-warm
|
|
188
55
|
fi
|
|
189
56
|
|
|
190
57
|
invoke:
|
package/tasks/scm.yml
CHANGED
|
@@ -113,6 +113,16 @@ tasks:
|
|
|
113
113
|
vars:
|
|
114
114
|
ENGINE_CMD: 'github-body issue-edit {{.CLI_ARGS}}'
|
|
115
115
|
|
|
116
|
+
body:issue:fetch:
|
|
117
|
+
desc: "[#2607] Fetch live issue body to UTF-8 --out-file for safe read-modify-write"
|
|
118
|
+
dir: '{{.USER_WORKING_DIR}}'
|
|
119
|
+
deps:
|
|
120
|
+
- task: :engine:_ts-build
|
|
121
|
+
cmds:
|
|
122
|
+
- task: :engine:invoke
|
|
123
|
+
vars:
|
|
124
|
+
ENGINE_CMD: 'github-body issue-fetch {{.CLI_ARGS}}'
|
|
125
|
+
|
|
116
126
|
body:comment:create:
|
|
117
127
|
desc: "[#1555] Safely create an issue/PR comment body from --body-file and live gh read-back"
|
|
118
128
|
dir: '{{.USER_WORKING_DIR}}'
|
package/tasks/verify.yml
CHANGED
|
@@ -59,6 +59,16 @@ tasks:
|
|
|
59
59
|
vars:
|
|
60
60
|
ENGINE_CMD: 'verify-content-manifest --project-root "{{.DEFT_ROOT}}"'
|
|
61
61
|
|
|
62
|
+
skill-external-fetch-gate:
|
|
63
|
+
desc: "Verify shipped skills do not pair external fetch/follow-through with execute/install without Security context mitigation (#1936 / #1532)."
|
|
64
|
+
deps:
|
|
65
|
+
- task: :engine:_ts-build
|
|
66
|
+
# Framework-source-only gate: scans THIS repo's content/skills tree.
|
|
67
|
+
cmds:
|
|
68
|
+
- task: :engine:invoke
|
|
69
|
+
vars:
|
|
70
|
+
ENGINE_CMD: 'verify-skill-external-fetch-gate --project-root "{{.DEFT_ROOT}}"'
|
|
71
|
+
|
|
62
72
|
contract-drift:
|
|
63
73
|
desc: "Drift gate for the public contract layer (#1799). Asserts packages/types/schemas/vbrief-core-0.6.schema.json matches content/vbrief/schemas/vbrief-core.schema.json and that @deftai/directive-types Status/version constants align with the schema. Three-state exit (0 clean / 1 drift / 2 config error)."
|
|
64
74
|
deps:
|
|
@@ -393,6 +403,16 @@ tasks:
|
|
|
393
403
|
vars:
|
|
394
404
|
ENGINE_CMD: 'verify:wip-cap --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
|
|
395
405
|
|
|
406
|
+
orphan-active:
|
|
407
|
+
desc: "Fail-closed orphan-active guard (#2321). Detects xbrief/active/ briefs with plan.status==running whose referenced GitHub issues are all closed and/or whose linked PR is merged — the stop-at:pr-open lifecycle leak. Remediation points at task scope:complete / scope:cancel or swarm finalize surfaces. Three-state exit (0 clean / 1 orphan / 2 config). Pass --skip-gh to rely on triage cache only."
|
|
408
|
+
dir: '{{.USER_WORKING_DIR}}'
|
|
409
|
+
deps:
|
|
410
|
+
- task: :engine:_ts-build
|
|
411
|
+
cmds:
|
|
412
|
+
- task: :engine:invoke
|
|
413
|
+
vars:
|
|
414
|
+
ENGINE_CMD: 'verify:orphan-active --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
|
|
415
|
+
|
|
396
416
|
agents-md-budget:
|
|
397
417
|
desc: "Layered AGENTS.md budget instrument (#645 + #2450). Fail-closed relative ratchet: counts the managed section and the unmanaged region separately (the #1309 propagation duplicates content across the marker) and fails when either region grows past plan.policy.agentsMdBudget. Seeded at current size, so it ships green; growth past the ratchet fails. ADVISORY absolute north-star: also reports managed-section size vs ≤8 KB / ~2k tok (#2372 layered instrument) without affecting exit codes in Wave 1. Three-state exit (0 within / 1 over ratchet / 2 config error)."
|
|
398
418
|
dir: '{{.USER_WORKING_DIR}}'
|
|
@@ -233,6 +233,8 @@ Reference: issue #2563; swarm skill Platform Requirements; env scrub + stdio inh
|
|
|
233
233
|
|
|
234
234
|
! Multi-line git commit / gh issue|pr|comment bodies: write UTF-8 (no BOM) to OS temp, then `git commit -F` / `gh --body-file` / `deft scm:body:* --body-file`. ⊗ bash heredocs, `<<<`, inline multi-line `--body`, or multi-line PS here-strings in the agent command box on Windows PowerShell — those patterns fail at parse time, split arguments, or get rewritten by host shell wrappers before git/gh runs. This applies to your own commit and PR tooling on win32; do not use bash heredocs even when user rules show POSIX patterns. `ghx` is read-only — mutations stay on live `gh`. Detail: `content/scm/github.md` § #2646 (#1417, #240, #798).
|
|
235
235
|
|
|
236
|
+
! Issue-body read-modify-write on win32: `task scm:body:issue:fetch --out-file` then edit the body file then `task scm:body:issue:edit --body-file` (fail-closed postcondition verify, #2607). ⊗ Capture-concat of `gh api repos/.../issues/<N> --jq .body` into PowerShell variables — PS string[]/$OFS collapses newlines to spaces and silently destroys live bodies (#2744, #2087, #2741, #1492). Detail: `content/scm/github.md` § #2744.
|
|
237
|
+
|
|
236
238
|
## 4. pre-pr and review-cycle skills
|
|
237
239
|
|
|
238
240
|
Before pushing any branch:
|
|
@@ -326,6 +328,7 @@ Use the canonical safe wrapper for issue bodies, PR bodies, and issue/PR comment
|
|
|
326
328
|
task scm:body:comment:create -- --repo OWNER/REPO --issue 1555 --body-file "$bodyFile"
|
|
327
329
|
task scm:body:comment:edit -- --repo OWNER/REPO --comment 123456789 --body-file "$bodyFile"
|
|
328
330
|
task scm:body:issue:create -- --repo OWNER/REPO --title "Title" --body-file "$bodyFile"
|
|
331
|
+
task scm:body:issue:fetch -- --repo OWNER/REPO --issue 1555 --out-file "$bodyFile"
|
|
329
332
|
task scm:body:issue:edit -- --repo OWNER/REPO --issue 1555 --body-file "$bodyFile"
|
|
330
333
|
task scm:body:pr:edit -- --repo OWNER/REPO --pr 42 --body-file "$bodyFile"
|
|
331
334
|
```
|
|
@@ -446,8 +449,10 @@ These rules bind **orchestrators** dispatching implementation, fix, or review-cy
|
|
|
446
449
|
**Worker-owns-lifecycle (Gap C):**
|
|
447
450
|
|
|
448
451
|
- ! When dispatching an implementation worker, the dispatch envelope MUST declare the unit-of-work boundary explicitly: `stop-at: pr-open` (worker opens PR and exits) OR `drive-to: merge-ready` (worker owns PR + Greptile review cycle + fix batches through merge-ready as ONE unit of work, spawning its own review poller per `skills/deft-directive-review-cycle/SKILL.md` monitoring tiers). Default for story implementation dispatches is `drive-to: merge-ready`.
|
|
452
|
+
- ! **Post-merge scope lifecycle (#2321 / Gap C):** Workers scoped `stop-at: pr-open` MUST NOT run `task scope:complete` before exit — their activation checkpoint rides into master on merge. The **orchestrator** (or Phase 6 `task swarm:finalize-cohort` / `task swarm:complete-cohort` on the headless path) MUST run `task scope:complete` or `task scope:cancel` for each shipped story xBRIEF after its PR merges. Workers scoped `drive-to: merge-ready` (or `drive-to: merge`) MUST include `task scope:complete` on their active xBRIEF as part of the same unit of work (after merge when appropriate).
|
|
449
453
|
- ! Workers scoped `drive-to: merge-ready` MUST drive to merge-ready in their own tool loop — pre-PR, push, PR open, review-cycle poll/fix loop, and the #1259 Step 6 fail-closed exit — without handing back at PR-open for the orchestrator to re-dispatch separate leaf agents for review or fixes.
|
|
450
454
|
- ⊗ Re-dispatch a separate review-monitor or fix agent after an implementation worker exits at PR-open when the original envelope scoped `drive-to: merge-ready` — that split recreates cross-agent state-handoff hazards and terminal lifecycle gaps (#1878 / Gap C).
|
|
455
|
+
- ⊗ Leave an `xbrief/active/` brief with `plan.status == running` on master after the story's issue is closed or its PR merged — `task verify:orphan-active` fails closed on that signature (#2321).
|
|
451
456
|
|
|
452
457
|
**Background / independent dispatch (Gap D):**
|
|
453
458
|
|
|
@@ -83,9 +83,9 @@ Legacy `vbrief/` read-accepted; `deft migrate:xbrief` for `xbrief/` (v0.6→v0.8
|
|
|
83
83
|
|
|
84
84
|
! When `plan.policy.allowDirectCommitsToMaster = true`, surface via `deft policy:show --field=allowDirectCommitsToMaster` (#746) — `.deft/core/scm/github.md` § Branch policy.
|
|
85
85
|
|
|
86
|
-
## Windows PowerShell: multi-line git/gh bodies (#2646)
|
|
86
|
+
## Windows PowerShell: multi-line git/gh bodies (#2646 / #2744)
|
|
87
87
|
|
|
88
|
-
! Multi-line git commit / gh issue|pr|comment bodies: write UTF-8 (no BOM) to OS temp, then `git commit -F` / `gh --body-file` / `deft scm:body:* --body-file`. ⊗ bash heredocs, `<<<`,
|
|
88
|
+
! Multi-line git commit / gh issue|pr|comment bodies: write UTF-8 (no BOM) to OS temp, then `git commit -F` / `gh --body-file` / `deft scm:body:* --body-file`. Issue-body RMW on win32: `deft scm:body:issue:fetch --out-file` then edit the file then `deft scm:body:issue:edit --body-file` (#2607 postcondition verify). ⊗ bash heredocs, `<<<`, inline multi-line `--body`, or PS capture-concat of `gh api --jq .body` (string[]/$OFS destroys bodies — #2087, #2741, #1492). Detail: `.deft/core/scm/github.md` § #2646 / #2744. `ghx` is read-only — mutations stay on live `gh`.
|
|
89
89
|
|
|
90
90
|
## Contextual guardrails (runtime-detect lazy-load)
|
|
91
91
|
|