harness-everything 0.31.2 → 0.31.3
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +19 -0
- package/bin/cli.js +1 -0
- package/ci/lib/isolated-test-process.js +30 -0
- package/ci/mechanism-2al-codex-hooks-compat.test.js +14 -14
- package/ci/mechanism-2ao-plugin-sync.test.js +19 -2
- package/ci/mechanism-2at-fable-execution-phases.test.js +46 -0
- package/ci/mechanism-2au-codex-runtime-safety.test.js +340 -0
- package/ci/mechanism-2av-test-home-isolation.test.js +48 -0
- package/ci/mechanism-test.js +2 -2
- package/docs/platform-capabilities.md +4 -0
- package/docs/platform-compatibility.json +1 -1
- package/fable-mode/SKILL.md +1 -1
- package/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/fable-mode/fable-haiku/SKILL.md +1 -1
- package/fable-mode/fable-opus/SKILL.md +1 -1
- package/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/fable-mode/references/execution-phases.md +93 -25
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +2 -2
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/references/execution-phases.md +93 -25
- package/plugins/harness-everything/skills/self-evolve/SKILL.md +1 -1
- package/plugins/harness-everything/skills/self-evolve/scripts/self-regression.js +8 -6
- package/sbom.cdx.json +7 -7
- package/scripts/codex-hooks-compat.js +265 -25
- package/self-evolve/SKILL.md +1 -1
- package/self-evolve/scripts/self-regression.js +8 -6
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "harness-everything",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
13
|
-
"version": "0.31.
|
|
13
|
+
"version": "0.31.3",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Miya Daniel"
|
|
16
16
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
3
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
4
|
-
"version": "0.31.
|
|
4
|
+
"version": "0.31.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Miya Daniel"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.31.3](https://github.com/dyphn1/Harness-everything/compare/v0.31.2...v0.31.3) (2026-10-16)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* **codex:** build runtime from tracked files and check previous runtime ([6bbb2c4](https://github.com/dyphn1/Harness-everything/commit/6bbb2c4c21c8bdecc69148e19126d0fe2b168328)), closes [#323](https://github.com/dyphn1/Harness-everything/issues/323)
|
|
10
|
+
* **codex:** guard stale-lock reclaim with token checks ([abf4026](https://github.com/dyphn1/Harness-everything/commit/abf40263766c33619f8a2c62dd3c16752774b654)), closes [#323](https://github.com/dyphn1/Harness-everything/issues/323)
|
|
11
|
+
* **codex:** retain compat runtimes and isolate test installs ([c5ee4e8](https://github.com/dyphn1/Harness-everything/commit/c5ee4e8c54dec758d77625913f1096aa281626a6))
|
|
12
|
+
* **codex:** scope source guards and isolate test runner homes ([c9cf30b](https://github.com/dyphn1/Harness-everything/commit/c9cf30b6bb1a336f4eff03037de0e08ab7bf49e7))
|
|
13
|
+
* **codex:** serialize compat installs on an owner lock ([b67e1c0](https://github.com/dyphn1/Harness-everything/commit/b67e1c0b95f16d0eae37485ab23ae4dfb1dc954a)), closes [#323](https://github.com/dyphn1/Harness-everything/issues/323) [#323](https://github.com/dyphn1/Harness-everything/issues/323)
|
|
14
|
+
|
|
5
15
|
## [0.31.2](https://github.com/dyphn1/Harness-everything/compare/v0.31.1...v0.31.2) (2026-10-15)
|
|
6
16
|
|
|
7
17
|
### Bug Fixes
|
|
@@ -29,6 +39,13 @@ All notable changes to this project are documented in this file.
|
|
|
29
39
|
|
|
30
40
|
## [Unreleased]
|
|
31
41
|
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- Codex source guards ignore unrelated repository dirtiness and untracked packaged copies; test runners isolate child HOME and CODEX_HOME directories (#323).
|
|
45
|
+
- Codex compatibility runtimes copy only Git-tracked plugin files, so ignored files cannot enter the runtime and do not block installs; `codex-hooks status` reports a missing or modified previous runtime as unhealthy, and the mechanism test fixture uses a portable Git config on Windows (#323).
|
|
46
|
+
- Codex compatibility install/update/uninstall serialize on an owner lock so concurrent installs cannot garbage-collect each other's current runtime; stale-lock recovery is guarded and token-checked so it cannot remove a live holder's lock (#323).
|
|
47
|
+
- Codex compatibility updates retain the current and previous runtime, reject unreleased checkout sources without explicit opt-in, and isolate plugin-sync tests from the real user hook installation (#323).
|
|
48
|
+
|
|
32
49
|
### Changes
|
|
33
50
|
|
|
34
51
|
* **workflow:** replace topology-wide skill/reference pushes with ordered active-step bindings; carry the same contract through dependency-ready Fable stages while keeping hooks and Stop fail-open (#297).
|
|
@@ -36,6 +53,8 @@ All notable changes to this project are documented in this file.
|
|
|
36
53
|
* **workflow:** resolve step/stage binding availability against real files (workspace references stay workspace-scoped; only recognized Harness bindings search installed plugin layouts); preserve knowledge signals across strategy recomposition; keep self-evolved manifest skills discoverable as metadata-only ids; positive skill evals assert skill-specific candidate bindings (#303 review).
|
|
37
54
|
* **workflow:** reject present non-array required/optional binding declarations before accepting a requirement plan, preserving workflow state on invalid input (#303 review).
|
|
38
55
|
* **test:** isolate verification fixtures from an inherited project-check recursion guard while preserving explicit guard coverage.
|
|
56
|
+
* **workflow:** document blocked Fable recovery by revising the stage map while retaining approved requirements (#316).
|
|
57
|
+
* **fable:** align execution phases with correlated workflow stages and scope the legacy/manual path (#317).
|
|
39
58
|
|
|
40
59
|
### Security
|
|
41
60
|
|
package/bin/cli.js
CHANGED
|
@@ -111,6 +111,7 @@ Commands:
|
|
|
111
111
|
codex-hooks Explicit Codex user-hook compatibility fallback for hosts
|
|
112
112
|
that fail to mount enabled plugin-bundled hooks.
|
|
113
113
|
Usage: harness codex-hooks <install|status|uninstall>
|
|
114
|
+
[--codex-home <path>] [--allow-unreleased-source]
|
|
114
115
|
Does not trust hooks or modify Codex config.toml.
|
|
115
116
|
plugin-sync Install or update the native Harness plugin on Claude Code
|
|
116
117
|
and Codex. Existing installations always take the update
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Do not rely on the caller remembering HOME/CODEX_HOME: even a test that
|
|
9
|
+
// accidentally invokes a real installer must inherit a disposable user home.
|
|
10
|
+
function spawnTestNode(args, options = {}) {
|
|
11
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-test-home-'));
|
|
12
|
+
try {
|
|
13
|
+
return spawnSync(process.execPath, args, {
|
|
14
|
+
...options,
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
...options.env,
|
|
18
|
+
HOME: home,
|
|
19
|
+
USERPROFILE: home,
|
|
20
|
+
CODEX_HOME: path.join(home, '.codex'),
|
|
21
|
+
APPDATA: path.join(home, 'AppData', 'Roaming'),
|
|
22
|
+
LOCALAPPDATA: path.join(home, 'AppData', 'Local'),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
} finally {
|
|
26
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { spawnTestNode };
|
|
@@ -64,7 +64,7 @@ fs.writeFileSync(configFile, 'model = "test"\n# user trust/config state\n', 'utf
|
|
|
64
64
|
const configBefore = fs.readFileSync(configFile, 'utf8');
|
|
65
65
|
|
|
66
66
|
try {
|
|
67
|
-
const installed = compat.install({ codexHome });
|
|
67
|
+
const installed = compat.install({ allowUnreleasedSource: true, codexHome });
|
|
68
68
|
check(installed.status === 'installed' && installed.entries > 0,
|
|
69
69
|
'explicit install materializes compatibility entries');
|
|
70
70
|
check(installed.trustChanged === false && fs.readFileSync(configFile, 'utf8') === configBefore,
|
|
@@ -95,7 +95,7 @@ try {
|
|
|
95
95
|
'compatibility runtime is self-contained for hook and router dependencies');
|
|
96
96
|
|
|
97
97
|
const countsBefore = Object.fromEntries(Object.entries(config.hooks).map(([event, entries]) => [event, entries.length]));
|
|
98
|
-
const updated = compat.install({ codexHome });
|
|
98
|
+
const updated = compat.install({ allowUnreleasedSource: true, codexHome });
|
|
99
99
|
const configAfterUpdate = read(hooksFile);
|
|
100
100
|
const countsAfter = Object.fromEntries(Object.entries(configAfterUpdate.hooks).map(([event, entries]) => [event, entries.length]));
|
|
101
101
|
check(updated.status === 'updated' && JSON.stringify(countsAfter) === JSON.stringify(countsBefore),
|
|
@@ -105,7 +105,7 @@ try {
|
|
|
105
105
|
const addedUserHook = { hooks: [{ type: 'command', command: 'node "/user/later.js"', timeout: 3 }] };
|
|
106
106
|
configAfterUpdate.hooks.PostToolUse.unshift(addedUserHook);
|
|
107
107
|
fs.writeFileSync(hooksFile, JSON.stringify(configAfterUpdate, null, 2) + '\n', 'utf8');
|
|
108
|
-
compat.install({ codexHome });
|
|
108
|
+
compat.install({ allowUnreleasedSource: true, codexHome });
|
|
109
109
|
check(read(hooksFile).hooks.PostToolUse.some(entry => entry.hooks?.[0]?.command === 'node "/user/later.js"'),
|
|
110
110
|
'update preserves user hooks added after the initial compatibility install');
|
|
111
111
|
|
|
@@ -125,7 +125,7 @@ try {
|
|
|
125
125
|
fs.writeFileSync(hooksFile, JSON.stringify(tampered, null, 2) + '\n', 'utf8');
|
|
126
126
|
const tamperedBytes = fs.readFileSync(hooksFile, 'utf8');
|
|
127
127
|
let updateRefused = false;
|
|
128
|
-
try { compat.install({ codexHome }); } catch (error) { updateRefused = /modified/.test(error.message); }
|
|
128
|
+
try { compat.install({ allowUnreleasedSource: true, codexHome }); } catch (error) { updateRefused = /modified/.test(error.message); }
|
|
129
129
|
check(updateRefused && fs.readFileSync(hooksFile, 'utf8') === tamperedBytes,
|
|
130
130
|
'modified Harness compatibility entry makes update fail closed without rewriting user config');
|
|
131
131
|
|
|
@@ -136,7 +136,7 @@ try {
|
|
|
136
136
|
|
|
137
137
|
// Restore the exact generated config by starting from the pre-tamper bytes.
|
|
138
138
|
fs.writeFileSync(hooksFile, beforeTamper, 'utf8');
|
|
139
|
-
compat.install({ codexHome });
|
|
139
|
+
compat.install({ allowUnreleasedSource: true, codexHome });
|
|
140
140
|
const removed = compat.uninstall({ codexHome });
|
|
141
141
|
const finalConfig = read(hooksFile);
|
|
142
142
|
check(removed.status === 'uninstalled' && removed.removed > 0 && removed.trustChanged === false,
|
|
@@ -153,7 +153,7 @@ try {
|
|
|
153
153
|
|
|
154
154
|
const emptyHome = path.join(root, 'empty-codex');
|
|
155
155
|
fs.mkdirSync(emptyHome, { recursive: true });
|
|
156
|
-
compat.install({ codexHome: emptyHome });
|
|
156
|
+
compat.install({ allowUnreleasedSource: true, codexHome: emptyHome });
|
|
157
157
|
check(fs.existsSync(path.join(emptyHome, 'hooks.json')),
|
|
158
158
|
'compatibility install can create a new hooks.json when none exists');
|
|
159
159
|
compat.uninstall({ codexHome: emptyHome });
|
|
@@ -164,7 +164,7 @@ try {
|
|
|
164
164
|
const marketplaceHome = path.join(root, 'marketplace-codex');
|
|
165
165
|
fs.cpSync(compat.SOURCE_PLUGIN_ROOT, marketplaceSource, { recursive: true });
|
|
166
166
|
fs.writeFileSync(path.join(marketplaceSource, 'hooks', 'marketplace-source-marker.txt'), 'newer marketplace source\n');
|
|
167
|
-
const marketplaceInstall = compat.install({ codexHome: marketplaceHome, sourcePluginRoot: marketplaceSource });
|
|
167
|
+
const marketplaceInstall = compat.install({ allowUnreleasedSource: true, codexHome: marketplaceHome, sourcePluginRoot: marketplaceSource });
|
|
168
168
|
const marketplaceManifest = read(path.join(marketplaceHome, 'harness-everything', 'compat-hooks', 'manifest.json'));
|
|
169
169
|
check(marketplaceInstall.sourcePluginSha256 === compat.treeHash(marketplaceSource) &&
|
|
170
170
|
marketplaceManifest.sourcePluginSha256 === compat.treeHash(marketplaceSource) &&
|
|
@@ -174,7 +174,7 @@ try {
|
|
|
174
174
|
|
|
175
175
|
const duplicateHome = path.join(root, 'duplicate-codex');
|
|
176
176
|
fs.mkdirSync(duplicateHome, { recursive: true });
|
|
177
|
-
compat.install({ codexHome: duplicateHome });
|
|
177
|
+
compat.install({ allowUnreleasedSource: true, codexHome: duplicateHome });
|
|
178
178
|
const duplicateConfig = read(path.join(duplicateHome, 'hooks.json'));
|
|
179
179
|
duplicateConfig.hooks.PreToolUse.push(JSON.parse(JSON.stringify(duplicateConfig.hooks.PreToolUse[0])));
|
|
180
180
|
fs.writeFileSync(path.join(duplicateHome, 'hooks.json'), JSON.stringify(duplicateConfig, null, 2) + '\n', 'utf8');
|
|
@@ -187,7 +187,7 @@ try {
|
|
|
187
187
|
const removedConfigHome = path.join(root, 'removed-config-codex');
|
|
188
188
|
fs.mkdirSync(removedConfigHome, { recursive: true });
|
|
189
189
|
fs.writeFileSync(path.join(removedConfigHome, 'hooks.json'), JSON.stringify(original, null, 2) + '\n', 'utf8');
|
|
190
|
-
compat.install({ codexHome: removedConfigHome });
|
|
190
|
+
compat.install({ allowUnreleasedSource: true, codexHome: removedConfigHome });
|
|
191
191
|
fs.rmSync(path.join(removedConfigHome, 'hooks.json'), { force: true });
|
|
192
192
|
const removedConfigResult = compat.uninstall({ codexHome: removedConfigHome });
|
|
193
193
|
check(removedConfigResult.status === 'uninstalled' && !fs.existsSync(path.join(removedConfigHome, 'hooks.json')),
|
|
@@ -206,7 +206,7 @@ try {
|
|
|
206
206
|
fs.writeFileSync(path.join(preexistingHome, 'hooks.json'),
|
|
207
207
|
JSON.stringify({ hooks: preexistingHooks }, null, 2) + '\n', 'utf8');
|
|
208
208
|
const preexistingBefore = fs.readFileSync(path.join(preexistingHome, 'hooks.json'), 'utf8');
|
|
209
|
-
const preexistingInstall = compat.install({ codexHome: preexistingHome });
|
|
209
|
+
const preexistingInstall = compat.install({ allowUnreleasedSource: true, codexHome: preexistingHome });
|
|
210
210
|
const preexistingManifest = read(path.join(preexistingHome, 'harness-everything', 'compat-hooks', 'manifest.json'));
|
|
211
211
|
check(preexistingInstall.status === 'installed' && preexistingManifest.entries.length === 0,
|
|
212
212
|
'byte-identical pre-existing user hooks are reused but never recorded as Harness-owned');
|
|
@@ -219,7 +219,7 @@ try {
|
|
|
219
219
|
fs.mkdirSync(forgedManifestHome, { recursive: true });
|
|
220
220
|
fs.writeFileSync(path.join(forgedManifestHome, 'hooks.json'),
|
|
221
221
|
JSON.stringify({ hooks: { PreToolUse: [userHook] } }, null, 2) + '\n', 'utf8');
|
|
222
|
-
compat.install({ codexHome: forgedManifestHome });
|
|
222
|
+
compat.install({ allowUnreleasedSource: true, codexHome: forgedManifestHome });
|
|
223
223
|
const forgedManifestPath = path.join(
|
|
224
224
|
forgedManifestHome, 'harness-everything', 'compat-hooks', 'manifest.json');
|
|
225
225
|
const forgedManifest = read(forgedManifestPath);
|
|
@@ -254,7 +254,7 @@ try {
|
|
|
254
254
|
}
|
|
255
255
|
return realRenameSync(source, destination);
|
|
256
256
|
};
|
|
257
|
-
try { compat.install({ codexHome: failedInstallHome }); }
|
|
257
|
+
try { compat.install({ allowUnreleasedSource: true, codexHome: failedInstallHome }); }
|
|
258
258
|
catch (error) { failedInstallRefused = /injected manifest commit failure/.test(error.message); }
|
|
259
259
|
finally { fs.renameSync = realRenameSync; }
|
|
260
260
|
check(failedInstallRefused &&
|
|
@@ -272,7 +272,7 @@ try {
|
|
|
272
272
|
process.platform === 'win32' ? 'junction' : 'dir'
|
|
273
273
|
);
|
|
274
274
|
let linkedOwnerRefused = false;
|
|
275
|
-
try { compat.install({ codexHome: linkedOwnerHome }); }
|
|
275
|
+
try { compat.install({ allowUnreleasedSource: true, codexHome: linkedOwnerHome }); }
|
|
276
276
|
catch (error) { linkedOwnerRefused = /linked|outside/.test(error.message); }
|
|
277
277
|
check(linkedOwnerRefused && fs.readdirSync(linkedOwnerOutside).length === 0,
|
|
278
278
|
'linked compatibility owner path cannot redirect writes outside Codex home');
|
|
@@ -283,7 +283,7 @@ try {
|
|
|
283
283
|
fs.writeFileSync(malformed, '{ this is not json', 'utf8');
|
|
284
284
|
const malformedBefore = fs.readFileSync(malformed, 'utf8');
|
|
285
285
|
let malformedRefused = false;
|
|
286
|
-
try { compat.install({ codexHome: malformedHome }); } catch (_) { malformedRefused = true; }
|
|
286
|
+
try { compat.install({ allowUnreleasedSource: true, codexHome: malformedHome }); } catch (_) { malformedRefused = true; }
|
|
287
287
|
check(malformedRefused && fs.readFileSync(malformed, 'utf8') === malformedBefore,
|
|
288
288
|
'malformed user hooks.json fails closed without replacement');
|
|
289
289
|
|
|
@@ -15,6 +15,17 @@ const {
|
|
|
15
15
|
|
|
16
16
|
const ROOT = path.resolve(__dirname, '..');
|
|
17
17
|
const codexHooksCompat = require('../scripts/codex-hooks-compat');
|
|
18
|
+
// A fake host runner does not mock the compatibility installer's filesystem
|
|
19
|
+
// writes. Fail closed if any case forgets to pass its explicit temporary home.
|
|
20
|
+
const realInstall = codexHooksCompat.install;
|
|
21
|
+
codexHooksCompat.install = options => {
|
|
22
|
+
assert(options && options.codexHome, 'tests must supply an explicit temporary Codex home');
|
|
23
|
+
const relative = path.relative(os.tmpdir(), path.resolve(options.codexHome));
|
|
24
|
+
assert(relative && !relative.startsWith('..') && !path.isAbsolute(relative),
|
|
25
|
+
'test Codex home must be inside the temporary directory');
|
|
26
|
+
return realInstall(options);
|
|
27
|
+
};
|
|
28
|
+
|
|
18
29
|
|
|
19
30
|
function fakeHost({ claudeInstalled = false, codexInstalled = false, codexPluginSourcePath = path.join(ROOT, 'plugins', 'harness-everything'), codexPluginSourceType = 'local', codexAvailableOnly = false, claudeMarketplace = true, codexMarketplace = true, unknownClaudePluginState = false, unknownClaudeYesFlag = false, unknownClaudeJsonFlag = false, clapCodexJsonFlag = false, missingCodexPluginCommand = false, codexHooksError = null } = {}) {
|
|
20
31
|
const calls = [];
|
|
@@ -283,7 +294,9 @@ for (const sourceType of ['git', 'npm', 'remote', 'unknown']) {
|
|
|
283
294
|
{
|
|
284
295
|
const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-plugin-sync-codex-home-'));
|
|
285
296
|
try {
|
|
286
|
-
const
|
|
297
|
+
const source = path.join(codexHome, 'plugin-cache');
|
|
298
|
+
fs.cpSync(codexHooksCompat.SOURCE_PLUGIN_ROOT, source, { recursive: true });
|
|
299
|
+
const fake = fakeHost({ codexInstalled: true, codexPluginSourcePath: source });
|
|
287
300
|
const result = sync({ host: 'codex', codexHome }, {
|
|
288
301
|
runner: fake.runner,
|
|
289
302
|
commandAvailable: fake.commandAvailable,
|
|
@@ -368,18 +381,22 @@ for (const sourceType of ['git', 'npm', 'remote', 'unknown']) {
|
|
|
368
381
|
// progress notice emitted before that work starts, the terminal stays
|
|
369
382
|
// silent for the whole run, which is indistinguishable from a hang.
|
|
370
383
|
const events = [];
|
|
384
|
+
const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-progress-codex-'));
|
|
371
385
|
const fake = fakeHost({ claudeInstalled: true, codexInstalled: true });
|
|
372
386
|
const runner = (command, args, opts) => {
|
|
373
387
|
events.push(`call:${command}`);
|
|
374
388
|
return fake.runner(command, args, opts);
|
|
375
389
|
};
|
|
376
|
-
const result = sync({ host: 'all', repository: 'dyphn1/Harness-everything', ref: 'main' }, {
|
|
390
|
+
const result = sync({ host: 'all', codexHome, repository: 'dyphn1/Harness-everything', ref: 'main' }, {
|
|
377
391
|
runner,
|
|
392
|
+
codexHooksInstall: fake.codexHooksInstall,
|
|
378
393
|
commandAvailable: () => true,
|
|
379
394
|
log: () => {},
|
|
380
395
|
warn: message => events.push(`warn:${message}`)
|
|
381
396
|
});
|
|
382
397
|
assert.strictEqual(result.failed, 0);
|
|
398
|
+
fs.rmSync(codexHome, { recursive: true, force: true });
|
|
399
|
+
assert.strictEqual(fake.codexHookInstalls.length, 1, 'progress test must mock hook installation as well as host commands');
|
|
383
400
|
assert.ok(events.includes('warn:[...] claude: syncing'), 'a claude progress notice must be emitted');
|
|
384
401
|
assert.ok(events.includes('warn:[...] codex: syncing'), 'a codex progress notice must be emitted');
|
|
385
402
|
assert.ok(events.indexOf('warn:[...] claude: syncing') < events.indexOf('call:claude'), 'the claude notice must be emitted before its first host command, not buffered until the run finishes');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
8
|
+
const guide = fs.readFileSync(path.join(ROOT, 'fable-mode/references/execution-phases.md'), 'utf8');
|
|
9
|
+
const packagedGuide = fs.readFileSync(path.join(ROOT, 'plugins/harness-everything/skills/fable-mode/references/execution-phases.md'), 'utf8');
|
|
10
|
+
const contract = fs.readFileSync(path.join(ROOT, 'fable-mode/CONTRACT-FORMAT.md'), 'utf8');
|
|
11
|
+
const activeStart = guide.indexOf('## Active workflow');
|
|
12
|
+
const recoveryStart = guide.indexOf('## Blocked recovery');
|
|
13
|
+
const legacyStart = guide.indexOf('## Legacy/manual fallback');
|
|
14
|
+
const active = guide.slice(activeStart, recoveryStart);
|
|
15
|
+
const legacyPath = '.claude/harness-everything/state/contracts/';
|
|
16
|
+
|
|
17
|
+
assert.strictEqual(packagedGuide, guide, 'OpenAI package includes the canonical execution phases reference');
|
|
18
|
+
assert.ok(activeStart >= 0 && recoveryStart > activeStart, 'active workflow and blocked recovery are explicit sections');
|
|
19
|
+
assert.ok(legacyStart > recoveryStart, 'legacy instructions are isolated after current recovery guidance');
|
|
20
|
+
assert.match(active, /workflow-stages\.json/);
|
|
21
|
+
assert.match(active, /workflow-disposition\.js.*start/s);
|
|
22
|
+
assert.match(active, /stage-binding.*--session-id.*--stage-id.*--binding-id.*--disposition.*--evidence/s);
|
|
23
|
+
assert.match(active, /contract-test\.js/);
|
|
24
|
+
assert.match(active, /worker identity/i);
|
|
25
|
+
assert.match(active, /exit code/i);
|
|
26
|
+
assert.match(active, /semantic MUSTs/i);
|
|
27
|
+
assert.match(active, /reminder-only/i);
|
|
28
|
+
assert.match(active, /without locking\s+ordinary tools or Stop/i);
|
|
29
|
+
assert.ok(!active.includes(legacyPath), 'active workflows never use the legacy contracts path');
|
|
30
|
+
assert.match(contract, /workflow-stages\.json/);
|
|
31
|
+
assert.match(contract, /stage results need observed worker identities and numeric exit codes/i);
|
|
32
|
+
|
|
33
|
+
const recovery = guide.slice(recoveryStart, legacyStart);
|
|
34
|
+
assert.match(recovery, /workflow already running; record a blocker before replanning/);
|
|
35
|
+
assert.match(recovery, /workflow-disposition\.js.*block/s);
|
|
36
|
+
assert.match(recovery, /workflow-stages\.json/);
|
|
37
|
+
assert.match(recovery, /workflow-disposition\.js.*start/s);
|
|
38
|
+
assert.match(recovery, /retaining|preserving/i);
|
|
39
|
+
assert.match(recovery, /plan.*blocked|blocked.*plan/s);
|
|
40
|
+
|
|
41
|
+
assert.match(guide, /behavior profile|role\/profile alias/i);
|
|
42
|
+
assert.match(guide, /explicit user or\s+host runtime choice/i);
|
|
43
|
+
assert.doesNotMatch(guide, /Resolve requested model|Opus macro work|do not silently change models/i);
|
|
44
|
+
assert.match(guide.slice(legacyStart), /`\.claude\/harness-everything\/state\/contracts\//);
|
|
45
|
+
|
|
46
|
+
console.log('PASS Fable execution phases match the active workflow contract and preserve a scoped legacy fallback');
|