create-harness-vibe-coding 0.8.18 → 0.8.19
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 +13 -0
- package/README.md +1 -0
- package/package.json +5 -2
- package/src/index.js +383 -267
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js +17 -0
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js.map +1 -0
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js +7 -0
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js.map +1 -0
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js +12 -0
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js.map +1 -0
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js +50 -0
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js.map +1 -0
- package/src/ui/dist/assets/TerminalDrawer-6GBZ9nXN.css +32 -0
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js +74 -0
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js.map +1 -0
- package/src/ui/dist/assets/WorkflowRoute-BnuhLJ6X.css +1 -0
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js +39 -0
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js.map +1 -0
- package/src/ui/dist/assets/index-BOVYWntB.js +117 -0
- package/src/ui/dist/assets/index-BOVYWntB.js.map +1 -0
- package/src/ui/dist/assets/index-DaUObq0H.css +1 -0
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js +7 -0
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js.map +1 -0
- package/src/ui/dist/assets/plus-QuTJyoT3.js +7 -0
- package/src/ui/dist/assets/plus-QuTJyoT3.js.map +1 -0
- package/src/ui/dist/assets/proxy-D0vUbds5.js +2 -0
- package/src/ui/dist/assets/proxy-D0vUbds5.js.map +1 -0
- package/src/ui/dist/assets/refresh-cw-JNmExijd.js +7 -0
- package/src/ui/dist/assets/refresh-cw-JNmExijd.js.map +1 -0
- package/src/ui/dist/assets/terminal-BBQvnOAK.js +7 -0
- package/src/ui/dist/assets/terminal-BBQvnOAK.js.map +1 -0
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js +7 -0
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js.map +1 -0
- package/src/ui/dist/assets/x-DoIOGAJz.js +7 -0
- package/src/ui/dist/assets/x-DoIOGAJz.js.map +1 -0
- package/src/ui/dist/index.html +18 -0
- package/src/ui/index.html +17 -0
- package/src/ui/package.json +33 -0
- package/src/ui/pnpm-lock.yaml +1582 -0
- package/src/ui/pnpm-workspace.yaml +2 -0
- package/src/ui/src/App.tsx +86 -0
- package/src/ui/src/api.ts +93 -0
- package/src/ui/src/components/AgentsRoute.tsx +503 -0
- package/src/ui/src/components/Footer.tsx +69 -0
- package/src/ui/src/components/Header.tsx +175 -0
- package/src/ui/src/components/LoadingView.tsx +22 -0
- package/src/ui/src/components/RolesRoute.tsx +169 -0
- package/src/ui/src/components/SettingsRoute.tsx +166 -0
- package/src/ui/src/components/TaskList.tsx +388 -0
- package/src/ui/src/components/TerminalDrawer.tsx +611 -0
- package/src/ui/src/components/WorkflowRoute.tsx +670 -0
- package/src/ui/src/hooks/useReducedMotion.ts +17 -0
- package/src/ui/src/hooks/useServerConnection.ts +114 -0
- package/src/ui/src/index.css +281 -0
- package/src/ui/src/main.tsx +11 -0
- package/src/ui/src/types.ts +108 -0
- package/src/ui/tsconfig.json +21 -0
- package/src/ui/vite.config.ts +19 -0
- package/src/wf-ui-server/__tests__/a2a-store.test.mjs +79 -0
- package/src/wf-ui-server/__tests__/peer-capsule.test.mjs +268 -0
- package/src/wf-ui-server/__tests__/pty-adapter.test.mjs +70 -0
- package/src/wf-ui-server/__tests__/runtime-config.test.mjs +43 -0
- package/src/wf-ui-server/__tests__/runtime-detector.test.mjs +90 -0
- package/src/wf-ui-server/__tests__/security.test.mjs +59 -0
- package/src/wf-ui-server/__tests__/server.integration.test.mjs +150 -0
- package/src/wf-ui-server/__tests__/session-registry.test.mjs +238 -0
- package/src/wf-ui-server/__tests__/settings.test.mjs +135 -0
- package/src/wf-ui-server/__tests__/task-parser.test.mjs +200 -0
- package/src/wf-ui-server/__tests__/terminal-store.test.mjs +138 -0
- package/src/wf-ui-server/__tests__/token.test.mjs +48 -0
- package/src/wf-ui-server/__tests__/ws-events.integration.test.mjs +419 -0
- package/src/wf-ui-server/__tests__/ws-terminal.integration.test.mjs +383 -0
- package/src/wf-ui-server/a2a-store.mjs +296 -0
- package/src/wf-ui-server/peer-capsule.mjs +213 -0
- package/src/wf-ui-server/pty-adapter.mjs +155 -0
- package/src/wf-ui-server/runtime-config.mjs +153 -0
- package/src/wf-ui-server/runtime-detector.mjs +374 -0
- package/src/wf-ui-server/security.mjs +67 -0
- package/src/wf-ui-server/server.mjs +849 -0
- package/src/wf-ui-server/session-registry.mjs +204 -0
- package/src/wf-ui-server/settings.mjs +100 -0
- package/src/wf-ui-server/task-parser.mjs +133 -0
- package/src/wf-ui-server/terminal-store.mjs +225 -0
- package/src/wf-ui-server/token.mjs +41 -0
- package/src/wf-ui-server/ws-events.mjs +354 -0
- package/src/wf-ui-server/ws-terminal.mjs +473 -0
- package/templates/common/.claude/commands/wf-help.md +1 -0
- package/templates/common/.claude/commands/wf-task-list.md +2 -0
- package/templates/common/.claude/commands/wf-ui.md +26 -0
- package/templates/common/.claude/commands/wf-update.md +54 -9
- package/templates/common/.claude/rules/ecc/common.md +1 -1
- package/templates/common/.claude/skills/wf-auto/SKILL.md +3 -3
- package/templates/common/.claude/skills/wf-auto-spark/SKILL.md +2 -2
- package/templates/common/.claude/skills/wf-ui/SKILL.md +78 -0
- package/templates/common/.claude/skills/wf-update/SKILL.md +55 -58
- package/templates/common/.harness-version +63 -39
- package/templates/common/.opencode/commands/wf-help.md +1 -0
- package/templates/common/.opencode/commands/wf-task-list.md +2 -0
- package/templates/common/.opencode/commands/wf-ui.md +26 -0
- package/templates/common/.opencode/commands/wf-update.md +54 -9
- package/templates/common/CLAUDE.md +1 -1
- package/templates/common/Harness/MEMORY.md +2 -0
- package/templates/common/Harness/README.md +6 -4
- package/templates/common/Harness/a2a/role-graph.json +79 -0
- package/templates/common/Harness/a2a/runtime-registry.json +5 -0
- package/templates/common/Harness/a2a/skills/terminal-control.json +15 -0
- package/templates/common/Harness/ownership.manifest.json +57 -2
- package/templates/common/Harness/scripts/README.md +134 -0
- package/templates/common/Harness/scripts/a2a-terminal.mjs +191 -0
- package/templates/common/Harness/scripts/context-budget.mjs +1 -1
- package/templates/common/Harness/scripts/sync-host-global.mjs +278 -0
- package/templates/common/Harness/scripts/task-state.mjs +556 -23
- package/templates/common/Harness/scripts/validate-harness.mjs +229 -19
- package/templates/common/Harness/scripts/wf-remove.mjs +8 -3
- package/templates/common/Harness/scripts/wf-update-check.mjs +37 -9
- package/templates/common/Harness/scripts/wf-update-runner.mjs +325 -0
- package/templates/common/Harness/settings.json +35 -0
- package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +7 -2
- package/templates/common/Harness/specs/runtime/command-surface.json +14 -0
- package/templates/common/Harness/specs/workflows/WF-AUTO-SPARK.md +8 -8
- package/templates/common/Harness/specs/workflows/WF-AUTO.md +12 -12
- package/templates/common/Harness/tasks/_template/NAMING.md +16 -16
- package/templates/common/Harness/tasks/_template/PLAN.md +17 -60
- package/templates/common/Harness/tasks/_template/PROBLEM.md +18 -0
- package/templates/common/Harness/tasks/_template/PROGRESS.md +9 -14
- package/templates/common/Harness/tasks/_template/REFERENCES.md +26 -0
- package/templates/common/Harness/tasks/_template/ARTIFACTS.md +0 -3
- package/templates/common/Harness/tasks/_template/NOTES.md +0 -3
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
4
5
|
|
|
5
6
|
const root = process.cwd();
|
|
6
7
|
const args = new Set(process.argv.slice(2));
|
|
7
8
|
const strict = args.has('--strict') || args.has('--post-bootstrap');
|
|
9
|
+
const manifestAudit = args.has('--manifest-audit');
|
|
8
10
|
|
|
9
11
|
if (args.has('--help') || args.has('-h')) {
|
|
10
|
-
console.log(`Usage: node Harness/scripts/validate-harness.mjs [--strict]
|
|
12
|
+
console.log(`Usage: node Harness/scripts/validate-harness.mjs [--strict] [--manifest-audit]
|
|
11
13
|
|
|
12
14
|
Default mode checks scaffold structure, links, agents, and skills.
|
|
13
15
|
--strict also fails when project fact docs still contain unresolved {{TOKEN}} placeholders.
|
|
14
|
-
|
|
16
|
+
--manifest-audit cross-references ownership.manifest.json against disk: flags missing framework files (manifest-to-disk) and extra files in harness directories (disk-to-manifest).`);
|
|
15
17
|
process.exit(0);
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -150,8 +152,10 @@ const required = [
|
|
|
150
152
|
'Harness/scripts/context-budget.mjs',
|
|
151
153
|
'Harness/scripts/l2-cache-telemetry.mjs',
|
|
152
154
|
'Harness/scripts/wf-update-check.mjs',
|
|
155
|
+
'Harness/scripts/wf-update-runner.mjs',
|
|
153
156
|
'Harness/scripts/wf-remove.mjs',
|
|
154
157
|
'Harness/scripts/scan-clean.mjs',
|
|
158
|
+
'Harness/scripts/sync-host-global.mjs',
|
|
155
159
|
'Harness/scripts/task-state.mjs',
|
|
156
160
|
'Harness/scripts/archive-tasks.mjs',
|
|
157
161
|
'Harness/specs/workflows/WF-STATE.md',
|
|
@@ -262,6 +266,48 @@ function resolveMetadataPath(value) {
|
|
|
262
266
|
return path.isAbsolute(value) ? value : path.resolve(root, value);
|
|
263
267
|
}
|
|
264
268
|
|
|
269
|
+
function containsPath(parent, child) {
|
|
270
|
+
const resolvedParent = path.resolve(parent);
|
|
271
|
+
const resolvedChild = path.resolve(child);
|
|
272
|
+
const rel = path.relative(resolvedParent, resolvedChild);
|
|
273
|
+
return rel === '' || (rel && !rel.startsWith('..') && !path.isAbsolute(rel));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function resolveUnder(base, rel) {
|
|
277
|
+
if (!rel || typeof rel !== 'string' || path.isAbsolute(rel)) return null;
|
|
278
|
+
const parts = rel.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
279
|
+
if (parts.some(part => part === '..')) return null;
|
|
280
|
+
const resolved = path.resolve(base, ...parts);
|
|
281
|
+
return containsPath(base, resolved) ? resolved : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function sha256Content(content) {
|
|
285
|
+
return 'sha256-' + createHash('sha256').update(content.replace(/\r\n/g, '\n')).digest('hex');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function sha256File(filePath) {
|
|
289
|
+
if (!fs.existsSync(filePath)) return null;
|
|
290
|
+
return sha256Content(fs.readFileSync(filePath, 'utf8'));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function sourceDestForHostFile(host, file) {
|
|
294
|
+
const normalized = String(file || '').replace(/\\/g, '/');
|
|
295
|
+
if (host === 'claude') {
|
|
296
|
+
if (normalized === 'settings.json') return '.claude/settings.json';
|
|
297
|
+
if (/^(commands|skills|agents|rules)\//.test(normalized)) return `.claude/${normalized}`;
|
|
298
|
+
}
|
|
299
|
+
if (host === 'codex') {
|
|
300
|
+
if (normalized === 'config.toml') return '.codex/config.toml';
|
|
301
|
+
if (normalized === 'hooks.json') return '.codex/hooks.json';
|
|
302
|
+
if (normalized.startsWith('skills/')) return `.agents/${normalized}`;
|
|
303
|
+
}
|
|
304
|
+
if (host === 'opencode') {
|
|
305
|
+
if (normalized === 'opencode.json') return 'opencode.json';
|
|
306
|
+
if (/^(commands|agents|plugins)\//.test(normalized)) return `.opencode/${normalized}`;
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
265
311
|
function requireText(rel, text, label = text) {
|
|
266
312
|
const body = read(rel);
|
|
267
313
|
if (body && !body.includes(text)) errors.push(`${rel} missing ${label}`);
|
|
@@ -304,10 +350,11 @@ function validateHostGlobalTargets() {
|
|
|
304
350
|
}
|
|
305
351
|
|
|
306
352
|
const minimumFiles = {
|
|
307
|
-
claude: ['commands/wf.md', 'skills/wf/SKILL.md'],
|
|
308
|
-
codex: ['skills/wf/SKILL.md'],
|
|
309
|
-
opencode: ['commands/wf.md'],
|
|
353
|
+
claude: ['commands/wf.md', 'commands/wf-ui.md', 'skills/wf/SKILL.md', 'skills/wf-ui/SKILL.md'],
|
|
354
|
+
codex: ['skills/wf/SKILL.md', 'skills/wf-ui/SKILL.md'],
|
|
355
|
+
opencode: ['commands/wf.md', 'commands/wf-ui.md'],
|
|
310
356
|
};
|
|
357
|
+
const runtimeRoot = isGlobalRuntimeRoot ? root : resolveMetadataPath(installMetadata.globalDir);
|
|
311
358
|
|
|
312
359
|
for (const [host, requiredFiles] of Object.entries(minimumFiles)) {
|
|
313
360
|
const target = hostGlobal.targets[host];
|
|
@@ -350,6 +397,22 @@ function validateHostGlobalTargets() {
|
|
|
350
397
|
errors.push(`missing host-global ${host} copied file: ${file}`);
|
|
351
398
|
continue;
|
|
352
399
|
}
|
|
400
|
+
|
|
401
|
+
const sourceDest = sourceDestForHostFile(host, file);
|
|
402
|
+
const sourcePath = sourceDest && runtimeRoot ? resolveUnder(runtimeRoot, sourceDest) : null;
|
|
403
|
+
if (!sourceDest || !sourcePath) {
|
|
404
|
+
errors.push(`host-global ${host} copied file has no runtime source mapping: ${file}`);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (!fs.existsSync(sourcePath)) {
|
|
408
|
+
errors.push(`host-global ${host} copied file source is missing: ${file} -> ${sourceDest}`);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const sourceHash = sha256File(sourcePath);
|
|
412
|
+
const targetHash = sha256File(filePath);
|
|
413
|
+
if (sourceHash && targetHash && sourceHash !== targetHash) {
|
|
414
|
+
errors.push(`host-global ${host} copied file is stale: ${file} (source ${sourceDest})`);
|
|
415
|
+
}
|
|
353
416
|
}
|
|
354
417
|
}
|
|
355
418
|
}
|
|
@@ -466,7 +529,7 @@ function listMarkdownFiles(rel) {
|
|
|
466
529
|
// Reserved: _template (system), auto (auto-mode capsule)
|
|
467
530
|
const TASK_NAME_RE = /^task-[a-z]+(-[a-z0-9]+){1,4}$/;
|
|
468
531
|
const TASK_NAME_MAX = 46; // "task-" (5) + ≤40 chars body + 1 safety = 46
|
|
469
|
-
const TASK_RESERVED = new Set(['_template', 'auto', '_archive']);
|
|
532
|
+
const TASK_RESERVED = new Set(['_template', 'auto', '_archive', 'continuous']);
|
|
470
533
|
const TASK_SAFE_ARCHIVE_STATUSES = new Set(['complete', 'verified', 'archived', 'abandoned', 'obsolete', 'done', 'closed', 'closeout']);
|
|
471
534
|
const TASK_NEVER_ARCHIVE_STATUSES = new Set(['active', 'blocked', 'in_progress', 'running', 'pending', 'needs-user-decision']);
|
|
472
535
|
const TASK_PHASE_ALIASES = new Map([
|
|
@@ -770,21 +833,147 @@ for (const rel of legacyRootSpecDocs) {
|
|
|
770
833
|
}
|
|
771
834
|
}
|
|
772
835
|
|
|
773
|
-
|
|
774
|
-
|
|
836
|
+
function auditManifestCoverage() {
|
|
837
|
+
const manifestText = read('Harness/ownership.manifest.json');
|
|
838
|
+
if (!manifestText) {
|
|
839
|
+
errors.push('Harness/ownership.manifest.json not found — cannot run manifest audit');
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
let manifest;
|
|
775
844
|
try {
|
|
776
|
-
|
|
777
|
-
for (const entry of manifest.frameworkOwned || []) {
|
|
778
|
-
if (!entry || typeof entry.path !== 'string') continue;
|
|
779
|
-
if (!fs.existsSync(path.join(root, ...entry.path.split('/')))) {
|
|
780
|
-
errors.push(`ownership manifest frameworkOwned file missing: ${entry.path}`);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
845
|
+
manifest = JSON.parse(manifestText);
|
|
783
846
|
} catch (err) {
|
|
784
847
|
errors.push(`Harness/ownership.manifest.json is not valid JSON: ${err.message}`);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const frameworkOwned = manifest.frameworkOwned || [];
|
|
852
|
+
const manifestFileSet = new Set(frameworkOwned.map(e => e && e.path).filter(Boolean));
|
|
853
|
+
const preserveGlobs = manifest.preserve || [];
|
|
854
|
+
const mergeSet = new Set(manifest.merge || []);
|
|
855
|
+
|
|
856
|
+
// 1. Manifest → Disk: frameworkOwned files must exist (always checked)
|
|
857
|
+
for (const entry of frameworkOwned) {
|
|
858
|
+
if (!entry || typeof entry.path !== 'string') continue;
|
|
859
|
+
if (!fs.existsSync(path.join(root, ...entry.path.split('/')))) {
|
|
860
|
+
errors.push(`ownership manifest frameworkOwned file missing: ${entry.path}`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 2. Only run Disk → Manifest scan when --manifest-audit is passed
|
|
865
|
+
if (!manifestAudit) return;
|
|
866
|
+
|
|
867
|
+
// Directories that Harness framework owns (for disk→manifest scan)
|
|
868
|
+
const harnessOwnedDirs = [
|
|
869
|
+
'.claude/agents/',
|
|
870
|
+
'.claude/commands/',
|
|
871
|
+
'.claude/skills/',
|
|
872
|
+
'.claude/rules/ecc/',
|
|
873
|
+
'.opencode/agents/',
|
|
874
|
+
'.opencode/commands/',
|
|
875
|
+
'.opencode/plugins/',
|
|
876
|
+
'.agents/skills/',
|
|
877
|
+
'.codex/',
|
|
878
|
+
'Harness/scripts/',
|
|
879
|
+
'Harness/specs/',
|
|
880
|
+
'Harness/templates/',
|
|
881
|
+
'Harness/research/',
|
|
882
|
+
];
|
|
883
|
+
|
|
884
|
+
// Root-level harness-owned files (not in subdirectories)
|
|
885
|
+
const harnessRootFiles = [
|
|
886
|
+
'opencode.json',
|
|
887
|
+
'AGENTS.md',
|
|
888
|
+
'CLAUDE.md',
|
|
889
|
+
'.claude/settings.json',
|
|
890
|
+
];
|
|
891
|
+
|
|
892
|
+
const extraFiles = [];
|
|
893
|
+
|
|
894
|
+
// Scan harness-owned directories
|
|
895
|
+
for (const dir of harnessOwnedDirs) {
|
|
896
|
+
const dirPath = path.join(root, dir);
|
|
897
|
+
if (!fs.existsSync(dirPath)) continue;
|
|
898
|
+
scanDir(dirPath, dir, extraFiles);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Scan root-level harness files
|
|
902
|
+
for (const rel of harnessRootFiles) {
|
|
903
|
+
const filePath = path.join(root, rel);
|
|
904
|
+
if (!fs.existsSync(filePath)) continue;
|
|
905
|
+
if (!manifestFileSet.has(rel) && !mergeSet.has(rel)) {
|
|
906
|
+
extraFiles.push(rel);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Also scan for top-level files like .harness-version, ownership.manifest.json
|
|
911
|
+
const versionFile = 'Harness/.harness-version';
|
|
912
|
+
if (fs.existsSync(path.join(root, versionFile)) && !manifestFileSet.has(versionFile)) {
|
|
913
|
+
// .harness-version is special — always framework-owned
|
|
914
|
+
}
|
|
915
|
+
const manifestFile = 'Harness/ownership.manifest.json';
|
|
916
|
+
if (fs.existsSync(path.join(root, manifestFile)) && !manifestFileSet.has(manifestFile)) {
|
|
917
|
+
// ownership.manifest.json is special — always framework-owned
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Check if extra files are user-owned (preserve/merge) or already in manifest
|
|
921
|
+
for (const file of extraFiles) {
|
|
922
|
+
// Skip files already registered in the manifest (correctly tracked)
|
|
923
|
+
if (manifestFileSet.has(file)) continue;
|
|
924
|
+
|
|
925
|
+
const isPreserve = preserveGlobs.some(g => {
|
|
926
|
+
const re = new RegExp('^' + g.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*') + '$');
|
|
927
|
+
return re.test(file);
|
|
928
|
+
});
|
|
929
|
+
if (isPreserve || mergeSet.has(file)) continue; // User file, skip
|
|
930
|
+
|
|
931
|
+
// Check if it's in the .harness-version checksums (tracked but maybe not in manifest)
|
|
932
|
+
const checksums = (() => {
|
|
933
|
+
try { return JSON.parse(fs.readFileSync(path.join(root, 'Harness', '.harness-version'), 'utf8')).checksums || {}; }
|
|
934
|
+
catch { return {}; }
|
|
935
|
+
})();
|
|
936
|
+
const isTracked = Object.prototype.hasOwnProperty.call(checksums, file);
|
|
937
|
+
|
|
938
|
+
if (isTracked) {
|
|
939
|
+
// File is in .harness-version but NOT in ownership manifest → manifest coverage gap
|
|
940
|
+
errors.push(`manifest coverage gap: ${file} is in .harness-version checksums but NOT in ownership.manifest.json frameworkOwned`);
|
|
941
|
+
} else {
|
|
942
|
+
// File is NOT tracked at all → truly extra/stale
|
|
943
|
+
const content = (() => { try { return fs.readFileSync(path.join(root, file), 'utf8'); } catch { return ''; } })();
|
|
944
|
+
const hasHarnessMarker = /harness:|Harness\/|create-harness-vibe-coding/i.test(content);
|
|
945
|
+
if (hasHarnessMarker) {
|
|
946
|
+
errors.push(`extra harness-owned file not in manifest: ${file} (has Harness content marker — likely stale framework file)`);
|
|
947
|
+
} else {
|
|
948
|
+
// File in harness directory but no harness marker → could be user file, warn only
|
|
949
|
+
console.warn(`Warning: untracked file in harness directory: ${file} (no Harness content marker — may be user file)`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
785
952
|
}
|
|
786
953
|
}
|
|
787
954
|
|
|
955
|
+
function scanDir(dirPath, prefix, results) {
|
|
956
|
+
let entries;
|
|
957
|
+
try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); }
|
|
958
|
+
catch { return; }
|
|
959
|
+
|
|
960
|
+
for (const entry of entries) {
|
|
961
|
+
const relPath = `${prefix}${entry.name}`;
|
|
962
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
963
|
+
|
|
964
|
+
if (entry.isDirectory()) {
|
|
965
|
+
scanDir(fullPath, `${relPath}/`, results);
|
|
966
|
+
} else if (entry.isFile()) {
|
|
967
|
+
// Normalize path separator
|
|
968
|
+
const normalized = relPath.replace(/\\/g, '/');
|
|
969
|
+
results.push(normalized);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Run manifest audit (basic manifest→disk always; --manifest-audit adds disk→manifest)
|
|
975
|
+
auditManifestCoverage();
|
|
976
|
+
|
|
788
977
|
const removedHookArtifacts = [
|
|
789
978
|
'Harness/HOOK_PROTOCOL.md',
|
|
790
979
|
'Harness/scripts/wf-mode-hook.mjs',
|
|
@@ -1331,8 +1520,8 @@ requireText('Harness/specs/workflows/WF-AUTO.md', 'Inherited WF/WF-MAX Constrain
|
|
|
1331
1520
|
requireText('Harness/specs/workflows/WF-AUTO.md', 'Mini PRD -> AC IDs -> test/validation plan -> implementer -> verifier', 'wf-auto per-cycle WF chain');
|
|
1332
1521
|
requireText('Harness/specs/workflows/WF-AUTO.md', 'reflector PASS', 'wf-auto reflector gate');
|
|
1333
1522
|
requireText('Harness/specs/workflows/WF-AUTO.md', 'Manual or benchmark-driven single-cycle', 'wf-auto bounded single-cycle tick contract');
|
|
1334
|
-
requireText('Harness/specs/workflows/WF-AUTO.md', 'Harness/tasks/
|
|
1335
|
-
requireText('Harness/specs/workflows/WF-AUTO.md', 'Harness/tasks/
|
|
1523
|
+
requireText('Harness/specs/workflows/WF-AUTO.md', 'Harness/tasks/continuous/PLAN.md', 'wf-auto bounded tick PLAN record');
|
|
1524
|
+
requireText('Harness/specs/workflows/WF-AUTO.md', 'Harness/tasks/continuous/PROGRESS.md', 'wf-auto bounded tick PROGRESS record');
|
|
1336
1525
|
requireText('Harness/specs/workflows/WF-AUTO-SPARK.md', 'Inherited Execution Chain', 'wf-auto-spark inherited execution chain');
|
|
1337
1526
|
requireText('Harness/specs/workflows/WF-AUTO-SPARK.md', 'External spark search replaces discovery only', 'wf-auto-spark discovery-only inheritance');
|
|
1338
1527
|
requireText('Harness/specs/workflows/WF-AUTO-SPARK.md', 'reflector PASS', 'wf-auto-spark reflector gate');
|
|
@@ -1464,8 +1653,8 @@ for (const skill of cacheDisciplinedSkills) {
|
|
|
1464
1653
|
}
|
|
1465
1654
|
requireText('.claude/skills/wf-auto/SKILL.md', 'bounded test tick', 'wf-auto skill bounded tick auto capsule rule');
|
|
1466
1655
|
requireText('.claude/skills/wf-auto/SKILL.md', 'missing auto capsule evidence', 'wf-auto skill missing auto capsule failure rule');
|
|
1467
|
-
requireText('.claude/skills/wf-auto/SKILL.md', 'Harness/tasks/
|
|
1468
|
-
requireText('.claude/skills/wf-auto/SKILL.md', 'Harness/tasks/
|
|
1656
|
+
requireText('.claude/skills/wf-auto/SKILL.md', 'Harness/tasks/continuous/PLAN.md', 'wf-auto skill PLAN record');
|
|
1657
|
+
requireText('.claude/skills/wf-auto/SKILL.md', 'Harness/tasks/continuous/PROGRESS.md', 'wf-auto skill PROGRESS record');
|
|
1469
1658
|
requireText('.claude/skills/wf-auto/SKILL.md', 'evidence ledger path/summary', 'wf-auto skill return evidence ledger path');
|
|
1470
1659
|
if (fs.existsSync(path.join(root, '.claude/skills/wf-browser/SKILL.md'))) {
|
|
1471
1660
|
requireText('.claude/skills/wf-browser/SKILL.md', 'Cache Discipline', 'wf-browser skill cache discipline');
|
|
@@ -1485,9 +1674,11 @@ if (fs.existsSync(path.join(root, '.claude/skills/wf-browser/SKILL.md'))) {
|
|
|
1485
1674
|
// Direct command checks
|
|
1486
1675
|
requireText('Harness/README.md', '/wf-update', 'wf-update direct command reference');
|
|
1487
1676
|
requireText('.claude/commands/wf-update.md', 'Do not invoke a skill', 'wf-update direct command boundary');
|
|
1677
|
+
requireText('Harness/README.md', '| `/wf-ui`, `$wf-ui` |', 'wf-ui direct command row');
|
|
1488
1678
|
for (const rel of ['.claude/commands/wf-update.md', '.opencode/commands/wf-update.md']) {
|
|
1489
1679
|
requireText(rel, '## Cache Discipline', `${rel} cache discipline`);
|
|
1490
1680
|
requireText(rel, 'agent.safeApplyCommand', `${rel} safe apply step`);
|
|
1681
|
+
requireText(rel, 'wf-update-runner.mjs', `${rel} multi-scope update runner`);
|
|
1491
1682
|
requireText(rel, '--apply-safe', `${rel} apply-safe command`);
|
|
1492
1683
|
requireText(rel, 'agent.aiMergeRequired', `${rel} AI merge step`);
|
|
1493
1684
|
requireText(rel, '--accept-local', `${rel} accept-local decision`);
|
|
@@ -1495,16 +1686,35 @@ for (const rel of ['.claude/commands/wf-update.md', '.opencode/commands/wf-updat
|
|
|
1495
1686
|
requireText(rel, '--accept-template', `${rel} accept-template decision`);
|
|
1496
1687
|
requireText(rel, '--finalize', `${rel} finalize command`);
|
|
1497
1688
|
requireText(rel, 'strict `--apply` only when', `${rel} strict apply boundary`);
|
|
1689
|
+
requireText(rel, 'sync-host-global.mjs', `${rel} host-global sync step`);
|
|
1498
1690
|
requireText(rel, '## Return', `${rel} return contract`);
|
|
1499
1691
|
requireText(rel, 'agent.releaseHighlights', `${rel} release highlights summary`);
|
|
1500
1692
|
requireText(rel, 'releaseNotes.highlights', `${rel} release notes fallback`);
|
|
1501
1693
|
}
|
|
1694
|
+
for (const rel of ['.claude/commands/wf-ui.md', '.opencode/commands/wf-ui.md']) {
|
|
1695
|
+
requireText(rel, 'direct command', `${rel} wf-ui direct command classification`);
|
|
1696
|
+
requireText(rel, 'Do not invoke a skill', `${rel} wf-ui direct no-skill boundary`);
|
|
1697
|
+
requireText(rel, 'create-harness-vibe-coding wf-ui', `${rel} wf-ui direct CLI launch`);
|
|
1698
|
+
requireText(rel, '--host 127.0.0.1', `${rel} wf-ui loopback host`);
|
|
1699
|
+
requireText(rel, '--open', `${rel} wf-ui browser open flag`);
|
|
1700
|
+
forbidText(rel, 'workflow command', `${rel} wf-ui workflow routing`);
|
|
1701
|
+
forbidText(rel, 'Load `CLAUDE.md`', `${rel} wf-ui old router preload`);
|
|
1702
|
+
forbidText(rel, 'Harness/tasks/task-wf-ui-control-0729/STATE.json', `${rel} wf-ui old task-state preload`);
|
|
1703
|
+
}
|
|
1704
|
+
for (const rel of ['.claude/skills/wf-ui/SKILL.md', '.agents/skills/wf-ui/SKILL.md']) {
|
|
1705
|
+
requireText(rel, 'Codex compatibility shim', `${rel} wf-ui Codex compatibility shim`);
|
|
1706
|
+
requireText(rel, 'direct command', `${rel} wf-ui direct command statement`);
|
|
1707
|
+
requireText(rel, 'create-harness-vibe-coding wf-ui', `${rel} wf-ui direct CLI launch`);
|
|
1708
|
+
forbidText(rel, 'Harness/tasks/task-wf-ui-control-0729/STATE.json', `${rel} wf-ui old task-state preload`);
|
|
1709
|
+
}
|
|
1502
1710
|
requireText('Harness/scripts/wf-update-check.mjs', 'releaseHighlights', 'wf-update-check release highlights metadata');
|
|
1503
1711
|
requireText('Harness/scripts/wf-update-check.mjs', 'updateReportRequired', 'wf-update-check user update report requirement');
|
|
1504
1712
|
requireText('.claude/commands/wf-help.md', 'direct command', 'wf-help wf-update direct command classification');
|
|
1505
1713
|
requireText('.claude/commands/wf-help.md', '$wf-help', 'wf-help Codex compatibility usage');
|
|
1506
1714
|
requireText('.claude/commands/wf-help.md', '/wf-browser', 'wf-help built-in browser workflow row');
|
|
1715
|
+
requireText('.claude/commands/wf-help.md', '| `/wf-ui` | direct command |', 'wf-help wf-ui direct command row');
|
|
1507
1716
|
requireText('.opencode/commands/wf-help.md', '/wf-browser', 'OpenCode wf-help built-in browser workflow row');
|
|
1717
|
+
requireText('.opencode/commands/wf-help.md', '| `/wf-ui` | direct command |', 'OpenCode wf-help wf-ui direct command row');
|
|
1508
1718
|
|
|
1509
1719
|
// wf-update skill must NOT claim Claude Code /wf-update as a skill invocation
|
|
1510
1720
|
// (allow mentions in the description/body that say "direct command" — those are correct)
|
|
@@ -83,6 +83,7 @@ const PURGEABLE_HARNESS_DATA_PATTERNS = [
|
|
|
83
83
|
const FRAMEWORK_TASK_PATTERNS = [
|
|
84
84
|
/^Harness\/tasks\/_template\//,
|
|
85
85
|
/^Harness\/tasks\/auto\//,
|
|
86
|
+
/^Harness\/tasks\/continuous\//,
|
|
86
87
|
];
|
|
87
88
|
|
|
88
89
|
function isUserData(file) {
|
|
@@ -151,6 +152,7 @@ const BUILT_IN_SKILL_NAMES = [
|
|
|
151
152
|
'wf-task-archive',
|
|
152
153
|
'wf-task-list',
|
|
153
154
|
'wf-task-record',
|
|
155
|
+
'wf-ui',
|
|
154
156
|
'wf-update',
|
|
155
157
|
];
|
|
156
158
|
|
|
@@ -169,6 +171,7 @@ const BUILT_IN_COMMAND_NAMES = [
|
|
|
169
171
|
'wf-task-archive',
|
|
170
172
|
'wf-task-list',
|
|
171
173
|
'wf-task-record',
|
|
174
|
+
'wf-ui',
|
|
172
175
|
'wf-update',
|
|
173
176
|
];
|
|
174
177
|
|
|
@@ -216,6 +219,7 @@ const CLEANUP_DIRS = [
|
|
|
216
219
|
'.claude/skills/wf-task-list',
|
|
217
220
|
'.claude/skills/wf-task-record',
|
|
218
221
|
'.claude/skills/wf-update',
|
|
222
|
+
'.claude/skills/wf-ui',
|
|
219
223
|
'.claude/skills/wf-remove',
|
|
220
224
|
'.claude/skills/subagent-orchestrator',
|
|
221
225
|
'.claude/skills',
|
|
@@ -239,6 +243,7 @@ const CLEANUP_DIRS = [
|
|
|
239
243
|
'.agents/skills/wf-task-list',
|
|
240
244
|
'.agents/skills/wf-task-record',
|
|
241
245
|
'.agents/skills/wf-update',
|
|
246
|
+
'.agents/skills/wf-ui',
|
|
242
247
|
'.agents/skills/wf-remove',
|
|
243
248
|
'.agents/skills/subagent-orchestrator',
|
|
244
249
|
'.agents/skills',
|
|
@@ -263,7 +268,7 @@ const CLEANUP_DIRS = [
|
|
|
263
268
|
'Harness/features',
|
|
264
269
|
'Harness/memory',
|
|
265
270
|
'Harness/tasks/_template',
|
|
266
|
-
'Harness/tasks/
|
|
271
|
+
'Harness/tasks/continuous',
|
|
267
272
|
'Harness/tasks',
|
|
268
273
|
'Harness',
|
|
269
274
|
];
|
|
@@ -434,8 +439,8 @@ async function main() {
|
|
|
434
439
|
'.claude/skills/wf-auto/SKILL.md',
|
|
435
440
|
'.agents/skills/wf-auto/SKILL.md',
|
|
436
441
|
'Harness/specs/workflows/WF-AUTO.md',
|
|
437
|
-
'Harness/tasks/
|
|
438
|
-
'Harness/tasks/
|
|
442
|
+
'Harness/tasks/continuous/PROGRESS.md',
|
|
443
|
+
'Harness/tasks/continuous/PLAN.md',
|
|
439
444
|
'Harness/tasks/_template/ARTIFACTS.md',
|
|
440
445
|
'Harness/tasks/_template/NOTES.md',
|
|
441
446
|
'Harness/tasks/_template/PLAN.md',
|
|
@@ -541,6 +541,10 @@ async function main() {
|
|
|
541
541
|
const acceptMerged = readRepeatedFlagValues(args, '--accept-merged');
|
|
542
542
|
const acceptTemplate = readRepeatedFlagValues(args, '--accept-template');
|
|
543
543
|
const ignoreVersion = args.includes('--ignore-version') || args.includes('--force-check');
|
|
544
|
+
const repairMode = args.includes('--repair');
|
|
545
|
+
const effectiveIgnoreVersion = ignoreVersion || repairMode;
|
|
546
|
+
const allowDowngrade = args.includes('--allow-downgrade');
|
|
547
|
+
const allowPrerelease = args.includes('--include-prerelease');
|
|
544
548
|
const explicitSource = readFlagValue(args, '--source-base') || process.env.WF_SOURCE_BASE;
|
|
545
549
|
let source;
|
|
546
550
|
let sourceBase;
|
|
@@ -632,7 +636,7 @@ async function main() {
|
|
|
632
636
|
const localGen = localVersion.generator || '0.0.0';
|
|
633
637
|
const remoteGen = remoteVersion.generator || '0.0.0';
|
|
634
638
|
|
|
635
|
-
if (!
|
|
639
|
+
if (!allowPrerelease && isPrerelease(remoteGen)) {
|
|
636
640
|
if (jsonOut) {
|
|
637
641
|
console.log(JSON.stringify({ status: 'up-to-date', version: localGen, remote: remoteGen, sourceBase }));
|
|
638
642
|
} else {
|
|
@@ -642,27 +646,41 @@ async function main() {
|
|
|
642
646
|
}
|
|
643
647
|
|
|
644
648
|
const versionCmp = cmpSemver(remoteGen, localGen);
|
|
649
|
+
const reportDowngrade = !sourceStable;
|
|
645
650
|
|
|
646
|
-
if (
|
|
647
|
-
const reportDowngrade = !sourceStable;
|
|
651
|
+
if (versionCmp < 0 && !allowDowngrade) {
|
|
648
652
|
if (jsonOut) {
|
|
649
653
|
console.log(JSON.stringify({
|
|
650
|
-
status:
|
|
654
|
+
status: reportDowngrade ? 'downgrade-refused' : 'up-to-date',
|
|
651
655
|
version: localGen,
|
|
652
656
|
remote: remoteGen,
|
|
653
657
|
sourceBase,
|
|
654
658
|
}));
|
|
655
|
-
} else if (
|
|
659
|
+
} else if (reportDowngrade) {
|
|
656
660
|
console.log(`WARN: Remote (v${remoteGen}) is OLDER than local (v${localGen}). Downgrade refused.`);
|
|
661
|
+
} else {
|
|
662
|
+
console.log(`Already up to date (v${localGen}). Remote ${remoteGen} is older and ignored.`);
|
|
663
|
+
}
|
|
664
|
+
if (reportDowngrade) process.exitCode = 1;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (!effectiveIgnoreVersion && versionCmp <= 0) {
|
|
669
|
+
if (jsonOut) {
|
|
670
|
+
console.log(JSON.stringify({
|
|
671
|
+
status: 'up-to-date',
|
|
672
|
+
version: localGen,
|
|
673
|
+
remote: remoteGen,
|
|
674
|
+
sourceBase,
|
|
675
|
+
}));
|
|
657
676
|
} else {
|
|
658
677
|
console.log(`Already up to date (v${localGen})`);
|
|
659
678
|
}
|
|
660
|
-
if (versionCmp < 0 && reportDowngrade) process.exitCode = 1;
|
|
661
679
|
return;
|
|
662
680
|
}
|
|
663
681
|
|
|
664
|
-
if (
|
|
665
|
-
if (!jsonOut) console.log(
|
|
682
|
+
if (effectiveIgnoreVersion) {
|
|
683
|
+
if (!jsonOut) console.log(`Version check bypassed (${repairMode ? '--repair' : '--ignore-version'}). Comparing files anyway.`);
|
|
666
684
|
}
|
|
667
685
|
|
|
668
686
|
const remoteChecksums = remoteVersion.checksums || {};
|
|
@@ -760,6 +778,9 @@ async function main() {
|
|
|
760
778
|
: 0;
|
|
761
779
|
return {
|
|
762
780
|
mode: 'script-first-ai-conflicts',
|
|
781
|
+
multiScopeDryRunCommand: 'node Harness/scripts/wf-update-runner.mjs --json',
|
|
782
|
+
multiScopeSafeApplyCommand: 'node Harness/scripts/wf-update-runner.mjs --apply-safe --json',
|
|
783
|
+
multiScopeFinalizeCommand: 'node Harness/scripts/wf-update-runner.mjs --finalize --json',
|
|
763
784
|
dryRunJsonCommand: 'node Harness/scripts/wf-update-check.mjs --json',
|
|
764
785
|
safeApplyCommand: 'node Harness/scripts/wf-update-check.mjs --apply-safe',
|
|
765
786
|
strictApplyCommand: 'node Harness/scripts/wf-update-check.mjs --apply',
|
|
@@ -776,7 +797,10 @@ async function main() {
|
|
|
776
797
|
aiMergeRequiredTruncated: truncated > 0 ? truncated : undefined,
|
|
777
798
|
conflictPolicy: 'Use the script for SAFE/NEW/adopted files. For each CONFLICT file, compare local content with templateHint/remoteUrl, then record the decision with --accept-local, --accept-merged, or --accept-template. Do not hand-edit Harness/.harness-version.',
|
|
778
799
|
postUpdateCommands: [
|
|
800
|
+
'node Harness/scripts/sync-host-global.mjs --json',
|
|
801
|
+
'node Harness/scripts/sync-host-global.mjs --apply --json',
|
|
779
802
|
'node Harness/scripts/validate-harness.mjs',
|
|
803
|
+
'node Harness/scripts/validate-harness.mjs --manifest-audit',
|
|
780
804
|
'node Harness/scripts/scan-clean.mjs --json',
|
|
781
805
|
],
|
|
782
806
|
bootstrapDebtCommand: 'node Harness/scripts/validate-harness.mjs --strict',
|
|
@@ -1010,8 +1034,11 @@ async function main() {
|
|
|
1010
1034
|
if (jsonOut) {
|
|
1011
1035
|
const verbose = args.includes('--verbose') || args.includes('--full-plan');
|
|
1012
1036
|
const jsonPlan = buildJsonPlan();
|
|
1037
|
+
const effectiveStatus = repairMode && versionCmp <= 0 && !localVersion.partialUpdate
|
|
1038
|
+
? 'repair-check'
|
|
1039
|
+
: (localVersion.partialUpdate ? 'partial-update' : 'update-available');
|
|
1013
1040
|
console.log(JSON.stringify({
|
|
1014
|
-
status:
|
|
1041
|
+
status: effectiveStatus,
|
|
1015
1042
|
from: localGen,
|
|
1016
1043
|
to: remoteGen,
|
|
1017
1044
|
sourceBase,
|
|
@@ -1026,6 +1053,7 @@ async function main() {
|
|
|
1026
1053
|
skipped: plan.skipped.length,
|
|
1027
1054
|
// Token-safe by default: attach the full plan only when --verbose / --full-plan is passed.
|
|
1028
1055
|
...(verbose ? { plan: jsonPlan } : {}),
|
|
1056
|
+
repair: repairMode || undefined,
|
|
1029
1057
|
agent: buildAgentHints(jsonPlan),
|
|
1030
1058
|
}, null, 2));
|
|
1031
1059
|
return;
|