harness-everything 0.47.1 → 0.47.2
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/.github/workflows/ci.yml +12 -0
- package/CHANGELOG.md +9 -0
- package/benchmarks/issue290-pilot/README.md +199 -0
- package/benchmarks/issue290-pilot/analyze.py +299 -0
- package/benchmarks/issue290-pilot/analyze.test.py +146 -0
- package/benchmarks/issue290-pilot/check.py +19 -0
- package/benchmarks/issue290-pilot/evidence/archive.json +7 -0
- package/benchmarks/issue290-pilot/evidence/group-summary.json +684 -0
- package/benchmarks/issue290-pilot/evidence/inventory.json +3728 -0
- package/benchmarks/issue290-pilot/evidence/ledger.json +4644 -0
- package/benchmarks/issue290-pilot/evidence/replay/pairs.json +482 -0
- package/benchmarks/issue290-pilot/evidence/replay/provenance.json +24 -0
- package/benchmarks/issue290-pilot/evidence/replay/tables.md +37 -0
- package/benchmarks/issue290-pilot/evidence.tar.gz +0 -0
- package/benchmarks/issue290-pilot/export.py +110 -0
- package/benchmarks/issue290-pilot/export.test.py +65 -0
- package/benchmarks/issue290-pilot/fable-supplement.py +55 -0
- package/benchmarks/issue290-pilot/fixture.test.py +28 -0
- package/benchmarks/issue290-pilot/fixtures/polyglot/instructions.md +19 -0
- package/benchmarks/issue290-pilot/fixtures/polyglot/package.json +29 -0
- package/benchmarks/issue290-pilot/fixtures/polyglot/simple-linked-list.js +44 -0
- package/benchmarks/issue290-pilot/fixtures/polyglot/simple-linked-list.spec.js +131 -0
- package/benchmarks/issue290-pilot/fixtures/quixbugs/LICENSE +7 -0
- package/benchmarks/issue290-pilot/fixtures/quixbugs/cases.json +7 -0
- package/benchmarks/issue290-pilot/fixtures/quixbugs/find_in_sorted.py +31 -0
- package/benchmarks/issue290-pilot/fixtures/quixbugs/test_find_in_sorted.py +15 -0
- package/benchmarks/issue290-pilot/frozen-documents.json +41 -0
- package/benchmarks/issue290-pilot/grader.cjs +43 -0
- package/benchmarks/issue290-pilot/grader.test.cjs +10 -0
- package/benchmarks/issue290-pilot/hook.test.cjs +27 -0
- package/benchmarks/issue290-pilot/host-hook.cjs +35 -0
- package/benchmarks/issue290-pilot/metrics.test.py +131 -0
- package/benchmarks/issue290-pilot/preflight/amendment.md +32 -0
- package/benchmarks/issue290-pilot/preflight/environment.json +15 -0
- package/benchmarks/issue290-pilot/preflight/fable-amendment.md +20 -0
- package/benchmarks/issue290-pilot/preflight/fable-frozen-files.json +4 -0
- package/benchmarks/issue290-pilot/preflight/fable-infrastructure-amendment.md +27 -0
- package/benchmarks/issue290-pilot/preflight/fable-retry-frozen-files.json +4 -0
- package/benchmarks/issue290-pilot/preflight/fable-supplement-v1.py +47 -0
- package/benchmarks/issue290-pilot/preflight/frozen-files.json +18 -0
- package/benchmarks/issue290-pilot/preflight/graders.json +67 -0
- package/benchmarks/issue290-pilot/pricing-assumptions.json +29 -0
- package/benchmarks/issue290-pilot/protocol.json +32 -0
- package/benchmarks/issue290-pilot/read_metrics.py +166 -0
- package/benchmarks/issue290-pilot/replay.py +167 -0
- package/benchmarks/issue290-pilot/reporting.py +82 -0
- package/benchmarks/issue290-pilot/run.py +262 -0
- package/benchmarks/issue290-pilot/sources.json +10 -0
- package/benchmarks/issue290-pilot/verification/analyzer-tests.log +5 -0
- package/benchmarks/issue290-pilot/verification/final-gates.json +58 -0
- package/benchmarks/issue290-pilot/verification/fixture-tests.log +5 -0
- package/benchmarks/issue290-pilot/verification/gates.json +58 -0
- package/benchmarks/issue290-pilot/verification/npm-audit.json +84 -0
- package/benchmarks/issue290-pilot/verification/offline-correction.json +59 -0
- package/benchmarks/issue290-pilot/verification/post-report/helper-tests.log +22 -0
- package/benchmarks/issue290-pilot/verification/post-report/test-consistency.log +626 -0
- package/benchmarks/issue290-pilot/verification/post-report/test-references.log +6 -0
- package/benchmarks/issue290-pilot/verification/post-report/waza-tokens.log +710 -0
- package/benchmarks/issue290-pilot/verification/test-collision.log +28 -0
- package/benchmarks/issue290-pilot/verification/test-consistency.log +28 -0
- package/benchmarks/issue290-pilot/verification/test-docs-capabilities.log +131 -0
- package/benchmarks/issue290-pilot/verification/test-mechanism.log +1829 -0
- package/benchmarks/issue290-pilot/verification/test-mutations.log +16 -0
- package/benchmarks/issue290-pilot/verification/test-platform-compatibility.log +9 -0
- package/benchmarks/issue290-pilot/verification/test-plugin-openai.log +6 -0
- package/benchmarks/issue290-pilot/verification/test-plugin-submission.log +5 -0
- package/benchmarks/issue290-pilot/verification/test-references.log +6 -0
- package/benchmarks/issue290-pilot/verification/test-release.log +59 -0
- package/benchmarks/issue290-pilot/verification/test-repo-contract.log +5 -0
- package/benchmarks/issue290-pilot/verification/test-routing-invariants.log +785 -0
- package/benchmarks/issue290-pilot/verification/test-routing-skills.log +28 -0
- package/benchmarks/issue290-pilot/verification/test.log +3373 -0
- package/benchmarks/issue290-pilot/verification/verification-report.md +76 -0
- package/benchmarks/issue290-pilot/verification/waza-tokens.log +710 -0
- package/benchmarks/issue290-pilot/verification/with-dependencies/gates.json +26 -0
- package/benchmarks/issue290-pilot/verification/with-dependencies/test-collision.log +8 -0
- package/benchmarks/issue290-pilot/verification/with-dependencies/test-consistency.log +626 -0
- package/benchmarks/issue290-pilot/verification/with-dependencies/test-mechanism.log +1693 -0
- package/benchmarks/issue290-pilot/verification/with-dependencies/test-release.log +38 -0
- package/benchmarks/issue290-pilot/verification/with-dependencies/test-routing-skills.log +44 -0
- package/benchmarks/issue290-pilot/verification/with-dependencies/test.log +3253 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/sbom.cdx.json +7 -7
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import tarfile
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
exporter = importlib.import_module('export')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SanitizerTest(unittest.TestCase):
|
|
13
|
+
def secrets(self):
|
|
14
|
+
return ['sk-'+'a'*24, 'ghp_'+'b'*36, 'github_pat_'+'c'*32,
|
|
15
|
+
'xoxb-'+'d'*24, 'AKIA'+'E'*16,
|
|
16
|
+
'Authorization: Bearer '+'f'*32,
|
|
17
|
+
'Authorization: Basic '+'Z'*24,
|
|
18
|
+
'-----BEGIN RSA '+'PRIVATE KEY-----',
|
|
19
|
+
'api_key='+'g'*32, 'password='+'h'*20,
|
|
20
|
+
json.dumps({'client_secret':'i'*32})]
|
|
21
|
+
|
|
22
|
+
def test_positive_classes_fail_before_and_after_json_encoding(self):
|
|
23
|
+
for value in self.secrets():
|
|
24
|
+
for text in (value, json.dumps({'log':value})):
|
|
25
|
+
with self.subTest(category=value[:8]), self.assertRaises(ValueError):
|
|
26
|
+
exporter.sanitize(text)
|
|
27
|
+
|
|
28
|
+
def test_negative_examples_remain_publishable(self):
|
|
29
|
+
for text in ['api_key=<REDACTED>', 'Authorization: Bearer <TOKEN>',
|
|
30
|
+
'password=${PASSWORD}', 'ghp_example', 'sk-short',
|
|
31
|
+
'public-key SHA256:abcdef', 'api_key = os.environ.get("KEY")']:
|
|
32
|
+
exporter.validate_no_secrets(exporter.sanitize(text))
|
|
33
|
+
|
|
34
|
+
def test_final_directory_scan_covers_metadata_and_logs(self):
|
|
35
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
36
|
+
root = Path(directory)
|
|
37
|
+
(root/'inventory.json').write_text(json.dumps({'note': self.secrets()[0]}))
|
|
38
|
+
with self.assertRaises(ValueError):
|
|
39
|
+
exporter.scan_final([root])
|
|
40
|
+
(root/'inventory.json').write_text('{}')
|
|
41
|
+
(root/'stderr.log').write_text(self.secrets()[5])
|
|
42
|
+
with self.assertRaises(ValueError):
|
|
43
|
+
exporter.scan_final([root])
|
|
44
|
+
|
|
45
|
+
def test_final_archive_scan_covers_contents_and_member_names(self):
|
|
46
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
47
|
+
target = Path(directory)/'final.tar.gz'
|
|
48
|
+
for name, body in [('metadata.json', self.secrets()[6]), (self.secrets()[1]+'.txt', '{}')]:
|
|
49
|
+
with tarfile.open(target, 'w:gz') as archive:
|
|
50
|
+
data = body.encode()
|
|
51
|
+
member = tarfile.TarInfo(name)
|
|
52
|
+
member.size = len(data)
|
|
53
|
+
archive.addfile(member, io.BytesIO(data))
|
|
54
|
+
with self.assertRaises(ValueError):
|
|
55
|
+
exporter.scan_final([target])
|
|
56
|
+
|
|
57
|
+
def test_private_reasoning_is_removed_structurally(self):
|
|
58
|
+
value = {'content':[{'type':'thinking','thinking':'private content','signature':'opaque'}], 'safe':1}
|
|
59
|
+
clean = exporter.strip_private_reasoning(value)
|
|
60
|
+
self.assertNotIn('private content', json.dumps(clean))
|
|
61
|
+
self.assertEqual(clean['safe'], 1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == '__main__':
|
|
65
|
+
unittest.main()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Preregistered Fable-only supplement; original bounded-stage rows stay retained."""
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
import time
|
|
9
|
+
import shutil
|
|
10
|
+
|
|
11
|
+
HERE=Path(__file__).resolve().parent
|
|
12
|
+
spec=importlib.util.spec_from_file_location('pilot',HERE/'run.py')
|
|
13
|
+
pilot=importlib.util.module_from_spec(spec)
|
|
14
|
+
spec.loader.exec_module(pilot)
|
|
15
|
+
original_prepare=pilot.prepare
|
|
16
|
+
WORKLOAD='polyglot-simple-linked-list-staged'
|
|
17
|
+
PROMPT="Use Fable staged execution. "+pilot.PROMPTS[WORKLOAD]
|
|
18
|
+
|
|
19
|
+
def linked_prepare(*args, **kwargs):
|
|
20
|
+
episode,primary,hooks=original_prepare(*args,**kwargs)
|
|
21
|
+
linked=Path(tempfile.mkdtemp(prefix='harness290-fable-'))
|
|
22
|
+
subprocess.run(['git','worktree','add','-q','-b','pilot-linked',str(linked)],cwd=primary,check=True)
|
|
23
|
+
shutil.copyfile(primary/'hook-settings.json',linked/'hook-settings.json')
|
|
24
|
+
for name in ['hook-settings.json','.pilot.json','simple-linked-list.js','dev-check.cjs']:
|
|
25
|
+
if not (linked/name).is_file():raise RuntimeError(f'linked launch prerequisite missing: {name}')
|
|
26
|
+
return episode,linked,hooks
|
|
27
|
+
|
|
28
|
+
if __name__=='__main__':
|
|
29
|
+
import argparse
|
|
30
|
+
parser=argparse.ArgumentParser();parser.add_argument('--output',required=True)
|
|
31
|
+
parser.add_argument('--engine',choices=['codex','claude'])
|
|
32
|
+
args=parser.parse_args();base=Path(args.output).resolve();base.mkdir(parents=True,exist_ok=True)
|
|
33
|
+
pilot.prepare=linked_prepare
|
|
34
|
+
pilot.PROMPTS[WORKLOAD]=PROMPT
|
|
35
|
+
start=time.monotonic()
|
|
36
|
+
for model in pilot.PROTOCOL['models']:
|
|
37
|
+
if args.engine and model['engine']!=args.engine:continue
|
|
38
|
+
for repeat in [1,2]:
|
|
39
|
+
for arm in (['A','B'] if repeat==1 else ['B','A']):
|
|
40
|
+
existing_seconds=0;existing_output=0
|
|
41
|
+
roots={Path('/tmp/issue290-formal-20261014').resolve(),Path('/tmp/issue290-fable-20261014').resolve(),base}
|
|
42
|
+
for root in roots:
|
|
43
|
+
for file in root.glob('*/result.json'):
|
|
44
|
+
existing_seconds+=json.loads(file.read_text())['wall_seconds']
|
|
45
|
+
for file in root.glob('*/transcript.jsonl'):
|
|
46
|
+
for line in file.read_text().splitlines():
|
|
47
|
+
try:e=json.loads(line)
|
|
48
|
+
except json.JSONDecodeError:continue
|
|
49
|
+
if e.get('type') in ['result','turn.completed']:
|
|
50
|
+
existing_output+=e.get('usage',{}).get('output_tokens',0)
|
|
51
|
+
if existing_seconds>pilot.PROTOCOL['aggregate_wall_budget_seconds'] or existing_output>pilot.PROTOCOL['aggregate_output_token_cap']:
|
|
52
|
+
raise SystemExit('shared original budget reached; remaining Fable rows unattempted')
|
|
53
|
+
episode=pilot.run_episode(base,model,WORKLOAD,arm,repeat)
|
|
54
|
+
pilot.require_router_attribution(episode)
|
|
55
|
+
pilot.summarize(base)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import tempfile
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
spec=importlib.util.spec_from_file_location('supplement',Path(__file__).with_name('fable-supplement.py'))
|
|
7
|
+
supp=importlib.util.module_from_spec(spec);spec.loader.exec_module(supp)
|
|
8
|
+
|
|
9
|
+
class LinkedFixtureTest(unittest.TestCase):
|
|
10
|
+
def test_claude_launch_settings_exist_in_linked_checkout(self):
|
|
11
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
12
|
+
episode,workspace,hooks=supp.linked_prepare(Path(directory),supp.WORKLOAD,'B',
|
|
13
|
+
supp.pilot.PROTOCOL['models'][1],0)
|
|
14
|
+
self.assertTrue((workspace/'hook-settings.json').is_file(),
|
|
15
|
+
'Claude --settings must exist before any model launch')
|
|
16
|
+
self.assertTrue((workspace/'.git').is_file(),'must use the linked checkout')
|
|
17
|
+
self.assertTrue((workspace/'.pilot.json').is_file())
|
|
18
|
+
|
|
19
|
+
def test_router_attribution_is_required_before_next_trial(self):
|
|
20
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
21
|
+
root=Path(directory)
|
|
22
|
+
with self.assertRaises(RuntimeError):supp.pilot.require_router_attribution(root)
|
|
23
|
+
(root/'router.txt').write_text('unrelated stdout')
|
|
24
|
+
with self.assertRaises(RuntimeError):supp.pilot.require_router_attribution(root)
|
|
25
|
+
(root/'router.txt').write_text('ROUTER WORKFLOW PLAN')
|
|
26
|
+
supp.pilot.require_router_attribution(root)
|
|
27
|
+
|
|
28
|
+
if __name__=='__main__':unittest.main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Instructions
|
|
2
|
+
|
|
3
|
+
Write a prototype of the music player application.
|
|
4
|
+
|
|
5
|
+
For the prototype, each song will simply be represented by a number.
|
|
6
|
+
Given a range of numbers (the song IDs), create a singly linked list.
|
|
7
|
+
|
|
8
|
+
Given a singly linked list, you should be able to reverse the list to play the songs in the opposite order.
|
|
9
|
+
|
|
10
|
+
~~~~exercism/note
|
|
11
|
+
The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures.
|
|
12
|
+
|
|
13
|
+
The simplest kind of linked list is a **singly** linked list.
|
|
14
|
+
That means that each element (or "node") contains data, along with something that points to the next node in the list.
|
|
15
|
+
|
|
16
|
+
If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings.
|
|
17
|
+
|
|
18
|
+
[intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d
|
|
19
|
+
~~~~
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exercism/javascript-simple-linked-list",
|
|
3
|
+
"description": "Exercism exercises in Javascript.",
|
|
4
|
+
"author": "Katrina Owen",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/exercism/javascript",
|
|
10
|
+
"directory": "exercises/practice/simple-linked-list"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@babel/core": "^7.25.2",
|
|
14
|
+
"@exercism/babel-preset-javascript": "^0.2.1",
|
|
15
|
+
"@exercism/eslint-config-javascript": "^0.6.0",
|
|
16
|
+
"@types/jest": "^29.5.12",
|
|
17
|
+
"@types/node": "^20.12.12",
|
|
18
|
+
"babel-jest": "^29.6.4",
|
|
19
|
+
"core-js": "~3.37.1",
|
|
20
|
+
"eslint": "^8.49.0",
|
|
21
|
+
"jest": "^29.7.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "jest ./*",
|
|
26
|
+
"watch": "jest --watch ./*",
|
|
27
|
+
"lint": "eslint ."
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// This is only a SKELETON file for the 'Simple Linked List' exercise. It's been provided as a
|
|
3
|
+
// convenience to get you started writing code faster.
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
export class Element {
|
|
7
|
+
constructor() {
|
|
8
|
+
throw new Error('Remove this statement and implement this function');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get value() {
|
|
12
|
+
throw new Error('Remove this statement and implement this function');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get next() {
|
|
16
|
+
throw new Error('Remove this statement and implement this function');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class List {
|
|
21
|
+
constructor() {
|
|
22
|
+
throw new Error('Remove this statement and implement this function');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
add(nextValue) {
|
|
26
|
+
throw new Error('Remove this statement and implement this function');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get length() {
|
|
30
|
+
throw new Error('Remove this statement and implement this function');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get head() {
|
|
34
|
+
throw new Error('Remove this statement and implement this function');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
toArray() {
|
|
38
|
+
throw new Error('Remove this statement and implement this function');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
reverse() {
|
|
42
|
+
throw new Error('Remove this statement and implement this function');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { List, Element } from './simple-linked-list';
|
|
2
|
+
|
|
3
|
+
describe('Element class', () => {
|
|
4
|
+
test('has constructor', () => {
|
|
5
|
+
const element = new Element(1);
|
|
6
|
+
expect(element.value).toEqual(1);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
xtest('value reflects constructor arg', () => {
|
|
10
|
+
const element = new Element(2);
|
|
11
|
+
expect(element.value).toEqual(2);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
xtest('has null for next by default', () => {
|
|
15
|
+
const element = new Element(1);
|
|
16
|
+
expect(element.next).toEqual(null);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('List class', () => {
|
|
21
|
+
xtest('has constructor', () => {
|
|
22
|
+
const list = new List();
|
|
23
|
+
expect(list).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
xtest('new lists should have length 0', () => {
|
|
27
|
+
const list = new List();
|
|
28
|
+
expect(list.length).toEqual(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
xtest('can add a element', () => {
|
|
32
|
+
const list = new List();
|
|
33
|
+
const element = new Element(1);
|
|
34
|
+
expect(() => list.add(element)).not.toThrow();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
xtest('adding a element increments length', () => {
|
|
38
|
+
const list = new List();
|
|
39
|
+
const element = new Element(1);
|
|
40
|
+
list.add(element);
|
|
41
|
+
expect(list.length).toEqual(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
xtest('adding two elements increments twice', () => {
|
|
45
|
+
const list = new List();
|
|
46
|
+
const element1 = new Element(1);
|
|
47
|
+
const element2 = new Element(3);
|
|
48
|
+
list.add(element1);
|
|
49
|
+
list.add(element2);
|
|
50
|
+
expect(list.length).toEqual(2);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
xtest('new Lists have a null head element', () => {
|
|
54
|
+
const list = new List();
|
|
55
|
+
expect(list.head).toEqual(null);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
xtest('adding an Element to an empty list sets the head Element', () => {
|
|
59
|
+
const list = new List();
|
|
60
|
+
const element = new Element(1);
|
|
61
|
+
list.add(element);
|
|
62
|
+
expect(list.head.value).toEqual(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
xtest('adding a second Element updates the head Element', () => {
|
|
66
|
+
const list = new List();
|
|
67
|
+
const element1 = new Element(1);
|
|
68
|
+
const element2 = new Element(3);
|
|
69
|
+
list.add(element1);
|
|
70
|
+
list.add(element2);
|
|
71
|
+
expect(list.head.value).toEqual(3);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
xtest('can get the next Element from the head', () => {
|
|
75
|
+
const list = new List();
|
|
76
|
+
const element1 = new Element(1);
|
|
77
|
+
const element2 = new Element(3);
|
|
78
|
+
list.add(element1);
|
|
79
|
+
list.add(element2);
|
|
80
|
+
expect(list.head.next.value).toEqual(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
xtest('can be initialized with an array', () => {
|
|
84
|
+
const list = new List([1, 2, 3]);
|
|
85
|
+
expect(list.length).toEqual(3);
|
|
86
|
+
expect(list.head.value).toEqual(3);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Lists with multiple elements', () => {
|
|
91
|
+
let list;
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
list = new List([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
|
94
|
+
});
|
|
95
|
+
xtest('with correct length', () => {
|
|
96
|
+
expect(list.length).toEqual(10);
|
|
97
|
+
});
|
|
98
|
+
xtest('with correct head value', () => {
|
|
99
|
+
expect(list.head.value).toEqual(10);
|
|
100
|
+
});
|
|
101
|
+
xtest('can traverse the list', () => {
|
|
102
|
+
expect(list.head.next.next.next.value).toEqual(7);
|
|
103
|
+
});
|
|
104
|
+
xtest('can convert to an array', () => {
|
|
105
|
+
const oneList = new List([1]);
|
|
106
|
+
expect(oneList.toArray()).toEqual([1]);
|
|
107
|
+
});
|
|
108
|
+
xtest('head of list is final element from input array', () => {
|
|
109
|
+
const twoList = new List([1, 2]);
|
|
110
|
+
expect(twoList.head.value).toEqual(2);
|
|
111
|
+
});
|
|
112
|
+
xtest('can convert longer list to an array', () => {
|
|
113
|
+
expect(list.toArray()).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
|
|
114
|
+
});
|
|
115
|
+
xtest('can be reversed', () => {
|
|
116
|
+
const twoList = new List([1, 2]);
|
|
117
|
+
expect(twoList.reverse().toArray()).toEqual([1, 2]);
|
|
118
|
+
});
|
|
119
|
+
xtest('can be reversed when it has more elements', () => {
|
|
120
|
+
const threeList = new List([1, 2, 3]);
|
|
121
|
+
expect(threeList.reverse().toArray()).toEqual([1, 2, 3]);
|
|
122
|
+
});
|
|
123
|
+
xtest('can reverse with many elements', () => {
|
|
124
|
+
expect(list.reverse().toArray()).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
|
125
|
+
});
|
|
126
|
+
xtest('can reverse a reversal', () => {
|
|
127
|
+
expect(list.reverse().reverse().toArray()).toEqual([
|
|
128
|
+
10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2017-2019 James Koppel
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
[[[3, 4, 5, 5, 5, 5, 6], 5], 3]
|
|
2
|
+
[[[1, 2, 3, 4, 6, 7, 8], 5], -1]
|
|
3
|
+
[[[1, 2, 3, 4, 6, 7, 8], 4], 3]
|
|
4
|
+
[[[2, 4, 6, 8, 10, 12, 14, 16, 18, 20], 18], 8]
|
|
5
|
+
[[[3, 5, 6, 7, 8, 9, 12, 13, 14, 24, 26, 27], 0], -1]
|
|
6
|
+
[[[3, 5, 6, 7, 8, 9, 12, 12, 14, 24, 26, 27], 12], 6]
|
|
7
|
+
[[[24, 26, 28, 50, 59], 101], -1]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
def find_in_sorted(arr, x):
|
|
2
|
+
def binsearch(start, end):
|
|
3
|
+
if start == end:
|
|
4
|
+
return -1
|
|
5
|
+
mid = start + (end - start) // 2
|
|
6
|
+
if x < arr[mid]:
|
|
7
|
+
return binsearch(start, mid)
|
|
8
|
+
elif x > arr[mid]:
|
|
9
|
+
return binsearch(mid, end)
|
|
10
|
+
else:
|
|
11
|
+
return mid
|
|
12
|
+
|
|
13
|
+
return binsearch(0, len(arr))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
Binary Search
|
|
20
|
+
|
|
21
|
+
Input:
|
|
22
|
+
arr: A sorted list of ints
|
|
23
|
+
x: A value to find
|
|
24
|
+
|
|
25
|
+
Output:
|
|
26
|
+
An index i such that arr[i] == x, or -1 if x not in arr
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> find_in_sorted([3, 4, 5, 5, 5, 5, 6], 5)
|
|
30
|
+
3
|
|
31
|
+
"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from load_testdata import load_json_testcases
|
|
3
|
+
|
|
4
|
+
if pytest.use_correct:
|
|
5
|
+
from correct_python_programs.find_in_sorted import find_in_sorted
|
|
6
|
+
else:
|
|
7
|
+
from python_programs.find_in_sorted import find_in_sorted
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
testdata = load_json_testcases(find_in_sorted.__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.parametrize("input_data,expected", testdata)
|
|
14
|
+
def test_find_in_sorted(input_data, expected):
|
|
15
|
+
assert find_in_sorted(*input_data) == expected
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source_revision": "ac519fa204ec90feb5067bc2525381b8820d045e",
|
|
3
|
+
"documents": {
|
|
4
|
+
"fable-mode/CONTRACT-FORMAT.md": {
|
|
5
|
+
"source_sha256": "c887fe5bb4139d232d522199ac7ae77a29cbf89b65ac44d47c50b2035edc727f",
|
|
6
|
+
"content": "# Fable Stage Contract Format\n\nIssue #85 Phase 3 makes the stage map a machine-readable execution contract.\nThe router still chooses only the execution topology. Fable owns the stage map,\nmodel selector, worker briefs, stage audit, advisory replan limits, and cold\nverifier.\nPhase 4 adds an optional bounded ensemble modifier without creating a new base\ntopology.\n\nRun state is stored under the existing workspace-keyed global Harness state\nroot, never inside the repository:\n\n```text\n<workspace-state>/state/fable-runs/<runId>/\n run.json\n contracts/<stageId>.json\n evidence/<stageId>.json\n ensemble.json # only when ensemble review executes\n```\n\n`planId` is a deterministic content hash of the router contract. `runId` is\nunique per orchestration. Two runs of the same plan therefore share a `planId`\nbut never share mutable stage-contract files.\n\n## Stage schema v2\n\nEvery planned stage declares dependency and write scope before dispatch:\n\n```json\n{\n \"stageId\": \"stage-3\",\n \"goal\": \"implement one bounded objective\",\n \"agent\": \"fable-worker-sonnet\",\n \"task\": \"self-contained task statement\",\n \"inputs\": [\"relevant files\", \"upstream outputs\"],\n \"expectedOutputs\": [\"artifact or evidence\"],\n \"outputPath\": \"path/to/artifact-or-null\",\n \"dependsOn\": [\"stage-1\"],\n \"writeSet\": [\"src/auth\", \"tests/auth.test.js\"],\n \"requiredBindings\": [{ \"id\": \"tdd\", \"path\": \"tdd/SKILL.md\" }],\n \"optionalBindings\": [{ \"id\": \"security-review\", \"path\": \"security-review/SKILL.md\" }],\n \"checkCommand\": \"node --test tests/auth.test.js\",\n \"passCondition\": \"exit 0\",\n \"failureReturn\": \"failure summary + evidence + missing prerequisite\"\n}\n```\n\nRules:\n\n- `stageId`, `goal`, `agent`, and `task` are required.\n- `dependsOn` is always explicit. Use `[]` for a root stage.\n- `writeSet` is always explicit. Use `[]` for a read-only stage.\n- `requiredBindings` and `optionalBindings` are optional arrays. Each entry is a stable `id` and may declare a concrete repository-relative `path`; paths reject absolute names, globs, and traversal. Availability is `available` only when the path resolves to a readable file: workspace references resolve only under the workspace, while recognized Harness bindings (a registered id at its registered path, or a skill id naming its own `<id>/SKILL.md`) also search installed Harness skill roots (`missing` otherwise; `unverified` when no hook runtime is installed). An id without a path resolves through self-evolved manifest metadata, else remains visible with unknown availability. A binding that does not resolve cannot be recorded as `loaded`.\n- `writeSet` contains concrete repository-relative path scopes, not globs,\n absolute paths, or `..` traversal.\n- a directory scope covers descendants; overlapping scopes cannot run in the\n same parallel batch.\n- every `dependsOn` target must exist; self edges and cycles are invalid.\n- `checkCommand` is exact-match evidence when present. `passCondition` explains\n what that command proves; it does not replace the command result.\n- workers do not widen their own `writeSet`. If scope must grow, return to the\n orchestrator and produce a revised plan before dispatch.\n\n## Workflow-plan consumer\n\nFor a session with an active mandatory workflow, write the stage array to the router-displayed `workflow-stages.json` path and invoke its `workflow-disposition.js start --session-id <id>` command. This binds the run to the active `workflowId` and bound workspace even after entering a linked worktree. The start/check output exposes bindings only for dependency-ready stages; future-stage paths stay undisclosed. Resolve an active binding with `workflow-disposition.js stage-binding --stage-id <id> --binding-id <id> --disposition loaded|not-needed|unavailable --evidence <evidence>`. A successful check with unresolved bindings records `binding-unresolved`; resolve them, then rerun the exact check before the stage can pass. This state is a reminder, not a tool or Stop lock. Every required stage needs an objective check, and an independent plan needs a read-only `fable-verifier` stage depending on all other stages. Stage results need observed worker identities and numeric exit codes; missing metadata is unresolved, not success.\n\nFor standalone use without an active session contract, write the router contract and stage array to files and run:\n\n```bash\nnode fable-mode/scripts/workflow-plan-consumer.js \\\n --plan-file <router-contract.json> \\\n --stages-file <stages.json> \\\n --root <workspace> \\\n --run-id <optional-stable-run-label> \\\n --session-id <optional-host-session-id>\n```\n\nThe consumer validates the graph, derives `planId`, creates the isolated run\nroot, and emits execution batches. It consumes only router-owned decisions:\nstrategy, parallelism, verifier requirement, workspace/memory hints, limits,\nand requested-model metadata. It does **not** choose a model or execute a\nstage.\n\nFor `fable-parallel`, every ready batch must have resolved dependencies and\npairwise non-overlapping write sets. An invalid graph or overlap rejects the\nplan before workers are spawned. For `fable-staged` and\n`fable-multi-agent-workspace`, the same dependency graph is serialized unless\na later validated plan explicitly allows parallelism.\n\n## Bounded ensemble evidence\n\n`workflowPlan.ensemble` is a modifier on a selected Fable topology, not a sixth\nexecution strategy. The kernel may add it only for high-uncertainty/high-stakes\nwork with comparable outputs (choice/ranking/verdict/recommendation) or when\npreserving disagreement is itself the deliverable. Creative generation,\nmechanical bulk work, explicit `no ensemble`, unavailable subagents, and blocked\nbase plans do not receive the modifier.\n\nCurrent contract:\n\n```json\n{\n \"maxCandidates\": 3,\n \"diversity\": [\"model\", \"prompt\", \"evidence\"],\n \"synthesis\": \"preserve-disagreement\",\n \"verifier\": \"independent\",\n \"reasonCodes\": [\"ensemble-high-uncertainty-comparable-output\"]\n}\n```\n\nEach candidate passed to `fable-mode/scripts/ensemble-review.js` records a\nbounded position, a model/prompt/evidence diversity fingerprint, objective\nverification status/evidence, and optional correctness/cost observations.\nCandidates with the same model+prompt+evidence fingerprint are not independent\nand are rejected. The verifier uses a distinct identity, declares\n`independent=true`, and supplies its own evidence.\n\nThe synthesizer keeps every position and its evidence. A strict majority is\nonly a proposal: any objective verification failure on the proposed position\nblocks selection, and verifier rejection/inconclusive/mismatch also blocks it.\nAn accepted position therefore never deletes minority positions or evidence\ngaps.\n\nRun it after the candidate artifacts/checks exist:\n\n```bash\nnode fable-mode/scripts/ensemble-review.js \\\n --plan-file <router-contract.json> \\\n --candidates-file <candidates.json> \\\n --verifier-file <verifier.json> \\\n --root <workspace> \\\n --run-id <runId> \\\n [--paired-evidence-file <paired-benchmark.json>]\n```\n\nThe correlated result is written to `ensemble.json` under the same run root.\nCorrectness is recorded separately from tool calls, tokens, elapsed time,\nretries, and failure mode so #71/#83 can consume the evidence without\nconflating quality with cost. Missing telemetry is allowed and never changes\nthe strategy/result. No ensemble or multi-agent improvement claim is permitted\nunless paired evidence establishes the same engine/model/fixture/rubric/config,\na sample count, effect estimate, and uncertainty.\n\n## Verification evidence\n\n`contract-test.js` watches Bash/PowerShell calls and resolves an exact\n`checkCommand` only when it can correlate the command to one run/stage. The\nresult updates the stage contract and writes:\n\n```text\nevidence/<stageId>.json\n```\n\nwith `planId`, `runId`, `stageId`, command, exit code, status, and captured\nevidence. If the same command is pending in multiple uncorrelated runs, the\nhook updates none of them and reports the ambiguity.\n\nBefore delivery, every check-bearing contract in the run must be `pass` (not\n`pending`, `planned`, `running`, or `fail`). A failed check returns to the\norchestrator for bounded re-planning.\n\n## Scope audit\n\n`subagent-scope-guard.js` snapshots active stage write sets at the beginning of\na worker burst and compares the post-burst Git diff paths with that immutable\nsnapshot. Expected paths are attributed to the matching stage; worker-level\nattribution is added when the host exposes a worker/subagent id and the stage\ncontract records it. Ambiguous or out-of-scope changes exit 2. With no declared\nstage contracts, the older conservative behavior remains: every newly changed\npath requires review.\n"
|
|
7
|
+
},
|
|
8
|
+
"fable-mode/SKILL.md": {
|
|
9
|
+
"source_sha256": "06de78fb5d054ce90a3ddf0ced7fcbdc647a3bf8b86bd2f7635fbdd2a5065648",
|
|
10
|
+
"content": "---\nname: fable-mode\ndescription: \"Stage large tasks through plans, named fable agents, failable checks, and skeptical review; fable on opus|sonnet|haiku selects a behavior profile, not a runtime model.\"\nlicense: Apache-2.0\nmetadata:\n author: Miya Daniel\n version: 0.31.0\n---\n\n# Fable Mode\n\n## Skill Contract\n\n| Component | Specification |\n| :--- | :--- |\n| **Trigger / Input** | Profile lookup or Tier 3 execution. |\n| **Expected Output** | Profile record or verified stages. |\n| **State Mutations** | Execution only: stage checklist and audit records. |\n| **Enforcement Gate** | Execution: `<this-skill-dir>/scripts/model-selector.js`, stage contracts, and `<skills-repo-root>/harness-everything/scripts/verify-gate.js`. |\n\n## USE FOR:\n- Large multi-file, multi-source, or multi-session work\n- Fable profile explanations or explicit Fable execution requests\n- Work needing named delegation and cold verification\n\n## DO NOT USE FOR:\n- One obvious single-pass edit\n- Ordinary Tier 2 implementation or bugfix work\n\n## Select Operation First\n\nFor explanation/profile-only lookup, read only `<this-skill-dir>/references/profile-lookup.md` and answer. No stage state, delegation, or selector source reads. Quoted aliases do not start Fable. Mixed execution retains its topology; execution follows the workflow below.\n\n## Workflow\n\n1. Discover the runtime and lock the authorized file scope before edits.\n2. Per stage, set artifact, check, and required/optional bindings; dependency-ready stages disclose them, passing once resolved. Gaps never lock tools or Stop; advisory replans never halt. Rule-of-3 covers same-signature failures.\n3. Resolve the requested behavior profile with `model-selector.js`; `opus`, `sonnet`/`sonnect`, and `haiku` are compatibility aliases, not model bindings.\n4. Delegate by profile: orchestrator coordinates, reasoning takes bounded judgment, mechanical takes low-ambiguity work; workers never spawn workers.\n5. The host runtime-model floor is advisory; record it, never block on model choice alone.\n6. Run stage checks, cold-review high-stakes artifacts, and audit profile/runtime/fallback fields.\n\nDeep dive: <this-skill-dir>/references/model-matrix.md\n"
|
|
11
|
+
},
|
|
12
|
+
"fable-mode/execution-guardrails/SKILL.md": {
|
|
13
|
+
"source_sha256": "f63fa60eeae82c736ec17cac0aabf5ea9e1b099e29ee4592d4bc31e8aae407ec",
|
|
14
|
+
"content": "---\nname: execution-guardrails\ndescription: \"Reference guardrails for verified warnings and safe replacements; active carriers must inject them explicitly.\"\nlicense: Apache-2.0\nmetadata:\n author: Miya Daniel\n version: 0.31.0\n---\n\n# Execution Guardrails\n\n**USE FOR**: Raising warnings or flagging problems, search-and-replace/regex file edits, batching minor caveats, reporting capability limits.\n\n| Component | Spec |\n| :--- | :--- |\n| **Trigger / Input** | Turn raising a warning or performing search-and-replace edits. |\n| **Expected Output** | Verified warnings; batched caveats without count-based stops; corruption-checked edits. |\n| **State Mutations** | None \u2014 governs execution quality for file-editing turns. |\n| **Enforcement Gate** | Verify before flagging; validate edits post-write. |\n\n## USE FOR:\n- Raising a warning or flagging a suspected problem\n- Search-and-replace or regex file edits\n- Batching multiple minor caveats during a run\n- Reporting capability limits (\"this may be beyond me\")\n\n## DO NOT USE FOR:\n- Task planning, staging, or fable-mode's staged loop itself\n- Git history rewriting (see `rewrite-commits`)\n- Skills that never raise warnings or edit files\n\n## Core Rules\n\n1. **Verify before flag** \u2014 Confirm a problem exists before reporting it. Never convert absence of evidence into a warning.\n2. **Warning batching.** Collect minor concerns and list them together in the next natural report or handoff; their count alone MUST NOT stop, pause, or return the worker/stage. An independently material, confirmed concern may stop the current stage and be surfaced immediately.\n3. **Find-and-replace safety** \u2014 Prefer structured edit tools over shell `sed`; anchor with unique context or `\\bword\\b` boundaries; verify file integrity post-edit. Never replace-all blindly.\n\nRepeated matching execution failures remain governed by Rule-of-3 / `zoom-out`; warning batching never substitutes for that recovery path.\n\nThis nested file is reference-only, not an always-on carrier; rules apply only when an active carrier injects them.\n\nDeep dive: <this-skill-dir>/references/guardrail-rules.md\n"
|
|
15
|
+
},
|
|
16
|
+
"fable-mode/references/execution-phases.md": {
|
|
17
|
+
"source_sha256": "cde9dc8c6d72812d9737bdbab8139678c1609add950a8d494f6ecaec91c9b279",
|
|
18
|
+
"content": "# Fable Mode \u2014 Execution Phases\n\n```mermaid\nflowchart TD\n Start[Qualifying Tier 3 task] --> Discover[Discover sources, runtime, and authorized scope]\n Discover --> Plan[Write stage map and pass conditions]\n Plan --> Select[Resolve requested model and write audit record]\n Select --> Delegate[Delegate to named agent or visible inline fallback]\n Delegate --> Contract[Write stage contract]\n Contract --> Verify[Run the named failable check]\n Verify --> Review[Cold-review high-stakes deliverables]\n Review --> Done[Record result or escalate blocker]\n```\n\n## Discovery and planning\n\nRead the relevant files and sources before producing an artifact. Lock the\nauthorized write scope in the handoff. Number the stages, assign one artifact\nand one pass condition to each. Replan counts are advisory and never a hard\nstop; three same-signature failures still require Rule-of-3 `zoom-out`.\n\n## Delegation\n\nUse `fable-orchestrator` for Opus macro work, `fable-worker-sonnet` for\nreasoning and synthesis, and `fable-worker-haiku` for mechanical work. Brief\neach worker with its exact output path, context, audit record, and check.\nWorkers do not spawn workers. If the named agent is unavailable, choose the\nexplicit inline fallback or stop and escalate; do not silently change models.\n\n## Verification and handoff\n\nBefore each spot-check, create `.claude/harness-everything/state/contracts/<stage>.json`\nusing `CONTRACT-FORMAT.md`, then run the exact command in that contract. A\nstage is not complete until the command passes and the audit record contains\nthe verifier result. Use `fable-verifier` with only the spec and artifact for\nhigh-stakes or unsupervised delivery. Review scope-guard output before merging.\n\nWhen macro scaffolding ends, transition to `tdd` for focused implementation.\n"
|
|
19
|
+
},
|
|
20
|
+
"fable-mode/references/model-matrix.md": {
|
|
21
|
+
"source_sha256": "ae38b9bccb371dda348fef0c97a5b0fa456df8263522f5f8b1de262b10cef3cd",
|
|
22
|
+
"content": "# Fable behavior profiles and runtime model floors\n\nFable profiles define **how work is performed and which role owns it**. They do not select or require a branded runtime model. The historical names `haiku`, `sonnet`/`sonnect`, and `opus` remain accepted aliases so existing prompts keep working.\n\n## Behavior Profile Matrix\n\n| Canonical profile | Legacy alias | Role | Typical work | Named agent |\n|---|---|---|---|---|\n| `mechanical` | `haiku` | Mechanical worker | Bulk edits, format conversion, boilerplate, structured extraction | `fable-worker-haiku` |\n| `reasoning` | `sonnet` / `sonnect` | Reasoning worker | Non-trivial implementation, research synthesis, analysis, bounded design | `fable-worker-sonnet` |\n| `orchestrator` | `opus` | Orchestrator | Stage decomposition, dependency management, cross-stage synthesis, architecture/final decisions | `fable-orchestrator` |\n| verifier | \u2014 | Cold reviewer | Spec-versus-artifact checks and independent re-verification | `fable-verifier` |\n\nThe machine-readable source is `fable-mode/behavior-profile-matrix.json`.\n\n## Runtime Model Floor Matrix\n\nRuntime floors are **host-specific recommendations**, not Fable identity and not an execution gate. A newer compatible model/effort may be used. If the active runtime is below or cannot be compared to the floor, Fable continues with the same behavior profile and records the status visibly.\n\n| Fable profile | Claude recommended floor | Codex recommended floor |\n|---|---|---|\n| `orchestrator` | Opus 5.5+ / medium+ | GPT-6 Sol+ / xhigh+ |\n| `reasoning` | Sonnet 5.5+ / medium+ | GPT-6 Sol+ / medium+ |\n| `mechanical` | Haiku 5.5+ | GPT-6 Luna 6+ / xhigh+ |\n\nThe machine-readable source is `fable-mode/runtime-model-floor-matrix.json`. These are Harness support baselines, not claims that every host currently exposes every listed model.\n\n## Resolution\n\n```text\ntask\n -> behavior profile\n -> named role / agent\n -> host adapter\n -> advisory runtime floor\n -> actual host-selected model\n -> execute the SAME behavior contract\n```\n\n`fable on haiku` normalizes to `mechanical`; `fable on sonnect` normalizes to `reasoning`; `fable on opus` normalizes to `orchestrator`. None of these aliases means switching the host to that branded model.\n\nThe audit record uses `requestedProfile`, `effectiveProfile`, `profileAlias`, `assignedRole`, `runtimeModel`, `runtimeEffort`, `recommendedRuntimeFloor`, `runtimeFloorStatus`, and `runtimeFloorReason`, plus stage verification fields.\n\nA `below-recommended` or `unknown` runtime-floor result is advisory and does not block Fable. Missing named-agent capability is separate: the existing `inline` or `stop` fallback policy decides whether the same profile runs inline or the stage is blocked.\n\nSee [execution phases](execution-phases.md) for the staged run contract.\n"
|
|
23
|
+
},
|
|
24
|
+
"fable-mode/references/profile-lookup.md": {
|
|
25
|
+
"source_sha256": "46063e011923178b90e5a63acf4841a3b36e1e0216e1b32abc7c72f9af03adfa",
|
|
26
|
+
"content": "# Profile lookup\n\nUse for explanation or a profile-only JSON record. No stage, audit-log mutation, delegation, or model switch is implied. Execution requests retain their selected workflow obligations.\n\n| Alias | Profile | Role | Named agent |\n|---|---|---|---|\n| haiku | mechanical | mechanical-worker | fable-worker-haiku |\n| sonnet / sonnect | reasoning | reasoning-worker | fable-worker-sonnet |\n| opus | orchestrator | orchestrator | fable-orchestrator |\n\nPreserve the input alias. Profiles describe behavior; the host chooses the actual runtime model. Report runtime fields as unknown when they were not supplied; never infer a model from an alias. Runtime floors are advisory, including unknown or below-recommended results.\n\nFor a lookup, these fields suffice: `requestedProfile`, `effectiveProfile`, `profileAlias`, `assignedRole`, `runtimeModel`, `runtimeEffort`. If the user requests a computed floor comparison, read the relevant entry in `runtime-model-floor-matrix.json` and the resolution rules in [runtime-floor rules](model-matrix.md); use `scripts/model-selector.js` for a stage audit, not by reading its implementation for a lookup.\n\nExample for `fable on sonnect` with no supplied runtime:\n\n```json\n{\"requestedProfile\":\"reasoning\",\"effectiveProfile\":\"reasoning\",\"profileAlias\":\"sonnect\",\"assignedRole\":\"reasoning-worker\",\"runtimeModel\":null,\"runtimeEffort\":null}\n```\n"
|
|
27
|
+
},
|
|
28
|
+
"harness-everything/references/triage-and-tiers.md": {
|
|
29
|
+
"source_sha256": "77ae1908d209d52c482d13a4fa2b28a7a4c0709cf4962ee41b3be0e1728bdcb4",
|
|
30
|
+
"content": "# Task Triage & Tier Details\n\nHarness uses tiers to estimate scope and select the smallest sufficient execution topology. A tier is **not** a fixed TODO/TDD/Fable pipeline. Once the router selects an applicable topology, however, that topology becomes the run's execution contract.\n\n> Architecture rule: **Contract-first lifecycle; flexible reasoning/implementation; semantic MUSTs may be reminder-observed, while only Rule-of-3 reflection and explicit permission boundaries may hard-block.**\n\n## 0. When Harness Routing Applies\n\n```mermaid\nflowchart TD\n U([User Prompt]) --> S{Software / project work?}\n S -- No --> B[Bypass Harness<br/>Answer naturally]\n S -- Yes --> K[Harness Kernel<br/>classify + select topology]\n K --> W{Workflow selected?}\n W -- No / deferred --> D[Preserve invariants<br/>choose smallest justified workflow]\n W -- Yes --> A[ACTIVE workflow contract]\n A --> P[Decompose requirements<br/>into ordered steps]\n P --> B[Bind skills/references<br/>to each step]\n B --> E[Execute active step only]\n E --> V{Required checks / stages resolved?}\n V -- Yes --> O([Claim completion])\n V -- No --> P[Diagnose / re-plan]\n P --> X{Workflow covers next step?}\n X -- Yes --> E\n X -- No --> C[Record evidence-backed escape<br/>for uncovered scope only]\n```\n\n### Bypass Rules\n\n- **Pure chat, translation, general web search, non-software writing:** bypass Harness routing.\n- **Software engineering / codebase / project work:** establish the Harness Kernel contract before mutation, even when the prompt already names a domain skill.\n\nHost skill selection is not proof that Harness routing already happened.\n\n## 1. Minimal Kernel Contract\n\nThe kernel protects lifecycle obligations without micromanaging model reasoning.\n\n### Mandatory invariants\n\n1. **Route before execution** \u2014 establish task scope/tier and the smallest justified topology.\n2. **Verify before claim** \u2014 completion requires objective evidence appropriate to the change.\n3. **Re-plan on repetition** \u2014 after three same-signature failures, stop micro-retrying and use a fresh diagnosis / `zoom-out`.\n4. **Bind knowledge by step** \u2014 compose ordered requirement steps and declare required/optional skills or references before execution.\n5. **Resolve selected workflow** \u2014 selected-topology required obligations are semantic MUSTs; implementation tactics MAY adapt where the contract allows.\n6. **Surface status** \u2014 non-trivial software/project work MUST use the single Harness Status format at required phase boundaries.\n\nThe model remains free to choose tools, implementation technique, decomposition details, and reasoning inside the workflow.\n\n### What is intentionally *not* mandatory\n\n- One universal `TODO \u2192 TDD \u2192 verification-loop` sequence for every Tier 2/3 task.\n- Multi-agent execution merely because a task is Tier 3.\n- Every keyword-matched skill/reference regardless of whether an active requirement needs it.\n- Every optional deep-dive/reference linked from a skill.\n\nKeyword/domain matches are normalized planning signals, not document selectors. Each ordered step declares its required and optional skill/reference bindings; only the active step or dependency-ready Fable stage exposes those paths. Read a required skill's complete entry and follow its applicable core contract before the step passes. Required bindings without a known path remain visible and unresolved. Optional bindings resolve as loaded or not-needed. Hooks may remind, but missing bindings do not hard-block ordinary tools or Stop.\n\n## 2. Routing Mechanism\n\nUse the public kernel entry point:\n\n```bash\nnode \"<this-skill-dir>/scripts/kernel-router.js\" \"<brief prompt summary>\"\n# or\nnpx github:dyphn1/Harness-everything next \"<brief prompt summary>\"\n```\n\n`kernel-router.js` delegates classification/knowledge-signal detection to `tier-router.js` and emits:\n\n- classified tier + rationale,\n- structured workflow plan,\n- compact **Harness Routing Checkpoint**,\n- required invariants,\n- normalized knowledge signals that help compose requirement steps, not paths to read,\n- ordered requirement steps and their active required/optional bindings after planning,\n- **Workflow Contract** for the selected topology.\n\nIf `UserPromptSubmit` already ran the kernel this turn, reuse that output. Do not infer a silent Tier 1/direct path from missing or degraded routing.\n\n### Active binding contract\n\n- Before a selected Tier 2/3 workflow starts, decompose the request into ordered requirement steps and confirm the smallest sufficient topology.\n- Put a required skill/reference only on steps whose acceptance criteria need it; put optional knowledge on its actual consumer step.\n- Load/resolve only bindings for the active step. Do not print or preload future-step bindings.\n- A required binding MUST resolve as loaded before the step passes; optional bindings resolve as loaded or not-needed. Unknown/unavailable required bindings stay visible and unresolved.\n- Fable uses its existing dependency/write-set graph as the step graph. Only dependency-ready stage bindings are disclosed; unresolved bindings keep a successful check at `binding-unresolved` until they are resolved and that exact check is rerun.\n\nThe binding disposition is an execution contract, not proof that a real host loaded a file. Preserve a host/session trace before making live-host claims. Missing evidence stays a reminder and does not create a persistent Harness lock.\n\n### Workflow escape contract\n\nEscape is audit metadata for a bounded contract exception. Use it only when the selected topology genuinely cannot represent part of the task. The permitted reason codes are:\n\n- `workflow-uncovered-scope` \u2014 the selected topology cannot represent a bounded part of the task;\n- `host-capability-unavailable` \u2014 the host lacks a capability required to execute the selected topology.\n\nEvery escape must name a declared stage in the correlated run and record the reason code, uncovered scope, and evidence explaining the coverage/capability gap. Host capability loss and missing isolation **MUST** remain visible as warnings/evidence, without creating a persistent execution lock.\n\n## 3. Tier Routing\n\n### Tier 1 \u2014 Trivial\n\nTypical shape: typo/small docs correction, narrow local edit, bounded explanation, straightforward Git operation.\n\nDefault topology is normally `direct-single`. Keep the lifecycle minimal, but still satisfy applicable scope/safety/verification obligations.\n\n### Tier 2 \u2014 Standard\n\nTypical shape: specific feature/bug fix, multi-file coordination, behavioral change, focused benchmark/review.\n\nThe router normally selects `iterative-single`: bounded reason/act work plus objective verification. Compose ordered steps and bind TDD to behavior-change, verification-loop to verification, and domain references only to steps whose acceptance needs them. Keyword matches alone never select or load documents.\n\nWhen an evaluated skill is applicable, follow its workflow rather than reading it and then discarding it because the implementation seems obvious.\n\n### Tier 3 \u2014 Macro\n\nTypical shape: repository-wide/architectural work, large ambiguous requirements, cross-source synthesis, or bounded delegation.\n\nThe router may select `fable-staged`, `fable-parallel`, or `fable-multi-agent-workspace`. A selected Fable topology is a semantic execution contract: its required stages, dependencies/write sets, checks, synthesis barrier, and verifier **MUST** resolve. Missing lifecycle evidence may produce reminders rather than a persistent workflow lock; fail-open mechanics do not make the contract optional.\n\nTier 3 does **not** automatically mean multi-agent; the router still chooses the smallest sufficient topology.\n\n## 4. Cognitive OS Relationship\n\n```mermaid\nflowchart LR\n D[Discover] --> T[Think]\n T --> Y[Try]\n Y --> S[Summarize]\n S --> R[Record]\n S -- evidence insufficient --> T\n Y -- same failure x3 --> Z[Zoom Out]\n Z --> T\n```\n\nThis describes a reasoning policy. The workflow contract governs lifecycle obligations; domain skills may define their own local phases inside it.\n\n## 5. Host Integration and Enforcement Strength\n\n| Host mode | Expected behavior |\n|---|---|\n| Lifecycle hooks available | Kernel records the selected semantic workflow contract. PreToolUse/Stop hooks observe/remind; Rule-of-3 and permission gates are the intentional blocking exceptions. |\n| Instruction-only / advisory delivery | Agent receives the same MUST/SHOULD/MAY semantic contract without lifecycle observations or hard-enforcement claims. |\n| Manual use | Invoke `harness-everything` / `harness next`, then execute the selected topology explicitly. |\n\nOn Claude and the local OpenAI package, supported hooks observe workflow/worktree/verification state and emit reminders. Rule-of-3 reflection and explicit action/permission gates remain separate blocking boundaries.\n\nNever turn mechanism/package evidence into a cross-host live-enforcement claim. #82 owns host-specific retained evidence.\n\n## 6. User-visible status contract\n\nFor non-trivial software/project work, user-facing progress **MUST** use one stable Markdown shape across direct, iterative, Fable, and multi-agent execution:\n\n```md\n### \ud83d\udea6 Harness Status\n\n- **Current:** <what is being done now>\n- **Read / Evidence:**\n - <important file/source/evidence read or confirmed>\n - <another item when multiple evidence items improve scanability>\n- **Next:** <next intended action>\n- **Risk / Blocked:** <only when materially applicable; omit otherwise>\n```\n\nWhen there is only one short evidence item, keep it inline on the **Read / Evidence** bullet. Use nested bullets when multiple items would otherwise become one dense sentence.\n\nEmit it before substantive execution, after a major phase completes, when the plan/assumption/direction materially changes, at meaningful phase boundaries during long-running work, and before final completion. The final response may merge the last status naturally.\n\nThis is a semantic communication MUST. It does **not** create a runtime lock, counter, reset requirement, or permission boundary. The routing checkpoint below is source state for this format, not a second competing progress template.\n\n## 7. Routing Checkpoint\n\nFor software/project work, consume this internal runtime state when producing the first Harness Status; do not render the checkpoint as a separate user-facing block:\n\n```markdown\n## \ud83d\udea6 Harness Routing Checkpoint\n- Tier: Tier X \u2014 <reason>\n- Strategy: <selected/deferred strategy>\n- Workflow state: <pending | running | satisfied | deferred | blocked>\n- Required invariants: <router invariants>\n- Knowledge signals: <normalized planning inputs; not document paths>\n- Active step: <ordered requirement id/type, with only its declared bindings>\n- Escape: <none | reason + uncovered scope + evidence>\n```\n\nThe checkpoint is runtime/source state, not a competing user-facing template. Render user progress through the single Harness Status contract above. The key distinction is: **active required bindings and selected-topology obligations are MUSTs; unrelated future knowledge stays undisclosed, while tactics MAY adapt.**\n\n## 8. Self-Healing and Dynamic Skills\n\nSelf-heal and fact-audit behavior remain part of the runtime. Prompt keyword matches emit normalized knowledge signals plus candidate step-binding ids; they do not print skill/reference paths. Self-evolved skills registered in a manifest's `generated` entries are matched on metadata (triggers) only: matching ids surface as `SELF-EVOLVED SKILL SIGNALS`, and a step that declares such an id resolves its `SKILL.md` from the manifest only once that step is active.\n\nIf bootstrap reports missing integration touchpoints:\n\n```bash\nnode \"<this-skill-dir>/scripts/self-heal.js\"\nnode \"<this-skill-dir>/scripts/self-heal.js\" --check\n```\n\nRespect an explicit user choice to remove/disable an integration.\n\nRelated contracts: [router workflow plan](router-workflow-plan.md) and [skill registry](skill-registry.md).\n"
|
|
31
|
+
},
|
|
32
|
+
"tdd/SKILL.md": {
|
|
33
|
+
"source_sha256": "138aad44c5b2db5270dd9ef6cc908673581aca1ca2195819abe6507780396b2d",
|
|
34
|
+
"content": "---\nname: tdd\ndescription: \"Drive Standard Tasks (Tier 2) with Test-Driven Development (RED/GREEN/REFACTOR) backed by terminal test evidence. USE FOR: \\\"implement a feature test-first\\\", \\\"fix a bug with a regression test\\\", \\\"refactor safely under tests\\\". DO NOT USE FOR: \\\"macro planning or scaffolding\\\", \\\"docs-only work with no testable logic\\\".\"\nlicense: Apache-2.0\nmetadata:\n author: Miya Daniel\n version: 0.13.0\n---\n\n# Test-Driven Development\n\n**ROUTER:** Select unit or integration evidence before RED/GREEN/REFACTOR.\n\n## Skill Contract\n\n| Component | Specification |\n| :--- | :--- |\n| **Trigger / Input** | Tier 2 feature, bugfix, or behavior-preserving refactor. |\n| **Expected Output** | RED/GREEN logs and a profile-tagged quality report. |\n| **State Mutations** | Tests, implementation, TODO state, and optional report JSON. |\n| **Enforcement Gate** | Project tests plus `npm run tdd:quality -- <evidence.json>`; exit 1 blocks completion. |\n\n## USE FOR:\n- implementing behavior test-first\n- bug regression tests\n- safe refactors under tests\n\n## DO NOT USE FOR:\n- macro planning or scaffolding\n- docs-only or non-testable work\n\n## Route the Behavior\n\nLoad `<this-skill-dir>/references/core-discipline.md` and the quality model, then only the unit or integration guide needed. Keep their applicability rules separate.\n\nDeep dive: <this-skill-dir>/references/core-discipline.md\n\nAfter three failed GREEN attempts, invoke `zoom-out` before another edit.\n"
|
|
35
|
+
},
|
|
36
|
+
"verification-loop/SKILL.md": {
|
|
37
|
+
"source_sha256": "a0454906b6863b0732db613347151dd7c0eb30bad3e75797150c07e9c13ba6c9",
|
|
38
|
+
"content": "---\nname: verification-loop\ndescription: \"Run objective verification gates (build, types, lint, tests, security scan) before claiming work done. USE FOR: \\\"run the full verification loop\\\", \\\"verify this is done before delivery\\\". DO NOT USE FOR: \\\"design discussions with no completed work\\\", \\\"planning without code changes\\\".\"\nlicense: Apache-2.0\nmetadata:\n author: Miya Daniel\n version: 0.26.0\n---\n\n# Verification Loop\n\nRun objective gates before claiming delivery readiness.\n\n## Skill Contract\n\n| Component | Specification |\n| :--- | :--- |\n| **Trigger / Input** | Completed change awaiting delivery. |\n| **Expected Output** | Evidence-backed verification report. |\n| **State Mutations** | Fills `<this-skill-dir>/templates/verification-report.template.md`. |\n| **Enforcement Gate** | Every applicable gate passes. |\n\n## USE FOR:\n- Verify a completed change\n- Run quality gates before delivery/PR\n\n## DO NOT USE FOR:\n- Fixing failures without re-running verification\n- New feature/test implementation (use `tdd`)\n\n## Workflow\n\n1. Run applicable build, type, lint, test/coverage, security, and diff gates.\n2. Reconcile cited REQ/ADR/spec lineage with `<this-skill-dir>/scripts/contract-integrity-audit.js`; stale/failed/audit-only evidence is not ready.\n3. For project-declared/discovered mechanical gates, follow `<this-skill-dir>/references/project-verification-contract.md`.\n4. On failure, fix and rerun from the first affected gate.\n5. Fill `<this-skill-dir>/templates/verification-report.template.md` with actual evidence.\n\nDeep dive: `<this-skill-dir>/references/verification-phases.md`\n"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Execute every upstream Polyglot assertion without a package-install/model
|
|
4
|
+
// cost. This explicit adaptation supports exactly the matchers in the pinned
|
|
5
|
+
// simple-linked-list spec, failing closed on anything else.
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const vm = require('vm');
|
|
8
|
+
const assert = require('node:assert/strict');
|
|
9
|
+
const { pathToFileURL } = require('url');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const equalValues = (actual, expected) => assert.deepEqual(structuredClone(actual), structuredClone(expected));
|
|
12
|
+
|
|
13
|
+
async function grade(target) {
|
|
14
|
+
const api = await import(pathToFileURL(path.resolve(target)).href);
|
|
15
|
+
const spec = fs.readFileSync(path.join(__dirname, 'fixtures/polyglot/simple-linked-list.spec.js'), 'utf8');
|
|
16
|
+
assert.equal((spec.match(/\b(?:x)?test\(/g) || []).length, 23, 'pinned test inventory');
|
|
17
|
+
const body = spec.replace(/^import .*;\s*/m, '').replace(/\bxtest\(/g, 'test(');
|
|
18
|
+
let before = () => {};
|
|
19
|
+
const results = [];
|
|
20
|
+
const sandbox = {
|
|
21
|
+
...api,
|
|
22
|
+
describe: (_name, fn) => { const previous = before; before = () => {}; fn(); before = previous; },
|
|
23
|
+
beforeEach: fn => { before = fn; },
|
|
24
|
+
test: (name, fn) => {
|
|
25
|
+
try { before(); fn(); results.push({name, pass: true}); }
|
|
26
|
+
catch (e) { results.push({name, pass: false, error: e.message}); }
|
|
27
|
+
},
|
|
28
|
+
expect: actual => ({
|
|
29
|
+
toEqual: expected => equalValues(actual, expected),
|
|
30
|
+
toBeDefined: () => assert.notEqual(actual, undefined),
|
|
31
|
+
not: {toThrow: () => assert.doesNotThrow(actual)},
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
vm.runInNewContext(body, sandbox, {timeout: 10000});
|
|
35
|
+
assert.equal(results.length, 23, 'all upstream cases executed');
|
|
36
|
+
console.log(JSON.stringify({pass: results.every(r => r.pass), tests: results.length, results}));
|
|
37
|
+
return results.every(r => r.pass);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (require.main === module) grade(process.argv[2]).then(pass => { process.exitCode = pass ? 0 : 1; }).catch(e => {
|
|
41
|
+
console.log(JSON.stringify({pass: false, graderError: e.message})); process.exitCode = 2;
|
|
42
|
+
});
|
|
43
|
+
module.exports = {grade, equalValues};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const vm = require('node:vm');
|
|
3
|
+
const { equalValues } = require('./grader.cjs');
|
|
4
|
+
|
|
5
|
+
for (const [actual, expected] of [
|
|
6
|
+
[[undefined], [null]], [[NaN], [null]], [[Infinity], [null]],
|
|
7
|
+
[{value: undefined}, {}], [Array(1), [null]],
|
|
8
|
+
]) assert.throws(() => equalValues(actual, expected), assert.AssertionError);
|
|
9
|
+
equalValues(vm.runInNewContext('[1, {x: undefined}, NaN]'), [1, {x: undefined}, NaN]);
|
|
10
|
+
console.log('grader equality PASS: five JSON-loss negative controls and cross-realm positive control');
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const {spawnSync} = require('child_process');
|
|
7
|
+
const root = path.resolve(__dirname, '../..');
|
|
8
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'issue290-hook-test-'));
|
|
9
|
+
const docs = ['fable-mode/SKILL.md', 'fable-mode/references/profile-lookup.md'];
|
|
10
|
+
const payload = {hook_event_name: 'UserPromptSubmit', session_id: 'issue290-mechanism', cwd: dir,
|
|
11
|
+
prompt: "Explain what 'fable on sonnect' means; return only a small JSON selection record."};
|
|
12
|
+
for (const arm of ['A', 'B']) {
|
|
13
|
+
fs.writeFileSync(path.join(dir, '.pilot.json'), JSON.stringify({source: root, arm, documents: docs,
|
|
14
|
+
trace: path.join(dir, arm+'.jsonl'), routerTrace: path.join(dir, arm+'.router')}));
|
|
15
|
+
const r = spawnSync(process.execPath, [path.join(__dirname, 'host-hook.cjs')], {
|
|
16
|
+
cwd: dir, input: JSON.stringify(payload), encoding: 'utf8',
|
|
17
|
+
env: {...process.env, HARNESS_STATE_HOME: path.join(dir, '.state-'+arm)},
|
|
18
|
+
});
|
|
19
|
+
assert.equal(r.status, 0, r.stderr);
|
|
20
|
+
const out = JSON.parse(r.stdout).hookSpecificOutput;
|
|
21
|
+
assert.equal(out.hookEventName, 'UserPromptSubmit');
|
|
22
|
+
assert.match(out.additionalContext, /direct-single/);
|
|
23
|
+
assert.match(fs.readFileSync(path.join(dir, arm+'.router'), 'utf8'), /profile-lookup/);
|
|
24
|
+
if (arm === 'A') assert.match(out.additionalContext, /read every listed reference now/);
|
|
25
|
+
else assert.doesNotMatch(out.additionalContext, /read every listed reference now/);
|
|
26
|
+
}
|
|
27
|
+
console.log('PASS canonical host-hook attribution and isolated A/B timing');
|