create-harness-vibe-coding 0.8.9 → 0.8.12
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/README-CN.md +5 -5
- package/README.md +6 -6
- package/docs/images/harness-architecture.drawio +2 -2
- package/package.json +3 -2
- package/templates/common/.claude/commands/wf-help.md +1 -1
- package/templates/common/.claude/settings.json +2 -2
- package/templates/common/.claude/skills/subagent-orchestrator/SKILL.md +2 -2
- package/templates/common/.claude/skills/wf-max/SKILL.md +3 -3
- package/templates/common/.claude/skills/wf-review/SKILL.md +63 -12
- package/templates/common/.codex/hooks.json +5 -5
- package/templates/common/.harness-version +28 -28
- package/templates/common/.opencode/commands/wf-help.md +1 -1
- package/templates/common/.opencode/commands/wf-review.md +2 -2
- package/templates/common/.opencode/plugins/harness-wf-status.mjs +12 -21
- package/templates/common/Harness/MEMORY.md +2 -2
- package/templates/common/Harness/README.md +3 -3
- package/templates/common/Harness/SETUP.md +1 -1
- package/templates/common/Harness/WF-AUTO-ANGLES.md +1 -1
- package/templates/common/Harness/WF-AUTO-SPARK.md +21 -4
- package/templates/common/Harness/WF-AUTO.md +9 -8
- package/templates/common/Harness/WF-MAX.md +37 -3
- package/templates/common/Harness/dispatch.md +1 -1
- package/templates/common/Harness/scripts/scan-clean.mjs +2 -1
- package/templates/common/Harness/scripts/validate-harness.mjs +36 -0
- package/templates/common/Harness/scripts/wf-auto-update-prompt.mjs +2 -2
- package/templates/common/Harness/scripts/wf-remove.mjs +1 -0
- package/templates/common/Harness/scripts/wf-update-check.mjs +123 -45
- package/templates/common/Harness/subagents.md +8 -1
- package/templates/common/README.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* wf-update-check.mjs
|
|
3
|
+
* wf-update-check.mjs - Fast harness update comparison.
|
|
4
4
|
* Fetches remote checksums, compares locally, classifies all files instantly.
|
|
5
5
|
* Only CONFLICT files need AI/user decision.
|
|
6
6
|
*
|
|
@@ -23,9 +23,13 @@ import { fileURLToPath } from 'url';
|
|
|
23
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
const ROOT = process.env.WF_ROOT ? resolve(process.env.WF_ROOT) : resolve(__dirname, '..', '..');
|
|
25
25
|
const VERSION_FILE = resolve(ROOT, 'Harness', '.harness-version');
|
|
26
|
-
const
|
|
26
|
+
const GITHUB_REPO = 'LiWeny16/create-harness-vibe-coding';
|
|
27
|
+
const GITHUB_LATEST_STABLE = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
|
|
28
|
+
const RAW_GITHUB = `https://raw.githubusercontent.com/${GITHUB_REPO}`;
|
|
29
|
+
const TEMPLATE_SUBPATH = 'templates/common/';
|
|
30
|
+
const DEFAULT_SOURCE_BASE = `${RAW_GITHUB}/main/${TEMPLATE_SUBPATH}`;
|
|
27
31
|
|
|
28
|
-
//
|
|
32
|
+
// Tier classification
|
|
29
33
|
|
|
30
34
|
/** Files we NEVER overwrite or delete. */
|
|
31
35
|
const PRESERVE_PATTERNS = [
|
|
@@ -56,10 +60,15 @@ const OPTIONAL_REGISTRATION_FILES = new Set([
|
|
|
56
60
|
'Harness/README.md',
|
|
57
61
|
]);
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
const BOOTSTRAP_ONLY_FILES = new Set([
|
|
64
|
+
'Harness/SETUP.md',
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
// Helpers
|
|
60
68
|
|
|
61
69
|
/** Reject paths that escape ROOT (traversal, absolute, .., etc.). */
|
|
62
70
|
function safePath(file) {
|
|
71
|
+
if (/^[A-Za-z]:/.test(file)) return null;
|
|
63
72
|
let normalized = file.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
64
73
|
if (/\/\//.test(normalized)) return null;
|
|
65
74
|
if (normalized.split('/').some(p => p === '..')) return null;
|
|
@@ -136,7 +145,7 @@ function sha256(content) {
|
|
|
136
145
|
function sha256File(path) {
|
|
137
146
|
if (!existsSync(path)) return null;
|
|
138
147
|
let content = readFileSync(path, 'utf-8');
|
|
139
|
-
// Normalize CRLF
|
|
148
|
+
// Normalize CRLF to LF.
|
|
140
149
|
content = content.replace(/\r\n/g, '\n');
|
|
141
150
|
return sha256(content);
|
|
142
151
|
}
|
|
@@ -146,16 +155,18 @@ function classify(file, localHash, storedHash) {
|
|
|
146
155
|
for (const p of PRESERVE_PATTERNS) {
|
|
147
156
|
if (p.test(file)) return 'PRESERVE';
|
|
148
157
|
}
|
|
149
|
-
// MERGE
|
|
158
|
+
// MERGE: dual-purpose, check if user modified.
|
|
150
159
|
for (const p of MERGE_PATTERNS) {
|
|
151
160
|
if (p.test(file)) {
|
|
152
161
|
if (localHash === storedHash) return 'SAFE'; // unmodified, safe
|
|
153
162
|
return 'CONFLICT'; // user modified, needs decision
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
|
-
// Everything else is SAFE runtime file
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
// Everything else is SAFE runtime file: always overwrite.
|
|
166
|
+
// Harness system files (scripts, skills, agents, commands, WF docs) are
|
|
167
|
+
// not user data; the template is authoritative. Only PRESERVE and MERGE
|
|
168
|
+
// files should ever require conflict resolution.
|
|
169
|
+
return 'SAFE';
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
async function fetchRemote(url, timeoutMs = 30000) {
|
|
@@ -174,7 +185,49 @@ async function fetchRemote(url, timeoutMs = 30000) {
|
|
|
174
185
|
}
|
|
175
186
|
}
|
|
176
187
|
|
|
177
|
-
//
|
|
188
|
+
// Main
|
|
189
|
+
|
|
190
|
+
function isPrerelease(v) {
|
|
191
|
+
if (!v || typeof v !== 'string') return false;
|
|
192
|
+
return /-[0-9A-Za-z.-]+/.test(v.replace(/^[^0-9]*/, ''));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function resolveStableSourceBase() {
|
|
196
|
+
try {
|
|
197
|
+
const controller = new AbortController();
|
|
198
|
+
const timer = setTimeout(() => controller.abort(), 15000);
|
|
199
|
+
let res;
|
|
200
|
+
try {
|
|
201
|
+
res = await fetch(GITHUB_LATEST_STABLE, {
|
|
202
|
+
signal: controller.signal,
|
|
203
|
+
headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'harness-wf-update-check' },
|
|
204
|
+
});
|
|
205
|
+
} finally {
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
}
|
|
208
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
209
|
+
const data = JSON.parse(await res.text());
|
|
210
|
+
const tag = data && data.tag_name;
|
|
211
|
+
if (!tag || data.prerelease) return null;
|
|
212
|
+
const source = `${RAW_GITHUB}/${tag}/${TEMPLATE_SUBPATH}`;
|
|
213
|
+
const versionController = new AbortController();
|
|
214
|
+
const versionTimer = setTimeout(() => versionController.abort(), 15000);
|
|
215
|
+
try {
|
|
216
|
+
const versionRes = await fetch(source + '.harness-version', {
|
|
217
|
+
signal: versionController.signal,
|
|
218
|
+
headers: { 'User-Agent': 'harness-wf-update-check' },
|
|
219
|
+
});
|
|
220
|
+
if (!versionRes.ok) return null;
|
|
221
|
+
const raw = await versionRes.text();
|
|
222
|
+
if (isTemplate(raw)) return null;
|
|
223
|
+
} finally {
|
|
224
|
+
clearTimeout(versionTimer);
|
|
225
|
+
}
|
|
226
|
+
return source;
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
178
231
|
|
|
179
232
|
async function main() {
|
|
180
233
|
const args = process.argv.slice(2);
|
|
@@ -186,7 +239,16 @@ async function main() {
|
|
|
186
239
|
const acceptMerged = readRepeatedFlagValues(args, '--accept-merged');
|
|
187
240
|
const acceptTemplate = readRepeatedFlagValues(args, '--accept-template');
|
|
188
241
|
const ignoreVersion = args.includes('--ignore-version') || args.includes('--force-check');
|
|
189
|
-
const
|
|
242
|
+
const explicitSource = readFlagValue(args, '--source-base') || process.env.WF_SOURCE_BASE;
|
|
243
|
+
let sourceBase = normalizeSourceBase(explicitSource || DEFAULT_SOURCE_BASE);
|
|
244
|
+
let stableTagResolved = false;
|
|
245
|
+
if (!explicitSource) {
|
|
246
|
+
const resolved = await resolveStableSourceBase();
|
|
247
|
+
if (resolved) {
|
|
248
|
+
sourceBase = normalizeSourceBase(resolved);
|
|
249
|
+
stableTagResolved = true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
190
252
|
|
|
191
253
|
// 1. Read local state
|
|
192
254
|
if (!existsSync(VERSION_FILE)) {
|
|
@@ -248,9 +310,9 @@ async function main() {
|
|
|
248
310
|
if (jsonOut) {
|
|
249
311
|
console.log(JSON.stringify({ status: 'template-remote', message: 'Remote .harness-version has not been generated yet.' }));
|
|
250
312
|
} else {
|
|
251
|
-
console.log('
|
|
313
|
+
console.log('WARN: Remote .harness-version is a template (contains {{placeholders}}).');
|
|
252
314
|
console.log(' The generate step has not been run on the remote repo. No update possible.');
|
|
253
|
-
console.log(' This is expected during development
|
|
315
|
+
console.log(' This is expected during development; the update mechanism works once the remote is live.');
|
|
254
316
|
}
|
|
255
317
|
process.exitCode = 1;
|
|
256
318
|
return;
|
|
@@ -267,10 +329,10 @@ async function main() {
|
|
|
267
329
|
return;
|
|
268
330
|
}
|
|
269
331
|
|
|
270
|
-
// Compare versions
|
|
332
|
+
// Compare versions and prevent downgrades from explicit custom sources.
|
|
271
333
|
function parseSemver(v) {
|
|
272
334
|
if (!v || typeof v !== 'string') return [0, 0, 0];
|
|
273
|
-
return v.replace(/^[^0-9]*/, '').split('
|
|
335
|
+
return v.replace(/^[^0-9]*/, '').split('.').slice(0, 3).map(n => Number(n) || 0);
|
|
274
336
|
}
|
|
275
337
|
function cmpSemver(a, b) {
|
|
276
338
|
const va = parseSemver(a), vb = parseSemver(b);
|
|
@@ -280,27 +342,38 @@ async function main() {
|
|
|
280
342
|
|
|
281
343
|
const localGen = localVersion.generator || '0.0.0';
|
|
282
344
|
const remoteGen = remoteVersion.generator || '0.0.0';
|
|
345
|
+
|
|
346
|
+
if (!ignoreVersion && isPrerelease(remoteGen)) {
|
|
347
|
+
if (jsonOut) {
|
|
348
|
+
console.log(JSON.stringify({ status: 'up-to-date', version: localGen, remote: remoteGen, sourceBase }));
|
|
349
|
+
} else {
|
|
350
|
+
console.log(`Already up to date (v${localGen}). Remote ${remoteGen} is a prerelease and is ignored.`);
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
283
355
|
const versionCmp = cmpSemver(remoteGen, localGen);
|
|
284
356
|
|
|
285
357
|
if (!ignoreVersion && versionCmp <= 0) {
|
|
358
|
+
const reportDowngrade = !stableTagResolved;
|
|
286
359
|
if (jsonOut) {
|
|
287
360
|
console.log(JSON.stringify({
|
|
288
|
-
status: versionCmp < 0 ? 'downgrade-refused' : 'up-to-date',
|
|
361
|
+
status: (versionCmp < 0 && reportDowngrade) ? 'downgrade-refused' : 'up-to-date',
|
|
289
362
|
version: localGen,
|
|
290
363
|
remote: remoteGen,
|
|
291
364
|
sourceBase,
|
|
292
365
|
}));
|
|
293
|
-
} else if (versionCmp < 0) {
|
|
294
|
-
console.log(
|
|
366
|
+
} else if (versionCmp < 0 && reportDowngrade) {
|
|
367
|
+
console.log(`WARN: Remote (v${remoteGen}) is OLDER than local (v${localGen}). Downgrade refused.`);
|
|
295
368
|
} else {
|
|
296
|
-
console.log(
|
|
369
|
+
console.log(`Already up to date (v${localGen})`);
|
|
297
370
|
}
|
|
298
|
-
if (versionCmp < 0) process.exitCode = 1;
|
|
371
|
+
if (versionCmp < 0 && reportDowngrade) process.exitCode = 1;
|
|
299
372
|
return;
|
|
300
373
|
}
|
|
301
374
|
|
|
302
375
|
if (ignoreVersion) {
|
|
303
|
-
if (!jsonOut) console.log('
|
|
376
|
+
if (!jsonOut) console.log('Version check bypassed (--ignore-version). Comparing files anyway.');
|
|
304
377
|
}
|
|
305
378
|
|
|
306
379
|
const remoteChecksums = remoteVersion.checksums || {};
|
|
@@ -469,8 +542,13 @@ async function main() {
|
|
|
469
542
|
continue;
|
|
470
543
|
}
|
|
471
544
|
|
|
545
|
+
if (BOOTSTRAP_ONLY_FILES.has(canonical) && localHash === null) {
|
|
546
|
+
plan.skipped.push({ file, reason: 'bootstrap-only file already removed locally' });
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
472
550
|
if (!storedHash) {
|
|
473
|
-
// New file from remote
|
|
551
|
+
// New file from remote: if local file exists, it is a CONFLICT.
|
|
474
552
|
if (localHash) {
|
|
475
553
|
if (localHash === remoteHash) {
|
|
476
554
|
plan.adopted.push({ file, localHash, remoteHash, reason: 'new remote file already matches local file' });
|
|
@@ -483,10 +561,10 @@ async function main() {
|
|
|
483
561
|
plan.conflict.push({ file, localHash, storedHash: 'none', remoteHash, reason: 'new remote file conflicts with existing local file' });
|
|
484
562
|
continue;
|
|
485
563
|
}
|
|
486
|
-
// New file
|
|
564
|
+
// New file: still respect PRESERVE classification.
|
|
487
565
|
const tier = classify(canonical, null, null);
|
|
488
566
|
if (tier === 'PRESERVE') {
|
|
489
|
-
plan.skipped.push({ file, reason: 'PRESERVE
|
|
567
|
+
plan.skipped.push({ file, reason: 'PRESERVE: new file would overwrite user data' });
|
|
490
568
|
} else {
|
|
491
569
|
plan.created.push({ file, remoteHash });
|
|
492
570
|
}
|
|
@@ -517,7 +595,7 @@ async function main() {
|
|
|
517
595
|
const tier = classify(canonical, localHash, storedHash);
|
|
518
596
|
|
|
519
597
|
if (tier === 'PRESERVE') {
|
|
520
|
-
plan.skipped.push({ file, reason: 'PRESERVE
|
|
598
|
+
plan.skipped.push({ file, reason: 'PRESERVE: user data' });
|
|
521
599
|
} else if (tier === 'SAFE') {
|
|
522
600
|
if (localHash === remoteHash) {
|
|
523
601
|
plan.skipped.push({ file, reason: 'already current' });
|
|
@@ -564,14 +642,14 @@ async function main() {
|
|
|
564
642
|
return;
|
|
565
643
|
}
|
|
566
644
|
|
|
567
|
-
console.log(`\
|
|
645
|
+
console.log(`\nUpdate: v${localGen} -> v${remoteGen}`);
|
|
568
646
|
console.log(` ${plan.updated.length} safe update, ${plan.created.length} new, ${plan.conflict.length} conflict, ${plan.skipped.length} skipped\n`);
|
|
569
647
|
|
|
570
648
|
// Show conflicts (these need AI/user decision)
|
|
571
649
|
if (plan.conflict.length > 0) {
|
|
572
|
-
console.log('
|
|
650
|
+
console.log('CONFLICTS (need your decision):');
|
|
573
651
|
for (const c of plan.conflict) {
|
|
574
|
-
console.log(`
|
|
652
|
+
console.log(` ! ${c.file} [${c.reason}]`);
|
|
575
653
|
}
|
|
576
654
|
if (plan.updated.length + plan.created.length > 0) {
|
|
577
655
|
console.log(' Tip: run --apply-safe to apply SAFE/NEW files first, then merge conflicts.');
|
|
@@ -581,17 +659,17 @@ async function main() {
|
|
|
581
659
|
|
|
582
660
|
// Show what will be auto-updated
|
|
583
661
|
if (plan.updated.length + plan.created.length > 0) {
|
|
584
|
-
console.log('
|
|
585
|
-
for (const u of plan.updated) console.log(`
|
|
662
|
+
console.log('AUTO (safe to apply):');
|
|
663
|
+
for (const u of plan.updated) console.log(` ^ ${u.file}`);
|
|
586
664
|
for (const c of plan.created) console.log(` + ${c.file}`);
|
|
587
665
|
console.log('');
|
|
588
666
|
}
|
|
589
667
|
|
|
590
668
|
// 4. Apply if requested
|
|
591
669
|
if (apply || applySafe) {
|
|
592
|
-
// Refuse to apply when conflicts exist
|
|
670
|
+
// Refuse to apply when conflicts exist; must resolve first.
|
|
593
671
|
if (apply && !applySafe && plan.conflict.length > 0) {
|
|
594
|
-
console.log(
|
|
672
|
+
console.log(`Cannot apply: ${plan.conflict.length} conflicts must be resolved first.`);
|
|
595
673
|
console.log(' Run --apply-safe to apply SAFE/NEW files first, or resolve conflicts manually then re-run --apply.');
|
|
596
674
|
process.exitCode = 1;
|
|
597
675
|
return plan;
|
|
@@ -607,23 +685,23 @@ async function main() {
|
|
|
607
685
|
for (const u of plan.updated) {
|
|
608
686
|
try {
|
|
609
687
|
const dest = safePath(u.file);
|
|
610
|
-
if (!dest) { console.error(`
|
|
611
|
-
// Symlink rejection
|
|
688
|
+
if (!dest) { console.error(` x Traversal rejected: ${u.file}`); failed++; continue; }
|
|
689
|
+
// Symlink rejection: do not follow symlinks.
|
|
612
690
|
if (lexists(dest)) {
|
|
613
|
-
try { if (lstatSync(dest).isSymbolicLink()) { console.error(`
|
|
691
|
+
try { if (lstatSync(dest).isSymbolicLink()) { console.error(` x Symlink rejected: ${u.file}`); failed++; continue; } } catch (_) {}
|
|
614
692
|
}
|
|
615
693
|
const content = await fetchRemote(sourceBase + remotePath(u.file));
|
|
616
694
|
const normalized = content.replace(/\r\n/g, '\n');
|
|
617
695
|
const fetchedHash = sha256(normalized);
|
|
618
696
|
if (fetchedHash !== u.remoteHash) {
|
|
619
|
-
console.error(`
|
|
697
|
+
console.error(` x Hash mismatch: ${u.file}`);
|
|
620
698
|
failed++; continue;
|
|
621
699
|
}
|
|
622
700
|
mkdirSync(dirname(dest), { recursive: true });
|
|
623
701
|
writeFileSync(dest, normalized, 'utf-8');
|
|
624
702
|
applied++;
|
|
625
703
|
} catch (e) {
|
|
626
|
-
console.error(`
|
|
704
|
+
console.error(` x Failed: ${u.file} - ${e.message}`);
|
|
627
705
|
failed++;
|
|
628
706
|
}
|
|
629
707
|
}
|
|
@@ -631,25 +709,25 @@ async function main() {
|
|
|
631
709
|
for (const c of plan.created) {
|
|
632
710
|
try {
|
|
633
711
|
const dest = safePath(c.file);
|
|
634
|
-
if (!dest) { console.error(`
|
|
712
|
+
if (!dest) { console.error(` x Traversal rejected: ${c.file}`); failed++; continue; }
|
|
635
713
|
// TOCTOU: recheck file didn't appear since planning
|
|
636
714
|
if (lexists(dest)) {
|
|
637
|
-
try { if (lstatSync(dest).isSymbolicLink()) { console.error(`
|
|
638
|
-
console.error(`
|
|
715
|
+
try { if (lstatSync(dest).isSymbolicLink()) { console.error(` x Symlink rejected: ${c.file}`); failed++; continue; } } catch (_) {}
|
|
716
|
+
console.error(` x File created since plan: ${c.file} - treating as CONFLICT`);
|
|
639
717
|
failed++; continue;
|
|
640
718
|
}
|
|
641
719
|
const content = await fetchRemote(sourceBase + remotePath(c.file));
|
|
642
720
|
const normalized = content.replace(/\r\n/g, '\n');
|
|
643
721
|
const fetchedHash = sha256(normalized);
|
|
644
722
|
if (fetchedHash !== c.remoteHash) {
|
|
645
|
-
console.error(`
|
|
723
|
+
console.error(` x Hash mismatch: ${c.file}`);
|
|
646
724
|
failed++; continue;
|
|
647
725
|
}
|
|
648
726
|
mkdirSync(dirname(dest), { recursive: true });
|
|
649
727
|
writeFileSync(dest, normalized, 'utf-8');
|
|
650
728
|
applied++;
|
|
651
729
|
} catch (e) {
|
|
652
|
-
console.error(`
|
|
730
|
+
console.error(` x Failed: ${c.file} - ${e.message}`);
|
|
653
731
|
failed++;
|
|
654
732
|
}
|
|
655
733
|
}
|
|
@@ -676,12 +754,12 @@ async function main() {
|
|
|
676
754
|
}
|
|
677
755
|
writeFileSync(VERSION_FILE, JSON.stringify(localVersion, null, 2) + '\n', 'utf-8');
|
|
678
756
|
if (plan.conflict.length === 0) {
|
|
679
|
-
console.log(
|
|
757
|
+
console.log(`Applied ${applied} files. Version updated to ${remoteVersion.generator}.`);
|
|
680
758
|
} else {
|
|
681
|
-
console.log(
|
|
759
|
+
console.log(`Applied ${applied} SAFE/NEW files. Version remains ${localGen}; ${plan.conflict.length} conflicts still need merge.`);
|
|
682
760
|
}
|
|
683
761
|
} else {
|
|
684
|
-
console.log(
|
|
762
|
+
console.log(`${failed} failures. NO files were version-tracked. Fix and re-run.`);
|
|
685
763
|
process.exitCode = 1;
|
|
686
764
|
}
|
|
687
765
|
}
|
|
@@ -124,7 +124,7 @@ Choose the cheapest coordination level that is safe.
|
|
|
124
124
|
Max parallelism removes the Harness default cap, not the runtime's physical or
|
|
125
125
|
account cap. For WF-MAX, record the current runtime budget, use native
|
|
126
126
|
subagents first, close completed agents before declaring the pool exhausted,
|
|
127
|
-
then overflow to
|
|
127
|
+
then overflow to a peer CLI (`claude -p`, `codex exec`, or `opencode run --agent <role> --dir .`) with explicit
|
|
128
128
|
dispatch packets. Generated Harness Codex config defaults to
|
|
129
129
|
`agents.max_threads = 12` and `agents.max_depth = 1`; if that becomes the
|
|
130
130
|
bottleneck, ask the user before raising `agents.max_threads` and keep
|
|
@@ -192,6 +192,13 @@ matrix from running behavior and evidence, not from the implementer's summary.
|
|
|
192
192
|
|
|
193
193
|
If either reviewer finds issues, the implementer or debugger fixes them and the same gate runs again. Do not move to final acceptance with open critical/high findings or without reflector PASS.
|
|
194
194
|
|
|
195
|
+
For `/wf-review`, use the installed `reviewer` role before inventing any
|
|
196
|
+
ad hoc review prompt. If no peer CLI is available, dispatch `reviewer` as an
|
|
197
|
+
independent same-runtime subagent context; for broad WF-MAX review, dispatch
|
|
198
|
+
`review-manager` when the runtime supports nested reviewer fan-out. Reviewer
|
|
199
|
+
agents report suggestions only. The controller accepts, rejects, or escalates
|
|
200
|
+
each finding and owns the final decision.
|
|
201
|
+
|
|
195
202
|
## Subagent Status Handling
|
|
196
203
|
|
|
197
204
|
| Status | Controller Action |
|
|
@@ -45,4 +45,4 @@ The agentic engineering harness lives in `Harness/`.
|
|
|
45
45
|
Tool discovery files stay at the repository root:
|
|
46
46
|
|
|
47
47
|
- Claude Code: `.claude/settings.json`, `.claude/agents/`, and `.claude/skills/`.
|
|
48
|
-
- Codex: `.agents/skills/` for repo skills and `.codex/` for config placeholders.
|
|
48
|
+
- Codex: `.agents/skills/` for repo skills and `.codex/` for config placeholders. The bundled update reminder uses a startup-only hook; avoid turn-by-turn runtime hooks unless `/wf-auto` explicitly opts into a bounded tick hook.
|