create-harness-vibe-coding 0.8.16 → 0.8.18
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/CHANGELOG.md +18 -0
- package/README-CN.md +100 -127
- package/README.md +63 -42
- package/package.json +1 -1
- package/src/generator.js +628 -121
- package/src/index.js +174 -43
- package/src/prompts.js +18 -0
- package/templates/common/.claude/commands/wf-auto-spark.md +16 -0
- package/templates/common/.claude/commands/wf-auto.md +16 -0
- package/templates/{optional/skills/browser-e2e/.opencode → common/.claude}/commands/wf-browser.md +1 -1
- package/templates/common/.claude/commands/wf-command-create.md +58 -0
- package/templates/common/.claude/commands/wf-help.md +13 -6
- package/templates/common/.claude/commands/wf-learn.md +16 -0
- package/templates/common/.claude/commands/wf-max.md +20 -0
- package/templates/common/.claude/commands/wf-readme.md +16 -0
- package/templates/common/.claude/commands/wf-remove.md +16 -0
- package/templates/common/.claude/commands/wf-review.md +16 -0
- package/templates/common/.claude/commands/wf-task-archive.md +26 -0
- package/templates/common/.claude/commands/wf-task-list.md +24 -0
- package/templates/common/.claude/commands/wf-task-record.md +24 -0
- package/templates/common/.claude/commands/wf.md +16 -0
- package/templates/common/.claude/rules/ecc/common.md +1 -1
- package/templates/common/.claude/skills/wf-agents-docs/SKILL.md +15 -30
- package/templates/common/.claude/skills/wf-browser/SKILL.md +176 -0
- package/templates/common/.claude/skills/wf-command-create/SKILL.md +37 -0
- package/templates/common/.claude/skills/wf-help/SKILL.md +30 -0
- package/templates/common/.claude/skills/wf-max/SKILL.md +22 -8
- package/templates/common/.claude/skills/wf-review/SKILL.md +29 -2
- package/templates/common/.claude/skills/wf-task-archive/SKILL.md +28 -0
- package/templates/common/.claude/skills/wf-task-list/SKILL.md +28 -0
- package/templates/common/.claude/skills/wf-task-record/SKILL.md +28 -0
- package/templates/common/.codex/config.toml +3 -5
- package/templates/common/.harness-version +108 -41
- package/templates/common/.opencode/agents/architect-manager.md +7 -1
- package/templates/common/.opencode/agents/explore-manager.md +10 -1
- package/templates/common/.opencode/agents/implement-manager.md +5 -3
- package/templates/common/.opencode/agents/review-manager.md +5 -2
- package/templates/common/.opencode/commands/wf-browser.md +16 -0
- package/templates/common/.opencode/commands/wf-command-create.md +61 -0
- package/templates/common/.opencode/commands/wf-help.md +13 -6
- package/templates/common/.opencode/commands/wf-max.md +12 -7
- package/templates/common/.opencode/commands/wf-task-archive.md +29 -0
- package/templates/common/.opencode/commands/wf-task-list.md +27 -0
- package/templates/common/.opencode/commands/wf-task-record.md +27 -0
- package/templates/common/CLAUDE.md +9 -7
- package/templates/common/Harness/MEMORY.md +12 -3
- package/templates/common/Harness/README.md +23 -41
- package/templates/common/Harness/ownership.manifest.json +162 -14
- package/templates/common/Harness/scripts/archive-tasks.mjs +12 -220
- package/templates/common/Harness/scripts/scan-clean.mjs +3 -9
- package/templates/common/Harness/scripts/task-state.mjs +1279 -0
- package/templates/common/Harness/scripts/validate-harness.mjs +635 -65
- package/templates/common/Harness/scripts/wf-remove.mjs +37 -5
- package/templates/common/Harness/scripts/wf-update-check.mjs +3 -0
- package/templates/common/Harness/specs/guides/SETUP.md +10 -2
- package/templates/common/Harness/specs/protocols/MEMORY_PROTOCOL.md +15 -0
- package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +49 -23
- package/templates/common/Harness/specs/runtime/command-surface.json +215 -0
- package/templates/common/Harness/specs/runtime/dispatch.md +2 -2
- package/templates/common/Harness/specs/runtime/subagents.md +15 -5
- package/templates/common/Harness/specs/workflows/WF-AUTO.md +6 -6
- package/templates/common/Harness/specs/workflows/WF-KERNEL.md +1 -1
- package/templates/common/Harness/specs/workflows/WF-MAX.md +35 -2
- package/templates/common/Harness/specs/workflows/WF-STATE.md +155 -36
- package/templates/common/Harness/tasks/_template/STATE.json +6 -0
- package/templates/common/opencode.json +1 -0
- package/templates/optional/catalog.json +2 -9
- package/templates/optional/skills/browser-e2e/.claude/skills/browser-e2e/SKILL.md +0 -42
- package/templates/optional/skills/browser-e2e/.claude/skills/wf-browser/SKILL.md +0 -201
- package/templates/optional/skills/browser-e2e/Harness/workflows/browser-e2e.md +0 -119
package/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import pc from 'picocolors';
|
|
7
|
-
import { askConflictPolicy, askOptionalSelections, askProjectName, askTargetDir } from './prompts.js';
|
|
7
|
+
import { askConflictPolicy, askInstallScope, askOptionalSelections, askProjectName, askTargetDir } from './prompts.js';
|
|
8
8
|
import { generate, getOptionalCatalog } from './generator.js';
|
|
9
9
|
|
|
10
10
|
const UPDATE_SUCCESS_STATUSES = new Set(['up-to-date', 'update-available', 'partial-update']);
|
|
@@ -45,6 +45,9 @@ if (showHelp) {
|
|
|
45
45
|
console.log(' --without <id,id> Remove optional workflow skills selected by --preset or --with');
|
|
46
46
|
console.log(' --recommend <id,id> Record recommendation-only external capabilities');
|
|
47
47
|
console.log(' --preset <name> Add a built-in optional workflow preset');
|
|
48
|
+
console.log(' --install-scope <scope> project or global (default: project)');
|
|
49
|
+
console.log(' --global-dir <dir> Global Harness runtime directory for --install-scope global');
|
|
50
|
+
console.log(' --host-global-dir <dir> Base directory for Claude/Codex/OpenCode global copies');
|
|
48
51
|
console.log(' --list-options Print optional workflow skills and presets');
|
|
49
52
|
console.log(' --json Output machine-readable JSON (use with --dry-run for planning)');
|
|
50
53
|
console.log('');
|
|
@@ -57,9 +60,10 @@ if (showHelp) {
|
|
|
57
60
|
console.log(' npx create-harness-vibe-coding@latest my-project ./dist/my-project -y');
|
|
58
61
|
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --dry-run');
|
|
59
62
|
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --on-conflict skip');
|
|
60
|
-
console.log(' npx create-harness-vibe-coding@latest web ./web -y --with ts-react-frontend,
|
|
61
|
-
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
62
|
-
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
63
|
+
console.log(' npx create-harness-vibe-coding@latest web ./web -y --with ts-react-frontend,ui-ux-review');
|
|
64
|
+
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
65
|
+
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
66
|
+
console.log(' npx create-harness-vibe-coding@latest app ./app -y --install-scope global');
|
|
63
67
|
console.log('');
|
|
64
68
|
process.exit(0);
|
|
65
69
|
}
|
|
@@ -80,6 +84,9 @@ const generationOptions = {
|
|
|
80
84
|
withoutOptions: parsed.flags.without || [],
|
|
81
85
|
externalOptions: parsed.flags.recommend || [],
|
|
82
86
|
preset: parsed.flags.preset,
|
|
87
|
+
installScope: parsed.flags.installScope || 'project',
|
|
88
|
+
globalDir: parsed.flags.globalDir,
|
|
89
|
+
hostGlobalDir: parsed.flags.hostGlobalDir,
|
|
83
90
|
json: Boolean(parsed.flags.json),
|
|
84
91
|
};
|
|
85
92
|
|
|
@@ -137,9 +144,19 @@ if (argName || skipPrompts) {
|
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
140
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
147
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
141
148
|
console.log(` Directory ${pc.green(targetDir)}`);
|
|
142
|
-
console.log(`
|
|
149
|
+
console.log(` Scope ${pc.green(generationOptions.installScope)}`);
|
|
150
|
+
if (generationOptions.globalDir) {
|
|
151
|
+
console.log(` Global dir ${pc.green(generationOptions.globalDir)}`);
|
|
152
|
+
}
|
|
153
|
+
if (generationOptions.hostGlobalDir) {
|
|
154
|
+
console.log(` Host dir ${pc.green(generationOptions.hostGlobalDir)}`);
|
|
155
|
+
}
|
|
156
|
+
const creates = generationOptions.installScope === 'global'
|
|
157
|
+
? 'project bridge/state/settings + global runtime + Claude/Codex/OpenCode host copies'
|
|
158
|
+
: 'CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/';
|
|
159
|
+
console.log(` Creates ${pc.cyan(creates)}`);
|
|
143
160
|
if (generationOptions.dryRun) {
|
|
144
161
|
console.log(` Mode ${pc.yellow('dry-run')}`);
|
|
145
162
|
}
|
|
@@ -176,8 +193,15 @@ if (argName || skipPrompts) {
|
|
|
176
193
|
try {
|
|
177
194
|
targetDir = await askTargetDir(projectName);
|
|
178
195
|
} catch {
|
|
179
|
-
targetDir = `./${projectName}`;
|
|
180
|
-
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
196
|
+
targetDir = `./${projectName}`;
|
|
197
|
+
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
generationOptions.installScope = await askInstallScope();
|
|
202
|
+
} catch {
|
|
203
|
+
generationOptions.installScope = 'project';
|
|
204
|
+
console.log(pc.dim(' Scope: project (default)'));
|
|
181
205
|
}
|
|
182
206
|
|
|
183
207
|
const scan = scanTarget(targetDir);
|
|
@@ -219,9 +243,19 @@ if (argName || skipPrompts) {
|
|
|
219
243
|
|
|
220
244
|
console.log('');
|
|
221
245
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
222
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
246
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
223
247
|
console.log(` Directory ${pc.green(targetDir)}`);
|
|
224
|
-
console.log(`
|
|
248
|
+
console.log(` Scope ${pc.green(generationOptions.installScope)}`);
|
|
249
|
+
if (generationOptions.globalDir) {
|
|
250
|
+
console.log(` Global dir ${pc.green(generationOptions.globalDir)}`);
|
|
251
|
+
}
|
|
252
|
+
if (generationOptions.hostGlobalDir) {
|
|
253
|
+
console.log(` Host dir ${pc.green(generationOptions.hostGlobalDir)}`);
|
|
254
|
+
}
|
|
255
|
+
const creates = generationOptions.installScope === 'global'
|
|
256
|
+
? 'project bridge/state + global runtime + Claude/Codex/OpenCode host copies'
|
|
257
|
+
: 'CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/';
|
|
258
|
+
console.log(` Creates ${pc.cyan(creates)}`);
|
|
225
259
|
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
226
260
|
if (generationOptions.withOptions.length > 0) {
|
|
227
261
|
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
@@ -237,11 +271,19 @@ if (argName || skipPrompts) {
|
|
|
237
271
|
printResult(preview, targetDir);
|
|
238
272
|
}
|
|
239
273
|
|
|
240
|
-
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
274
|
+
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
275
|
+
console.log(pc.bold('Project plan:'));
|
|
276
|
+
printSummary(preview.summary);
|
|
277
|
+
printPlan(preview.plan);
|
|
278
|
+
if (preview.globalPlan) {
|
|
279
|
+
console.log(pc.bold('\nGlobal runtime plan:'));
|
|
280
|
+
console.log(` Directory ${pc.cyan(preview.globalDir)}`);
|
|
281
|
+
printSummary(preview.globalSummary);
|
|
282
|
+
printPlan(preview.globalPlan);
|
|
283
|
+
}
|
|
284
|
+
printHostPlans(preview.hostPlans, preview.hostSummary);
|
|
285
|
+
printWarnings(preview);
|
|
286
|
+
console.log('');
|
|
245
287
|
|
|
246
288
|
if (generationOptions.dryRun) {
|
|
247
289
|
process.exit(0);
|
|
@@ -270,11 +312,19 @@ if (argName || skipPrompts) {
|
|
|
270
312
|
|
|
271
313
|
function printResult(result, targetDir) {
|
|
272
314
|
if (result.success) {
|
|
273
|
-
if (result.dryRun) {
|
|
274
|
-
console.log(pc.yellow('\nDry run: no files or directories were written.'));
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
315
|
+
if (result.dryRun) {
|
|
316
|
+
console.log(pc.yellow('\nDry run: no files or directories were written.'));
|
|
317
|
+
console.log(pc.bold('Project plan:'));
|
|
318
|
+
printSummary(result.summary);
|
|
319
|
+
printPlan(result.plan);
|
|
320
|
+
if (result.globalPlan) {
|
|
321
|
+
console.log(pc.bold('\nGlobal runtime plan:'));
|
|
322
|
+
console.log(` Directory ${pc.cyan(result.globalDir)}`);
|
|
323
|
+
printSummary(result.globalSummary);
|
|
324
|
+
printPlan(result.globalPlan);
|
|
325
|
+
}
|
|
326
|
+
printHostPlans(result.hostPlans, result.hostSummary);
|
|
327
|
+
if (result.warnings.length > 0) {
|
|
278
328
|
console.log(pc.yellow('\nWarning(s):'));
|
|
279
329
|
for (const warning of result.warnings) {
|
|
280
330
|
console.log(pc.yellow(` - ${warning}`));
|
|
@@ -283,11 +333,18 @@ function printResult(result, targetDir) {
|
|
|
283
333
|
console.log('');
|
|
284
334
|
return;
|
|
285
335
|
}
|
|
286
|
-
|
|
287
|
-
console.log(pc.green('\nGeneration complete.\n'));
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
336
|
+
|
|
337
|
+
console.log(pc.green('\nGeneration complete.\n'));
|
|
338
|
+
console.log(pc.bold('Project install:'));
|
|
339
|
+
printSummary(result.summary);
|
|
340
|
+
if (result.globalSummary) {
|
|
341
|
+
console.log(pc.bold('Global runtime:'));
|
|
342
|
+
console.log(` Directory ${pc.cyan(result.globalDir)}`);
|
|
343
|
+
printSummary(result.globalSummary);
|
|
344
|
+
}
|
|
345
|
+
printHostPlans(result.hostPlans, result.hostSummary, { summaryOnly: true });
|
|
346
|
+
|
|
347
|
+
printWarnings(result);
|
|
291
348
|
|
|
292
349
|
console.log(pc.bold('Next steps:'));
|
|
293
350
|
console.log(` ${pc.cyan(`cd ${targetDir}`)}`);
|
|
@@ -384,13 +441,31 @@ function parseArgs(args) {
|
|
|
384
441
|
flags.recommend.push(value);
|
|
385
442
|
}
|
|
386
443
|
} else if (arg === '--preset') {
|
|
387
|
-
const parsedValue = readValue('--preset', i);
|
|
388
|
-
flags.preset = parsedValue.value;
|
|
389
|
-
i = parsedValue.nextIndex;
|
|
390
|
-
} else if (arg.startsWith('--preset=')) {
|
|
391
|
-
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
392
|
-
} else if (arg
|
|
393
|
-
|
|
444
|
+
const parsedValue = readValue('--preset', i);
|
|
445
|
+
flags.preset = parsedValue.value;
|
|
446
|
+
i = parsedValue.nextIndex;
|
|
447
|
+
} else if (arg.startsWith('--preset=')) {
|
|
448
|
+
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
449
|
+
} else if (arg === '--install-scope') {
|
|
450
|
+
const parsedValue = readValue('--install-scope', i);
|
|
451
|
+
flags.installScope = parsedValue.value;
|
|
452
|
+
i = parsedValue.nextIndex;
|
|
453
|
+
} else if (arg.startsWith('--install-scope=')) {
|
|
454
|
+
flags.installScope = readEqualsValue('--install-scope', arg.slice('--install-scope='.length));
|
|
455
|
+
} else if (arg === '--global-dir') {
|
|
456
|
+
const parsedValue = readValue('--global-dir', i);
|
|
457
|
+
flags.globalDir = parsedValue.value;
|
|
458
|
+
i = parsedValue.nextIndex;
|
|
459
|
+
} else if (arg.startsWith('--global-dir=')) {
|
|
460
|
+
flags.globalDir = readEqualsValue('--global-dir', arg.slice('--global-dir='.length));
|
|
461
|
+
} else if (arg === '--host-global-dir') {
|
|
462
|
+
const parsedValue = readValue('--host-global-dir', i);
|
|
463
|
+
flags.hostGlobalDir = parsedValue.value;
|
|
464
|
+
i = parsedValue.nextIndex;
|
|
465
|
+
} else if (arg.startsWith('--host-global-dir=')) {
|
|
466
|
+
flags.hostGlobalDir = readEqualsValue('--host-global-dir', arg.slice('--host-global-dir='.length));
|
|
467
|
+
} else if (arg.startsWith('-')) {
|
|
468
|
+
errors.push(`Unknown flag "${arg}"`);
|
|
394
469
|
} else {
|
|
395
470
|
positionals.push(arg);
|
|
396
471
|
}
|
|
@@ -434,17 +509,30 @@ function printSummary(summary) {
|
|
|
434
509
|
console.log('');
|
|
435
510
|
}
|
|
436
511
|
|
|
437
|
-
function printPlan(plan) {
|
|
438
|
-
for (const [label, files] of Object.entries(plan)) {
|
|
439
|
-
if (!files.length) continue;
|
|
512
|
+
function printPlan(plan) {
|
|
513
|
+
for (const [label, files] of Object.entries(plan)) {
|
|
514
|
+
if (!files.length) continue;
|
|
440
515
|
console.log(` ${label}:`);
|
|
441
516
|
for (const file of files) {
|
|
442
517
|
console.log(` - ${file}`);
|
|
443
518
|
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function printHostPlans(hostPlans = [], hostSummary = [], { summaryOnly = false } = {}) {
|
|
523
|
+
if (!hostPlans?.length) return;
|
|
524
|
+
|
|
525
|
+
const summariesByHost = new Map((hostSummary || []).map(item => [item.host, item.summary]));
|
|
526
|
+
console.log(pc.bold('\nHost-global copies:'));
|
|
527
|
+
for (const hostPlan of hostPlans) {
|
|
528
|
+
console.log(` ${hostPlan.host} ${pc.cyan(hostPlan.root)}`);
|
|
529
|
+
const summary = summariesByHost.get(hostPlan.host);
|
|
530
|
+
if (summary) printSummary(summary);
|
|
531
|
+
if (!summaryOnly) printPlan(hostPlan.plan);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function printWarnings(result) {
|
|
448
536
|
if (!result.warnings.length) return;
|
|
449
537
|
|
|
450
538
|
console.log(pc.yellow('\nWarning(s):'));
|
|
@@ -455,7 +543,7 @@ function printWarnings(result) {
|
|
|
455
543
|
|
|
456
544
|
function printJsonResult(result) {
|
|
457
545
|
// Remove `created` array from output — it is already in the plan, avoid duplication
|
|
458
|
-
const { created, ...rest } = result;
|
|
546
|
+
const { created, globalCreated, hostCreated, ...rest } = result;
|
|
459
547
|
console.log(JSON.stringify(rest, null, 2));
|
|
460
548
|
if (!result.success) {
|
|
461
549
|
process.exit(1);
|
|
@@ -703,12 +791,44 @@ function createJsonScan(scan) {
|
|
|
703
791
|
}
|
|
704
792
|
|
|
705
793
|
function createAgentGuidance(result, { projectName, targetDir, options, scan }) {
|
|
706
|
-
const
|
|
794
|
+
const projectAttentionFiles = [...new Set([
|
|
707
795
|
...(result.plan?.conflict || []),
|
|
708
796
|
...(result.plan?.skip || []),
|
|
709
797
|
])].sort();
|
|
710
|
-
const
|
|
711
|
-
|
|
798
|
+
const globalAttentionFiles = [...new Set([
|
|
799
|
+
...(result.globalPlan?.conflict || []),
|
|
800
|
+
...(result.globalPlan?.skip || []),
|
|
801
|
+
])].sort();
|
|
802
|
+
const hostAttentionFiles = (result.hostPlans || []).flatMap(hostPlan => (
|
|
803
|
+
[...new Set([
|
|
804
|
+
...(hostPlan.plan?.conflict || []),
|
|
805
|
+
...(hostPlan.plan?.skip || []),
|
|
806
|
+
])].sort().map(file => ({ host: hostPlan.host, root: hostPlan.root, file }))
|
|
807
|
+
));
|
|
808
|
+
const aiMergeRequired = [
|
|
809
|
+
...projectAttentionFiles.map(file => createFileGuidance(file)),
|
|
810
|
+
...globalAttentionFiles.map(file => {
|
|
811
|
+
const guidance = createFileGuidance(file);
|
|
812
|
+
return {
|
|
813
|
+
...guidance,
|
|
814
|
+
file: `global:${guidance.file}`,
|
|
815
|
+
scope: 'global-runtime',
|
|
816
|
+
reason: `Global runtime file in ${result.globalDir || 'the selected global directory'} needs review. ${guidance.reason}`,
|
|
817
|
+
};
|
|
818
|
+
}),
|
|
819
|
+
...hostAttentionFiles.map(({ host, root, file }) => {
|
|
820
|
+
const guidance = createFileGuidance(file);
|
|
821
|
+
return {
|
|
822
|
+
...guidance,
|
|
823
|
+
file: `host:${host}:${guidance.file}`,
|
|
824
|
+
scope: `host-global:${host}`,
|
|
825
|
+
reason: `Host-global ${host} file in ${root} needs review. ${guidance.reason}`,
|
|
826
|
+
};
|
|
827
|
+
}),
|
|
828
|
+
];
|
|
829
|
+
const hasBlockingConflicts = (result.plan?.conflict || []).length > 0
|
|
830
|
+
|| (result.globalPlan?.conflict || []).length > 0
|
|
831
|
+
|| (result.hostPlans || []).some(hostPlan => (hostPlan.plan?.conflict || []).length > 0);
|
|
712
832
|
const safeMergeCommand = commandFor(projectName, targetDir, {
|
|
713
833
|
...options,
|
|
714
834
|
dryRun: false,
|
|
@@ -769,6 +889,14 @@ function createAgentGuidance(result, { projectName, targetDir, options, scan })
|
|
|
769
889
|
mkdir: result.plan?.mkdir?.length || 0,
|
|
770
890
|
backup: result.plan?.backup?.length || 0,
|
|
771
891
|
overwrite: result.plan?.overwrite?.length || 0,
|
|
892
|
+
globalCreate: result.globalPlan?.create?.length || 0,
|
|
893
|
+
globalMkdir: result.globalPlan?.mkdir?.length || 0,
|
|
894
|
+
globalBackup: result.globalPlan?.backup?.length || 0,
|
|
895
|
+
globalOverwrite: result.globalPlan?.overwrite?.length || 0,
|
|
896
|
+
hostCreate: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.create?.length || 0), 0),
|
|
897
|
+
hostMkdir: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.mkdir?.length || 0), 0),
|
|
898
|
+
hostBackup: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.backup?.length || 0), 0),
|
|
899
|
+
hostOverwrite: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.overwrite?.length || 0), 0),
|
|
772
900
|
},
|
|
773
901
|
aiMergeRequired,
|
|
774
902
|
next,
|
|
@@ -839,6 +967,9 @@ function commandFor(projectName, targetDir, options) {
|
|
|
839
967
|
if (options.withoutOptions?.length) args.push('--without', options.withoutOptions.join(','));
|
|
840
968
|
if (options.externalOptions?.length) args.push('--recommend', options.externalOptions.join(','));
|
|
841
969
|
if (options.preset) args.push('--preset', options.preset);
|
|
970
|
+
if (options.installScope && options.installScope !== 'project') args.push('--install-scope', options.installScope);
|
|
971
|
+
if (options.globalDir) args.push('--global-dir', options.globalDir);
|
|
972
|
+
if (options.hostGlobalDir) args.push('--host-global-dir', options.hostGlobalDir);
|
|
842
973
|
if (options.json) args.push('--json');
|
|
843
974
|
|
|
844
975
|
return args.map(shellQuoteArg).join(' ');
|
package/src/prompts.js
CHANGED
|
@@ -40,6 +40,24 @@ export async function askTargetDir(projectName) {
|
|
|
40
40
|
return dir.trim();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export async function askInstallScope() {
|
|
44
|
+
const scope = await p.select({
|
|
45
|
+
message: 'Harness install scope?',
|
|
46
|
+
initialValue: 'project',
|
|
47
|
+
options: [
|
|
48
|
+
{ value: 'project', label: 'Project-local', hint: 'full Harness scaffold in this project' },
|
|
49
|
+
{ value: 'global', label: 'Global + project state', hint: 'shared runtime plus project-local tasks/memory' },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (p.isCancel(scope)) {
|
|
54
|
+
p.cancel('Cancelled');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return scope;
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
export async function askConflictPolicy(scan) {
|
|
44
62
|
const reason = scan.hasHarness
|
|
45
63
|
? 'Target already has Harness/. Choose how to handle existing files.'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run perpetual inspiration mode via the wf-auto-spark skill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /wf-auto-spark
|
|
6
|
+
|
|
7
|
+
This is a **workflow command**, not a direct command. Do not execute it as a
|
|
8
|
+
static help or script command.
|
|
9
|
+
|
|
10
|
+
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
|
+
2. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
12
|
+
3. Execute per the skill adapter `.claude/skills/wf-auto-spark/SKILL.md` (mirror: `.agents/skills/wf-auto-spark/SKILL.md`).
|
|
13
|
+
4. Do not duplicate the workflow here. The skill adapter and `Harness/specs/workflows/WF-AUTO-SPARK.md` are authoritative.
|
|
14
|
+
|
|
15
|
+
If this runtime cannot invoke the skill directly, read
|
|
16
|
+
`.claude/skills/wf-auto-spark/SKILL.md` and follow it in place.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run perpetual adaptive auto-optimization via the wf-auto skill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /wf-auto
|
|
6
|
+
|
|
7
|
+
This is a **workflow command**, not a direct command. Do not execute it as a
|
|
8
|
+
static help or script command.
|
|
9
|
+
|
|
10
|
+
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
|
+
2. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
12
|
+
3. Execute per the skill adapter `.claude/skills/wf-auto/SKILL.md` (mirror: `.agents/skills/wf-auto/SKILL.md`).
|
|
13
|
+
4. Do not duplicate the workflow here. The skill adapter and `Harness/specs/workflows/WF-AUTO.md` are authoritative.
|
|
14
|
+
|
|
15
|
+
If this runtime cannot invoke the skill directly, read
|
|
16
|
+
`.claude/skills/wf-auto/SKILL.md` and follow it in place.
|
package/templates/{optional/skills/browser-e2e/.opencode → common/.claude}/commands/wf-browser.md
RENAMED
|
@@ -10,7 +10,7 @@ static help or script command.
|
|
|
10
10
|
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
11
|
2. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
12
12
|
3. Execute per the skill adapter `.claude/skills/wf-browser/SKILL.md` (mirror: `.agents/skills/wf-browser/SKILL.md`).
|
|
13
|
-
4. Do not duplicate the workflow here. The skill adapter and `Harness/
|
|
13
|
+
4. Do not duplicate the workflow here. The skill adapter and `Harness/specs/protocols/HARNESS_BRIDGE.md` are authoritative.
|
|
14
14
|
|
|
15
15
|
If this runtime cannot invoke the skill directly, read
|
|
16
16
|
`.claude/skills/wf-browser/SKILL.md` and follow it in place.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# /wf-command-create
|
|
2
|
+
|
|
3
|
+
Create or modify a Harness wf-* command surface atomically. Do not invoke a skill or start WF mode.
|
|
4
|
+
|
|
5
|
+
## Classification
|
|
6
|
+
|
|
7
|
+
DIRECT command. It is a Harness maintenance command: it must create or resume a task capsule, then update every command surface declared by `Harness/specs/runtime/command-surface.json`. Never loads `Harness/MEMORY.md` as part of command routing.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
/wf-command-create <wf-command-id> [--direct|--workflow] [--task <task-id>]
|
|
12
|
+
|
|
13
|
+
- Command id must be `wf` or `wf-*` kebab-case.
|
|
14
|
+
- Use an existing task capsule when `--task` is supplied or when the active task clearly matches.
|
|
15
|
+
- Otherwise create a `task-<verb>-<noun>[-detail]` capsule with `node Harness/scripts/task-state.mjs record <task-id> --create --apply --json`.
|
|
16
|
+
- Record the command id, classification, and validation evidence in the task capsule.
|
|
17
|
+
|
|
18
|
+
## Required Surface Checklist
|
|
19
|
+
|
|
20
|
+
Update all applicable files before returning:
|
|
21
|
+
|
|
22
|
+
- `Harness/specs/runtime/command-surface.json`
|
|
23
|
+
- `.claude/commands/<id>.md`
|
|
24
|
+
- `.opencode/commands/<id>.md`
|
|
25
|
+
- `.claude/skills/<id>/SKILL.md` when Codex compatibility or workflow routing is needed
|
|
26
|
+
- `.agents/skills/<id>/SKILL.md` mirror when a Claude skill exists
|
|
27
|
+
- Matching `templates/common/...` files
|
|
28
|
+
- `CLAUDE.md` and `templates/common/CLAUDE.md`
|
|
29
|
+
- `.claude/rules/ecc/common.md` and template mirror
|
|
30
|
+
- `.claude/commands/wf-help.md`, `.opencode/commands/wf-help.md`, and template mirrors
|
|
31
|
+
- `Harness/README.md`, `Harness/MEMORY.md`, and template mirrors
|
|
32
|
+
- `Harness/scripts/validate-harness.mjs` and template mirror
|
|
33
|
+
- `Harness/scripts/wf-remove.mjs` and template mirror
|
|
34
|
+
- `tests/generator.test.js`, `tests/anti-drift.test.js`, and `tests/validate-harness.test.js`
|
|
35
|
+
- `Harness/.harness-version`, `templates/common/.harness-version`, and ownership manifests via `node scripts/build-version.mjs`
|
|
36
|
+
|
|
37
|
+
## Validation
|
|
38
|
+
|
|
39
|
+
Run, at minimum:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm test
|
|
43
|
+
node Harness/scripts/validate-harness.mjs
|
|
44
|
+
node templates/common/Harness/scripts/validate-harness.mjs
|
|
45
|
+
node Harness/scripts/context-budget.mjs --json
|
|
46
|
+
node scripts/build-version.mjs --check
|
|
47
|
+
npm run check:mirrors
|
|
48
|
+
git diff --check
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If a check is blocked by an existing unrelated repository issue, record the command, failure, and reason in the task capsule.
|
|
52
|
+
|
|
53
|
+
## Return
|
|
54
|
+
|
|
55
|
+
- Command id and classification.
|
|
56
|
+
- Task capsule path.
|
|
57
|
+
- Files changed by surface category.
|
|
58
|
+
- Verification results.
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
Return this help table directly. Do not invoke a skill, do not start WF mode,
|
|
4
4
|
do not dispatch agents, and do not edit files.
|
|
5
5
|
|
|
6
|
+
Codex compatibility: `$wf-help` or `/skills wf-help` loads the `wf-help` shim,
|
|
7
|
+
which returns this same table without loading `Harness/MEMORY.md` or entering WF.
|
|
8
|
+
|
|
6
9
|
| Command | Type | Usage | Purpose |
|
|
7
10
|
| --- | --- | --- | --- |
|
|
8
11
|
| `/wf-help` | direct command | `/wf-help` | Show this command table. |
|
|
@@ -12,14 +15,18 @@ do not dispatch agents, and do not edit files.
|
|
|
12
15
|
| `/wf-auto-spark` | workflow skill | `/wf-auto-spark` | Perpetual inspiration mode with roadmap anchoring and external spark search. |
|
|
13
16
|
| `/wf-review <focus>` | workflow skill | `/wf-review security and test coverage` | Peer CLI review through Claude/Codex/OpenCode, with reviewer subagent fallback. |
|
|
14
17
|
| `/wf-learn` | workflow skill | `/wf-learn` | Force context-master -> memory-master learning cycle after repeated failures or closeout. |
|
|
15
|
-
| `/wf-browser <task>` |
|
|
18
|
+
| `/wf-browser <task>` | workflow skill | `/wf-browser verify checkout flow` | Built-in browser automation/E2E workflow with real UI interaction, screenshots, traces, and CDP/network evidence. |
|
|
16
19
|
| `/wf-readme <task>` | workflow skill | `/wf-readme polish quickstart` | Preserve, merge, or improve README docs without trampling existing project documentation. |
|
|
17
20
|
| `/wf-update` | direct command | `/wf-update` | Check/apply Harness scaffold updates with safe file classification and conflict handling. |
|
|
18
21
|
| `/wf-remove` | workflow skill | `/wf-remove` | Safely remove Harness files while preserving project/user data unless explicitly purged. |
|
|
22
|
+
| `/wf-task-record <task-id>` | direct command | `/wf-task-record my-feature --create` | Record user intent into a task capsule (wraps task-state.mjs). |
|
|
23
|
+
| `/wf-task-list` | direct command | `/wf-task-list` | List all task capsules with status, phase, and dependencies. |
|
|
24
|
+
| `/wf-task-archive [--apply]` | direct command | `/wf-task-archive --apply` | Archive completed task capsules (dry-run by default). |
|
|
25
|
+
| `/wf-command-create <wf-command-id>` | direct command | `/wf-command-create wf-report --direct` | Create or modify wf-* command surfaces atomically from the command registry. |
|
|
19
26
|
|
|
20
27
|
Source of truth: `Harness/README.md#Skill Commands` plus installed skills under
|
|
21
|
-
`.claude/skills/` (Claude Code) or `.agents/skills/`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
`.claude/skills/` (Claude Code and OpenCode adapters) or `.agents/skills/`
|
|
29
|
+
(Codex). In OpenCode, every workflow command above is visible as a thin command
|
|
30
|
+
wrapper under `.opencode/commands/` (e.g. `/wf`, `/wf-max`); each wrapper routes
|
|
31
|
+
to the matching `.claude/skills/<command>/SKILL.md` adapter and does not
|
|
32
|
+
duplicate the workflow.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run the context-master -> memory-master learning cycle via the wf-learn skill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /wf-learn
|
|
6
|
+
|
|
7
|
+
This is a **workflow command**, not a direct command. Do not execute it as a
|
|
8
|
+
static help or script command.
|
|
9
|
+
|
|
10
|
+
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
|
+
2. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
12
|
+
3. Execute per the skill adapter `.claude/skills/wf-learn/SKILL.md` (mirror: `.agents/skills/wf-learn/SKILL.md`).
|
|
13
|
+
4. Do not duplicate the workflow here. The skill adapter and `Harness/specs/protocols/MEMORY_PROTOCOL.md` are authoritative.
|
|
14
|
+
|
|
15
|
+
If this runtime cannot invoke the skill directly, read
|
|
16
|
+
`.claude/skills/wf-learn/SKILL.md` and follow it in place.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run WF-MAX with mandatory subagent fan-out and recorded degradation
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /wf-max
|
|
6
|
+
|
|
7
|
+
This is a **workflow command**, not a direct command. Do not execute it as a
|
|
8
|
+
static help or script command.
|
|
9
|
+
|
|
10
|
+
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
|
+
2. Load and follow `.claude/skills/wf-max/SKILL.md` (mirror: `.agents/skills/wf-max/SKILL.md`) and `Harness/specs/workflows/WF-MAX.md`.
|
|
12
|
+
3. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
13
|
+
4. MUST attempt native runtime subagent fan-out before planning is considered complete:
|
|
14
|
+
- start `task-scribe` when available for task ledger/state;
|
|
15
|
+
- start W0 exploration through manager/explorer/researcher roles with disjoint read sets;
|
|
16
|
+
- when running in OpenCode, require project `opencode.json` to have `subagent_depth >= 2` and manager agents to have `permission.task` allowlists for manager -> worker fan-out;
|
|
17
|
+
- if nested manager fan-out is unavailable, dispatch leaf workers directly from the primary agent with exact WF-MAX dispatch packets.
|
|
18
|
+
5. In every runtime, MUST attempt native subagent fan-out before implementation planning is considered complete; never silently continue as a solo controller.
|
|
19
|
+
6. If native fan-out fails, record `fanoutAttempted: true`, runtime, channel tried, agents requested, failure/degradation reason, and the fallback path in `Harness/tasks/<task-id>/PLAN.md` or `PROGRESS.md`.
|
|
20
|
+
7. Continue with WF-Max-Useful by default or WF-Max-Strict only when the user explicitly requests strict mode.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run the README preservation and improvement workflow via the wf-readme skill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /wf-readme
|
|
6
|
+
|
|
7
|
+
This is a **workflow command**, not a direct command. Do not execute it as a
|
|
8
|
+
static help or script command.
|
|
9
|
+
|
|
10
|
+
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
|
+
2. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
12
|
+
3. Execute per the skill adapter `.claude/skills/wf-readme/SKILL.md` (mirror: `.agents/skills/wf-readme/SKILL.md`).
|
|
13
|
+
4. Do not duplicate the workflow here. The skill adapter and the project root \`README.md\` ownership rules are authoritative.
|
|
14
|
+
|
|
15
|
+
If this runtime cannot invoke the skill directly, read
|
|
16
|
+
`.claude/skills/wf-readme/SKILL.md` and follow it in place.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run safe harness removal via the wf-remove skill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /wf-remove
|
|
6
|
+
|
|
7
|
+
This is a **workflow command**, not a direct command. Do not execute it as a
|
|
8
|
+
static help or script command.
|
|
9
|
+
|
|
10
|
+
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
|
+
2. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
12
|
+
3. Execute per the skill adapter `.claude/skills/wf-remove/SKILL.md` (mirror: `.agents/skills/wf-remove/SKILL.md`).
|
|
13
|
+
4. Do not duplicate the workflow here. The skill adapter and `Harness/scripts/wf-remove.mjs` are authoritative.
|
|
14
|
+
|
|
15
|
+
If this runtime cannot invoke the skill directly, read
|
|
16
|
+
`.claude/skills/wf-remove/SKILL.md` and follow it in place.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run peer CLI or reviewer-subagent review via the wf-review skill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /wf-review
|
|
6
|
+
|
|
7
|
+
This is a **workflow command**, not a direct command. Do not execute it as a
|
|
8
|
+
static help or script command.
|
|
9
|
+
|
|
10
|
+
1. Load `CLAUDE.md`, `Harness/MEMORY.md` (index only per Memory Preflight), then `Harness/README.md`.
|
|
11
|
+
2. Preserve cache-first order per `Harness/specs/runtime/context-loading.md#Cache-First Context Contract`.
|
|
12
|
+
3. Execute per the skill adapter `.claude/skills/wf-review/SKILL.md` (mirror: `.agents/skills/wf-review/SKILL.md`).
|
|
13
|
+
4. Do not duplicate the workflow here. The skill adapter and the wf-review peer-review contract are authoritative.
|
|
14
|
+
|
|
15
|
+
If this runtime cannot invoke the skill directly, read
|
|
16
|
+
`.claude/skills/wf-review/SKILL.md` and follow it in place.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# /wf-task-archive
|
|
2
|
+
|
|
3
|
+
Archive completed/obsolete task capsules to `Harness/tasks/_archive/`. Do not invoke a skill or start WF mode.
|
|
4
|
+
|
|
5
|
+
## Classification
|
|
6
|
+
|
|
7
|
+
DIRECT command. Wraps `node Harness/scripts/task-state.mjs archive`. Never loads Harness/MEMORY.md.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
/wf-task-archive [--dry-run] [--apply] [--task <id>] [--keep <n>]
|
|
12
|
+
|
|
13
|
+
- Defaults to dry-run (`--dry-run`) without `--apply`.
|
|
14
|
+
- With `--apply`: moves eligible task directories into yearly subdirectories under `_archive/`.
|
|
15
|
+
- With `--task <id>`: targets a specific task for archive eligibility check.
|
|
16
|
+
- With `--keep <n>`: keeps N most recent non-archived task capsules (default 5).
|
|
17
|
+
- Tasks with active, blocked, in_progress, running, pending, or needs-user-decision status are never auto-archived.
|
|
18
|
+
|
|
19
|
+
## Execution
|
|
20
|
+
|
|
21
|
+
Run: `node Harness/scripts/task-state.mjs archive [--dry-run] [--apply] [--task <id>] [--keep <n>] [--json]`
|
|
22
|
+
|
|
23
|
+
## Return
|
|
24
|
+
|
|
25
|
+
- JSON output with archive plan: scanned, archiveable, toArchive, kept, skipped counts and per-task results.
|
|
26
|
+
- If the command fails, report the error and do not retry.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# /wf-task-list
|
|
2
|
+
|
|
3
|
+
List Harness task capsules with state, phase, status, and dependency info. Do not invoke a skill or start WF mode.
|
|
4
|
+
|
|
5
|
+
## Classification
|
|
6
|
+
|
|
7
|
+
DIRECT command. Wraps `node Harness/scripts/task-state.mjs list --json`. Never loads Harness/MEMORY.md.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
/wf-task-list [--json]
|
|
12
|
+
|
|
13
|
+
- By default, returns a human-readable listing of all task capsules.
|
|
14
|
+
- With --json, returns structured JSON output.
|
|
15
|
+
- Shows active, open, blocked, verified tasks with dependency info.
|
|
16
|
+
|
|
17
|
+
## Execution
|
|
18
|
+
|
|
19
|
+
Run: `node Harness/scripts/task-state.mjs list --json`
|
|
20
|
+
|
|
21
|
+
## Return
|
|
22
|
+
|
|
23
|
+
- JSON output of all task capsules with status, phase, dependencies, and archive eligibility.
|
|
24
|
+
- If the command fails, report the error and do not retry.
|