create-harness-vibe-coding 0.8.17 → 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.
Files changed (150) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README-CN.md +2 -0
  3. package/README.md +3 -0
  4. package/package.json +5 -2
  5. package/src/generator.js +613 -97
  6. package/src/index.js +556 -309
  7. package/src/prompts.js +18 -0
  8. package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js +17 -0
  9. package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js.map +1 -0
  10. package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js +7 -0
  11. package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js.map +1 -0
  12. package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js +12 -0
  13. package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js.map +1 -0
  14. package/src/ui/dist/assets/TaskList-BN4r8s1i.js +50 -0
  15. package/src/ui/dist/assets/TaskList-BN4r8s1i.js.map +1 -0
  16. package/src/ui/dist/assets/TerminalDrawer-6GBZ9nXN.css +32 -0
  17. package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js +74 -0
  18. package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js.map +1 -0
  19. package/src/ui/dist/assets/WorkflowRoute-BnuhLJ6X.css +1 -0
  20. package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js +39 -0
  21. package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js.map +1 -0
  22. package/src/ui/dist/assets/index-BOVYWntB.js +117 -0
  23. package/src/ui/dist/assets/index-BOVYWntB.js.map +1 -0
  24. package/src/ui/dist/assets/index-DaUObq0H.css +1 -0
  25. package/src/ui/dist/assets/maximize-2-LNrr_rKr.js +7 -0
  26. package/src/ui/dist/assets/maximize-2-LNrr_rKr.js.map +1 -0
  27. package/src/ui/dist/assets/plus-QuTJyoT3.js +7 -0
  28. package/src/ui/dist/assets/plus-QuTJyoT3.js.map +1 -0
  29. package/src/ui/dist/assets/proxy-D0vUbds5.js +2 -0
  30. package/src/ui/dist/assets/proxy-D0vUbds5.js.map +1 -0
  31. package/src/ui/dist/assets/refresh-cw-JNmExijd.js +7 -0
  32. package/src/ui/dist/assets/refresh-cw-JNmExijd.js.map +1 -0
  33. package/src/ui/dist/assets/terminal-BBQvnOAK.js +7 -0
  34. package/src/ui/dist/assets/terminal-BBQvnOAK.js.map +1 -0
  35. package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js +7 -0
  36. package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js.map +1 -0
  37. package/src/ui/dist/assets/x-DoIOGAJz.js +7 -0
  38. package/src/ui/dist/assets/x-DoIOGAJz.js.map +1 -0
  39. package/src/ui/dist/index.html +18 -0
  40. package/src/ui/index.html +17 -0
  41. package/src/ui/package.json +33 -0
  42. package/src/ui/pnpm-lock.yaml +1582 -0
  43. package/src/ui/pnpm-workspace.yaml +2 -0
  44. package/src/ui/src/App.tsx +86 -0
  45. package/src/ui/src/api.ts +93 -0
  46. package/src/ui/src/components/AgentsRoute.tsx +503 -0
  47. package/src/ui/src/components/Footer.tsx +69 -0
  48. package/src/ui/src/components/Header.tsx +175 -0
  49. package/src/ui/src/components/LoadingView.tsx +22 -0
  50. package/src/ui/src/components/RolesRoute.tsx +169 -0
  51. package/src/ui/src/components/SettingsRoute.tsx +166 -0
  52. package/src/ui/src/components/TaskList.tsx +388 -0
  53. package/src/ui/src/components/TerminalDrawer.tsx +611 -0
  54. package/src/ui/src/components/WorkflowRoute.tsx +670 -0
  55. package/src/ui/src/hooks/useReducedMotion.ts +17 -0
  56. package/src/ui/src/hooks/useServerConnection.ts +114 -0
  57. package/src/ui/src/index.css +281 -0
  58. package/src/ui/src/main.tsx +11 -0
  59. package/src/ui/src/types.ts +108 -0
  60. package/src/ui/tsconfig.json +21 -0
  61. package/src/ui/vite.config.ts +19 -0
  62. package/src/wf-ui-server/__tests__/a2a-store.test.mjs +79 -0
  63. package/src/wf-ui-server/__tests__/peer-capsule.test.mjs +268 -0
  64. package/src/wf-ui-server/__tests__/pty-adapter.test.mjs +70 -0
  65. package/src/wf-ui-server/__tests__/runtime-config.test.mjs +43 -0
  66. package/src/wf-ui-server/__tests__/runtime-detector.test.mjs +90 -0
  67. package/src/wf-ui-server/__tests__/security.test.mjs +59 -0
  68. package/src/wf-ui-server/__tests__/server.integration.test.mjs +150 -0
  69. package/src/wf-ui-server/__tests__/session-registry.test.mjs +238 -0
  70. package/src/wf-ui-server/__tests__/settings.test.mjs +135 -0
  71. package/src/wf-ui-server/__tests__/task-parser.test.mjs +200 -0
  72. package/src/wf-ui-server/__tests__/terminal-store.test.mjs +138 -0
  73. package/src/wf-ui-server/__tests__/token.test.mjs +48 -0
  74. package/src/wf-ui-server/__tests__/ws-events.integration.test.mjs +419 -0
  75. package/src/wf-ui-server/__tests__/ws-terminal.integration.test.mjs +383 -0
  76. package/src/wf-ui-server/a2a-store.mjs +296 -0
  77. package/src/wf-ui-server/peer-capsule.mjs +213 -0
  78. package/src/wf-ui-server/pty-adapter.mjs +155 -0
  79. package/src/wf-ui-server/runtime-config.mjs +153 -0
  80. package/src/wf-ui-server/runtime-detector.mjs +374 -0
  81. package/src/wf-ui-server/security.mjs +67 -0
  82. package/src/wf-ui-server/server.mjs +849 -0
  83. package/src/wf-ui-server/session-registry.mjs +204 -0
  84. package/src/wf-ui-server/settings.mjs +100 -0
  85. package/src/wf-ui-server/task-parser.mjs +133 -0
  86. package/src/wf-ui-server/terminal-store.mjs +225 -0
  87. package/src/wf-ui-server/token.mjs +41 -0
  88. package/src/wf-ui-server/ws-events.mjs +354 -0
  89. package/src/wf-ui-server/ws-terminal.mjs +473 -0
  90. package/templates/common/.claude/commands/wf-command-create.md +58 -0
  91. package/templates/common/.claude/commands/wf-help.md +5 -0
  92. package/templates/common/.claude/commands/wf-task-archive.md +26 -0
  93. package/templates/common/.claude/commands/wf-task-list.md +26 -0
  94. package/templates/common/.claude/commands/wf-task-record.md +24 -0
  95. package/templates/common/.claude/commands/wf-ui.md +26 -0
  96. package/templates/common/.claude/commands/wf-update.md +54 -9
  97. package/templates/common/.claude/rules/ecc/common.md +1 -1
  98. package/templates/common/.claude/skills/wf-agents-docs/SKILL.md +15 -30
  99. package/templates/common/.claude/skills/wf-auto/SKILL.md +3 -3
  100. package/templates/common/.claude/skills/wf-auto-spark/SKILL.md +2 -2
  101. package/templates/common/.claude/skills/wf-command-create/SKILL.md +37 -0
  102. package/templates/common/.claude/skills/wf-max/SKILL.md +1 -1
  103. package/templates/common/.claude/skills/wf-review/SKILL.md +29 -2
  104. package/templates/common/.claude/skills/wf-task-archive/SKILL.md +28 -0
  105. package/templates/common/.claude/skills/wf-task-list/SKILL.md +28 -0
  106. package/templates/common/.claude/skills/wf-task-record/SKILL.md +28 -0
  107. package/templates/common/.claude/skills/wf-ui/SKILL.md +78 -0
  108. package/templates/common/.claude/skills/wf-update/SKILL.md +55 -58
  109. package/templates/common/.harness-version +105 -47
  110. package/templates/common/.opencode/commands/wf-command-create.md +61 -0
  111. package/templates/common/.opencode/commands/wf-help.md +5 -0
  112. package/templates/common/.opencode/commands/wf-task-archive.md +29 -0
  113. package/templates/common/.opencode/commands/wf-task-list.md +29 -0
  114. package/templates/common/.opencode/commands/wf-task-record.md +27 -0
  115. package/templates/common/.opencode/commands/wf-ui.md +26 -0
  116. package/templates/common/.opencode/commands/wf-update.md +54 -9
  117. package/templates/common/CLAUDE.md +8 -6
  118. package/templates/common/Harness/MEMORY.md +11 -0
  119. package/templates/common/Harness/README.md +21 -38
  120. package/templates/common/Harness/a2a/role-graph.json +79 -0
  121. package/templates/common/Harness/a2a/runtime-registry.json +5 -0
  122. package/templates/common/Harness/a2a/skills/terminal-control.json +15 -0
  123. package/templates/common/Harness/ownership.manifest.json +142 -2
  124. package/templates/common/Harness/scripts/README.md +134 -0
  125. package/templates/common/Harness/scripts/a2a-terminal.mjs +191 -0
  126. package/templates/common/Harness/scripts/context-budget.mjs +1 -1
  127. package/templates/common/Harness/scripts/sync-host-global.mjs +278 -0
  128. package/templates/common/Harness/scripts/task-state.mjs +949 -26
  129. package/templates/common/Harness/scripts/validate-harness.mjs +637 -62
  130. package/templates/common/Harness/scripts/wf-remove.mjs +42 -5
  131. package/templates/common/Harness/scripts/wf-update-check.mjs +37 -9
  132. package/templates/common/Harness/scripts/wf-update-runner.mjs +325 -0
  133. package/templates/common/Harness/settings.json +35 -0
  134. package/templates/common/Harness/specs/guides/SETUP.md +8 -0
  135. package/templates/common/Harness/specs/protocols/MEMORY_PROTOCOL.md +15 -0
  136. package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +16 -5
  137. package/templates/common/Harness/specs/runtime/command-surface.json +229 -0
  138. package/templates/common/Harness/specs/runtime/subagents.md +6 -0
  139. package/templates/common/Harness/specs/workflows/WF-AUTO-SPARK.md +8 -8
  140. package/templates/common/Harness/specs/workflows/WF-AUTO.md +12 -12
  141. package/templates/common/Harness/specs/workflows/WF-MAX.md +5 -0
  142. package/templates/common/Harness/specs/workflows/WF-STATE.md +66 -0
  143. package/templates/common/Harness/tasks/_template/NAMING.md +16 -16
  144. package/templates/common/Harness/tasks/_template/PLAN.md +17 -60
  145. package/templates/common/Harness/tasks/_template/PROBLEM.md +18 -0
  146. package/templates/common/Harness/tasks/_template/PROGRESS.md +9 -14
  147. package/templates/common/Harness/tasks/_template/REFERENCES.md +26 -0
  148. package/templates/common/Harness/tasks/_template/STATE.json +6 -0
  149. package/templates/common/Harness/tasks/_template/ARTIFACTS.md +0 -3
  150. package/templates/common/Harness/tasks/_template/NOTES.md +0 -3
@@ -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) {
@@ -141,12 +142,36 @@ const BUILT_IN_SKILL_NAMES = [
141
142
  'wf-auto',
142
143
  'wf-auto-spark',
143
144
  'wf-browser',
145
+ 'wf-command-create',
144
146
  'wf-help',
145
147
  'wf-learn',
146
148
  'wf-max',
147
149
  'wf-readme',
148
150
  'wf-remove',
149
151
  'wf-review',
152
+ 'wf-task-archive',
153
+ 'wf-task-list',
154
+ 'wf-task-record',
155
+ 'wf-ui',
156
+ 'wf-update',
157
+ ];
158
+
159
+ const BUILT_IN_COMMAND_NAMES = [
160
+ 'wf',
161
+ 'wf-auto',
162
+ 'wf-auto-spark',
163
+ 'wf-browser',
164
+ 'wf-command-create',
165
+ 'wf-help',
166
+ 'wf-learn',
167
+ 'wf-max',
168
+ 'wf-readme',
169
+ 'wf-remove',
170
+ 'wf-review',
171
+ 'wf-task-archive',
172
+ 'wf-task-list',
173
+ 'wf-task-record',
174
+ 'wf-ui',
150
175
  'wf-update',
151
176
  ];
152
177
 
@@ -159,8 +184,10 @@ const KNOWN_FRAMEWORK_FILES = new Set([
159
184
  `.claude/skills/${name}/SKILL.md`,
160
185
  `.agents/skills/${name}/SKILL.md`,
161
186
  ]),
162
- '.claude/commands/wf-help.md',
163
- '.opencode/commands/wf-help.md',
187
+ ...BUILT_IN_COMMAND_NAMES.flatMap(name => [
188
+ `.claude/commands/${name}.md`,
189
+ `.opencode/commands/${name}.md`,
190
+ ]),
164
191
  '.claude/rules/ecc/common.md',
165
192
  'opencode.json',
166
193
  ]);
@@ -177,6 +204,7 @@ const CLEANUP_DIRS = [
177
204
  '.claude/skills/wf-auto',
178
205
  '.claude/skills/wf-auto-spark',
179
206
  '.claude/skills/wf-browser',
207
+ '.claude/skills/wf-command-create',
180
208
  '.claude/skills/wf-help',
181
209
  '.claude/skills/tdd',
182
210
  '.claude/skills/ts-react-frontend',
@@ -187,7 +215,11 @@ const CLEANUP_DIRS = [
187
215
  '.claude/skills/wf-max',
188
216
  '.claude/skills/wf-readme',
189
217
  '.claude/skills/wf-review',
218
+ '.claude/skills/wf-task-archive',
219
+ '.claude/skills/wf-task-list',
220
+ '.claude/skills/wf-task-record',
190
221
  '.claude/skills/wf-update',
222
+ '.claude/skills/wf-ui',
191
223
  '.claude/skills/wf-remove',
192
224
  '.claude/skills/subagent-orchestrator',
193
225
  '.claude/skills',
@@ -196,6 +228,7 @@ const CLEANUP_DIRS = [
196
228
  '.agents/skills/wf-auto',
197
229
  '.agents/skills/wf-auto-spark',
198
230
  '.agents/skills/wf-browser',
231
+ '.agents/skills/wf-command-create',
199
232
  '.agents/skills/wf-help',
200
233
  '.agents/skills/tdd',
201
234
  '.agents/skills/ts-react-frontend',
@@ -206,7 +239,11 @@ const CLEANUP_DIRS = [
206
239
  '.agents/skills/wf-max',
207
240
  '.agents/skills/wf-readme',
208
241
  '.agents/skills/wf-review',
242
+ '.agents/skills/wf-task-archive',
243
+ '.agents/skills/wf-task-list',
244
+ '.agents/skills/wf-task-record',
209
245
  '.agents/skills/wf-update',
246
+ '.agents/skills/wf-ui',
210
247
  '.agents/skills/wf-remove',
211
248
  '.agents/skills/subagent-orchestrator',
212
249
  '.agents/skills',
@@ -231,7 +268,7 @@ const CLEANUP_DIRS = [
231
268
  'Harness/features',
232
269
  'Harness/memory',
233
270
  'Harness/tasks/_template',
234
- 'Harness/tasks/auto',
271
+ 'Harness/tasks/continuous',
235
272
  'Harness/tasks',
236
273
  'Harness',
237
274
  ];
@@ -402,8 +439,8 @@ async function main() {
402
439
  '.claude/skills/wf-auto/SKILL.md',
403
440
  '.agents/skills/wf-auto/SKILL.md',
404
441
  'Harness/specs/workflows/WF-AUTO.md',
405
- 'Harness/tasks/auto/PROGRESS.md',
406
- 'Harness/tasks/auto/PLAN.md',
442
+ 'Harness/tasks/continuous/PROGRESS.md',
443
+ 'Harness/tasks/continuous/PLAN.md',
407
444
  'Harness/tasks/_template/ARTIFACTS.md',
408
445
  'Harness/tasks/_template/NOTES.md',
409
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 (!ignoreVersion && isPrerelease(remoteGen)) {
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 (!ignoreVersion && versionCmp <= 0) {
647
- const reportDowngrade = !sourceStable;
651
+ if (versionCmp < 0 && !allowDowngrade) {
648
652
  if (jsonOut) {
649
653
  console.log(JSON.stringify({
650
- status: (versionCmp < 0 && reportDowngrade) ? 'downgrade-refused' : 'up-to-date',
654
+ status: reportDowngrade ? 'downgrade-refused' : 'up-to-date',
651
655
  version: localGen,
652
656
  remote: remoteGen,
653
657
  sourceBase,
654
658
  }));
655
- } else if (versionCmp < 0 && reportDowngrade) {
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 (ignoreVersion) {
665
- if (!jsonOut) console.log('Version check bypassed (--ignore-version). Comparing files anyway.');
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: localVersion.partialUpdate ? 'partial-update' : 'update-available',
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;
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const SCRIPT_ROOT = path.resolve(__dirname, '..', '..');
10
+ const rawArgs = process.argv.slice(2);
11
+ const JSON_OUT = rawArgs.includes('--json');
12
+
13
+ function readFlagValue(args, flagName) {
14
+ const idx = args.indexOf(flagName);
15
+ if (idx === -1) return null;
16
+ const value = args[idx + 1];
17
+ return value && !value.startsWith('--') ? value : null;
18
+ }
19
+
20
+ function stripFlagWithValue(args, flagName) {
21
+ const next = [];
22
+ for (let i = 0; i < args.length; i += 1) {
23
+ if (args[i] === flagName) {
24
+ i += 1;
25
+ continue;
26
+ }
27
+ if (args[i].startsWith(`${flagName}=`)) continue;
28
+ next.push(args[i]);
29
+ }
30
+ return next;
31
+ }
32
+
33
+ function normalizePath(value) {
34
+ return String(value || '').replace(/\\/g, '/');
35
+ }
36
+
37
+ function comparablePath(value) {
38
+ const resolved = path.resolve(value);
39
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
40
+ }
41
+
42
+ function samePath(a, b) {
43
+ if (!a || !b) return false;
44
+ return comparablePath(a) === comparablePath(b);
45
+ }
46
+
47
+ function parseSemver(value) {
48
+ if (!value || typeof value !== 'string') return [0, 0, 0];
49
+ return value.replace(/^[^0-9]*/, '').split('.').slice(0, 3).map(part => Number(part) || 0);
50
+ }
51
+
52
+ function cmpSemver(a, b) {
53
+ const left = parseSemver(a);
54
+ const right = parseSemver(b);
55
+ for (let i = 0; i < 3; i += 1) {
56
+ if ((left[i] || 0) > (right[i] || 0)) return 1;
57
+ if ((left[i] || 0) < (right[i] || 0)) return -1;
58
+ }
59
+ return 0;
60
+ }
61
+
62
+ function shouldAutoRepair(firstJson, targetVersion) {
63
+ if (firstJson?.status !== 'up-to-date') return false;
64
+ const remote = firstJson.remote || firstJson.to || null;
65
+ if (remote && targetVersion && cmpSemver(remote, targetVersion) < 0) return false;
66
+ return true;
67
+ }
68
+
69
+ function defaultGlobalDir() {
70
+ return process.env.HARNESS_GLOBAL_HOME
71
+ || path.join(os.homedir(), '.harness', 'create-harness-vibe-coding');
72
+ }
73
+
74
+ function versionPath(root) {
75
+ return path.join(root, 'Harness', '.harness-version');
76
+ }
77
+
78
+ function updateScriptPath(root) {
79
+ return path.join(root, 'Harness', 'scripts', 'wf-update-check.mjs');
80
+ }
81
+
82
+ function syncScriptPath(root) {
83
+ return path.join(root, 'Harness', 'scripts', 'sync-host-global.mjs');
84
+ }
85
+
86
+ function validateScriptPath(root) {
87
+ return path.join(root, 'Harness', 'scripts', 'validate-harness.mjs');
88
+ }
89
+
90
+ function scanCleanScriptPath(root) {
91
+ return path.join(root, 'Harness', 'scripts', 'scan-clean.mjs');
92
+ }
93
+
94
+ function readVersion(root) {
95
+ try {
96
+ const file = versionPath(root);
97
+ if (!fs.existsSync(file)) return null;
98
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+
104
+ function hasRunnableUpdater(root) {
105
+ return fs.existsSync(versionPath(root)) && fs.existsSync(updateScriptPath(root));
106
+ }
107
+
108
+ function discoverGlobalRuntime(projectVersion) {
109
+ const candidates = [];
110
+ if (projectVersion?.globalDir) candidates.push(projectVersion.globalDir);
111
+ if (process.env.HARNESS_GLOBAL_HOME) candidates.push(process.env.HARNESS_GLOBAL_HOME);
112
+ candidates.push(defaultGlobalDir());
113
+ const scriptVersion = readVersion(SCRIPT_ROOT);
114
+ if (scriptVersion?.installScope === 'global') candidates.push(SCRIPT_ROOT);
115
+
116
+ for (const candidate of candidates) {
117
+ if (!candidate) continue;
118
+ const resolved = path.resolve(candidate);
119
+ if (hasRunnableUpdater(resolved)) return resolved;
120
+ }
121
+ return null;
122
+ }
123
+
124
+ function dedupeTargets(targets) {
125
+ const seen = new Set();
126
+ const result = [];
127
+ for (const target of targets) {
128
+ const key = comparablePath(target.root);
129
+ if (seen.has(key)) continue;
130
+ seen.add(key);
131
+ result.push(target);
132
+ }
133
+ return result;
134
+ }
135
+
136
+ function discoverTargets(projectRoot) {
137
+ const targets = [];
138
+ const projectVersion = readVersion(projectRoot);
139
+ const projectHasUpdater = hasRunnableUpdater(projectRoot);
140
+ const globalRoot = discoverGlobalRuntime(projectVersion);
141
+
142
+ if (projectHasUpdater && !samePath(projectRoot, globalRoot)) {
143
+ targets.push({
144
+ scope: projectVersion?.installScope === 'global' && projectVersion?.globalDir ? 'project-bridge' : 'project',
145
+ root: projectRoot,
146
+ version: projectVersion?.generator || null,
147
+ });
148
+ }
149
+
150
+ if (globalRoot) {
151
+ const globalVersion = readVersion(globalRoot);
152
+ targets.push({
153
+ scope: 'global',
154
+ root: globalRoot,
155
+ version: globalVersion?.generator || null,
156
+ });
157
+ }
158
+
159
+ return dedupeTargets(targets);
160
+ }
161
+
162
+ function runNode(root, scriptPath, args, { parseJson = false } = {}) {
163
+ const result = spawnSync(process.execPath, [scriptPath, ...args], {
164
+ cwd: root,
165
+ encoding: 'utf8',
166
+ env: process.env,
167
+ });
168
+ const stdout = result.stdout || '';
169
+ const stderr = result.stderr || '';
170
+ let json = null;
171
+ if (parseJson && stdout.trim()) {
172
+ try {
173
+ json = JSON.parse(stdout.trim());
174
+ } catch {
175
+ json = { rawOutput: stdout };
176
+ }
177
+ }
178
+ return {
179
+ status: result.status ?? 1,
180
+ stdout,
181
+ stderr,
182
+ ...(parseJson ? { json } : {}),
183
+ };
184
+ }
185
+
186
+ function wantsApply(args) {
187
+ return args.includes('--apply') || args.includes('--apply-safe') || args.includes('--safe-only') || args.includes('--finalize');
188
+ }
189
+
190
+ function wantsRepair(args) {
191
+ return args.includes('--repair') || args.includes('--ignore-version') || args.includes('--force-check');
192
+ }
193
+
194
+ function ensureJsonArg(args) {
195
+ return args.includes('--json') ? args : [...args, '--json'];
196
+ }
197
+
198
+ function withRepair(args) {
199
+ return wantsRepair(args) ? args : [...args, '--repair'];
200
+ }
201
+
202
+ function runUpdateForTarget(target, forwardArgs) {
203
+ const parseJson = forwardArgs.includes('--json');
204
+ const script = updateScriptPath(target.root);
205
+ const first = runNode(target.root, script, forwardArgs, { parseJson });
206
+ const runs = [{ command: `node Harness/scripts/wf-update-check.mjs ${forwardArgs.join(' ')}`.trim(), ...first }];
207
+ let effective = first;
208
+
209
+ if (
210
+ parseJson
211
+ && first.status === 0
212
+ && shouldAutoRepair(first.json, target.version)
213
+ && !wantsRepair(forwardArgs)
214
+ ) {
215
+ const repairArgs = withRepair(forwardArgs);
216
+ const repair = runNode(target.root, script, repairArgs, { parseJson });
217
+ runs.push({ command: `node Harness/scripts/wf-update-check.mjs ${repairArgs.join(' ')}`.trim(), ...repair });
218
+ effective = repair;
219
+ }
220
+
221
+ return { ...target, update: effective, runs };
222
+ }
223
+
224
+ function runPostUpdate(target, forwardArgs) {
225
+ const post = [];
226
+ const apply = wantsApply(forwardArgs);
227
+ const syncScript = syncScriptPath(target.root);
228
+ if (target.scope === 'global' && fs.existsSync(syncScript)) {
229
+ const syncArgs = apply ? ['--apply', '--json'] : ['--json'];
230
+ post.push({
231
+ step: 'sync-host-global',
232
+ ...runNode(target.root, syncScript, syncArgs, { parseJson: true }),
233
+ });
234
+ }
235
+ if (apply && fs.existsSync(validateScriptPath(target.root))) {
236
+ post.push({
237
+ step: 'validate',
238
+ ...runNode(target.root, validateScriptPath(target.root), [], { parseJson: false }),
239
+ });
240
+ post.push({
241
+ step: 'manifest-audit',
242
+ ...runNode(target.root, validateScriptPath(target.root), ['--manifest-audit'], { parseJson: false }),
243
+ });
244
+ }
245
+ if (apply && target.scope !== 'global' && fs.existsSync(scanCleanScriptPath(target.root))) {
246
+ post.push({
247
+ step: 'scan-clean',
248
+ ...runNode(target.root, scanCleanScriptPath(target.root), ['--json'], { parseJson: true }),
249
+ });
250
+ }
251
+ return post;
252
+ }
253
+
254
+ function printText(result) {
255
+ if (!result.targets.length) {
256
+ console.error(result.message);
257
+ return;
258
+ }
259
+ for (const target of result.targets) {
260
+ console.log(`\n[${target.scope}] ${target.root}`);
261
+ for (const run of target.runs || []) {
262
+ if (run.stdout) process.stdout.write(run.stdout);
263
+ if (run.stderr) process.stderr.write(run.stderr);
264
+ }
265
+ for (const post of target.post || []) {
266
+ console.log(`[post:${post.step}] exit ${post.status}`);
267
+ if (post.stdout) process.stdout.write(post.stdout);
268
+ if (post.stderr) process.stderr.write(post.stderr);
269
+ }
270
+ }
271
+ }
272
+
273
+ function main() {
274
+ const projectRoot = path.resolve(readFlagValue(rawArgs, '--project') || process.cwd());
275
+ const forwardArgs = stripFlagWithValue(rawArgs, '--project');
276
+ const effectiveArgs = JSON_OUT ? ensureJsonArg(forwardArgs) : forwardArgs;
277
+ const targets = discoverTargets(projectRoot);
278
+
279
+ if (targets.length === 0) {
280
+ const result = {
281
+ success: false,
282
+ status: 'no-harness-target',
283
+ projectRoot: normalizePath(projectRoot),
284
+ message: 'No project Harness updater or global Harness runtime was found. No project files were modified.',
285
+ targets: [],
286
+ };
287
+ if (JSON_OUT) console.log(JSON.stringify(result, null, 2));
288
+ else printText(result);
289
+ process.exitCode = 1;
290
+ return;
291
+ }
292
+
293
+ const updatedTargets = targets.map(target => {
294
+ const withUpdate = runUpdateForTarget(target, effectiveArgs);
295
+ return {
296
+ ...withUpdate,
297
+ post: runPostUpdate(withUpdate, effectiveArgs),
298
+ };
299
+ });
300
+
301
+ const failures = [];
302
+ for (const target of updatedTargets) {
303
+ if (target.update.status !== 0) failures.push(`${target.scope}: update exit ${target.update.status}`);
304
+ for (const post of target.post || []) {
305
+ if (post.status !== 0) failures.push(`${target.scope}: ${post.step} exit ${post.status}`);
306
+ }
307
+ }
308
+
309
+ const result = {
310
+ success: failures.length === 0,
311
+ status: failures.length === 0 ? 'ok' : 'failed',
312
+ projectRoot: normalizePath(projectRoot),
313
+ targets: updatedTargets.map(target => ({
314
+ ...target,
315
+ root: normalizePath(target.root),
316
+ })),
317
+ ...(failures.length ? { failures } : {}),
318
+ };
319
+
320
+ if (JSON_OUT) console.log(JSON.stringify(result, null, 2));
321
+ else printText(result);
322
+ if (failures.length) process.exitCode = 1;
323
+ }
324
+
325
+ main();
@@ -39,5 +39,40 @@
39
39
  "claimMinReadRatioPercent": 95,
40
40
  "claimMinUpliftPoints": 2
41
41
  }
42
+ },
43
+ "server": {
44
+ "host": "127.0.0.1",
45
+ "port": 0
46
+ },
47
+ "terminal": {
48
+ "enabled": false,
49
+ "attachMode": false,
50
+ "defaultRuntime": "codex"
51
+ },
52
+ "peers": {
53
+ "allowlist": [
54
+ "claude",
55
+ "cc",
56
+ "codex",
57
+ "opencode",
58
+ "gemini",
59
+ "qwen",
60
+ "deepseek",
61
+ "pi",
62
+ "aider",
63
+ "goose",
64
+ "amp",
65
+ "crush",
66
+ "cline",
67
+ "plandex",
68
+ "openclaw",
69
+ "chatgpt",
70
+ "openai"
71
+ ]
72
+ },
73
+ "ui": {
74
+ "theme": "auto",
75
+ "language": "zh",
76
+ "reducedMotion": false
42
77
  }
43
78
  }
@@ -55,6 +55,14 @@ Claude or Codex must follow this order during bootstrap. This sequence is broade
55
55
 
56
56
  Before writing, identify the project state:
57
57
 
58
+ Choose the install scope before scaffold writes:
59
+
60
+ - `--install-scope project` (default): full Harness scaffold is project-local.
61
+ - `--install-scope global`: write shared runtime assets to the selected global Harness directory and copy Claude Code, Codex, and OpenCode command/skill surfaces to host-global directories while keeping `Harness/tasks/`, `Harness/PROGRESS.md`, project memory, research, architecture, and project settings under the target project.
62
+ - Use `--global-dir <dir>` when the global runtime location must be explicit.
63
+ - Use `--host-global-dir <dir>` only when the host-global copy base must be explicit; the installer creates `claude/`, `codex/`, and `opencode/` subdirectories under it.
64
+ - Project settings in `Harness/settings.json` override global settings defaults. Existing user-authored files at config, command, skill, or agent paths are preserved or surfaced for review unless they carry a Harness ownership marker.
65
+
58
66
  | Project state | Required action |
59
67
  | --- | --- |
60
68
  | Empty or new project | Run the scaffold, then follow this file for 0-1 bootstrap |
@@ -10,6 +10,21 @@ Memory is downstream of evidence. It records reusable patterns, not raw transcri
10
10
  - **L2 = Startup Digest**: `Harness/memory/startup-hints.md`. Lightweight hints loaded at session start. Not a replacement for full router.
11
11
  - **L3 = Detailed Durable Memory**: `Harness/memory/*.md` files. Loaded only when scenario matches via `routes.md`.
12
12
 
13
+ ## Project/Global Memory Boundary
14
+
15
+ - `Harness/tasks/` and `Harness/PROGRESS.md` are always project-local, even when the Harness runtime is installed globally.
16
+ - Project memory lives in `Harness/memory/` and records repo-specific lessons, preferences, and tool patterns.
17
+ - Global memory may live under the selected global Harness directory for cross-project lessons and preferences.
18
+ - Global memory must never store project task state, secrets, raw transcripts, or private project facts.
19
+
20
+ ## Project/Global Settings Boundary
21
+
22
+ - Project settings live in `Harness/settings.json` and override global settings.
23
+ - Global settings defaults live in the selected global Harness runtime under `Harness/settings.json`.
24
+ - Host-global settings are copied, not symlinked, into Claude Code, Codex, and OpenCode user config directories.
25
+ - Settings and memory must never store secrets; use external secret stores or environment variables.
26
+ - Existing user-authored config, command, skill, or agent files are user-owned unless they carry a Harness ownership marker.
27
+
13
28
  ## When To Write Memory
14
29
 
15
30
  Write memory when any condition applies:
@@ -11,7 +11,7 @@ Archived tasks move to `Harness/tasks/_archive/YYYY/<task-id>/`.
11
11
  ## What Is Never Archived
12
12
 
13
13
  - `Harness/tasks/_template/` - scaffold template, never moved.
14
- - `Harness/tasks/auto/` - WF-AUTO permanent state capsule, never moved unless
14
+ - `Harness/tasks/continuous/` - WF-AUTO/WF-AUTO-SPARK permanent state capsule, never moved unless
15
15
  explicitly allowed by WF-AUTO docs.
16
16
  - `Harness/tasks/_archive/` - the archive directory itself.
17
17
  - Active, blocked, in-progress, running, pending, or needs-user-decision tasks.
@@ -46,21 +46,32 @@ machine state.
46
46
  - Do NOT delete historical evidence.
47
47
  - `Harness/PROGRESS.md` keeps the last 5 non-archived task entries in the Task
48
48
  Index.
49
- - When outer task capsules exceed 5 completed/abandoned/obsolete tasks, archive
50
- the oldest safe tasks.
49
+ - When outer task capsules exceed 5 completed/abandoned/obsolete tasks, remind
50
+ the user to run the task archive command. Do not auto-archive during unrelated
51
+ validation or closeout unless the user explicitly invokes archive apply mode.
51
52
  - The validator (`Harness/scripts/validate-harness.mjs`) warns when
52
53
  `Harness/tasks/` holds more than 5 outer task capsules (excluding `_archive`,
53
54
  `_template`, `auto`) and fails in `--strict` mode.
54
55
 
55
56
  ## Script
56
57
 
57
- Use `Harness/scripts/task-state.mjs archive`:
58
+ User-facing command:
58
59
 
59
- - Default: dry-run.
60
+ - Codex: `$wf-task-archive` or `/skills wf-task-archive`.
61
+ - Claude Code/OpenCode: `/wf-task-archive`.
62
+
63
+ Underlying implementation: `Harness/scripts/task-state.mjs archive`.
64
+
65
+ - Default: dry-run (respects `--keep` cap, archives oldest-eligible up to cap).
60
66
  - `--apply` to execute.
67
+ - Explicit `--apply` with no `--task` filter: archives **ALL** eligible tasks, bypassing `--keep`.
61
68
  - `--keep 5` to set the non-archived task threshold.
62
69
  - `--task <task-id>` to archive a specific task.
63
70
  - `--json` for machine-readable output.
71
+ - After archiving, `Harness/tasks/_archive/INDEX.md` is regenerated with a full
72
+ task graph: roots, dependency chains, blocks relationships, and per-task details
73
+ (status, phase, goal, depends-on, blocks). The graph covers **all** archived tasks
74
+ across all years, so it stays accurate as tasks accumulate.
64
75
  - Run `node Harness/scripts/task-state.mjs archive --dry-run --json` before
65
76
  applying a risky cleanup.
66
77