Build context-priority local CLI and brutalist launch demo
This commit is contained in:
1 parent
70b7a8bbcd
commit
090400a95e
26 files changed
+759
No files matched your search
@@ -0,0 +1,47 @@
|
||||
# Context Priority
|
||||
|
||||
Local, deterministic transcript selection. Keep critical exchanges verbatim instead of letting recent tangents take the whole continuation budget. No model calls, uploads, live compaction or runtime dependencies.
|
||||
|
||||
The board references [claude-code #83489](https://github.com/anthropics/claude-code/issues/83489), which requests named model/effort presets. This tool implements the board's **separate context-priority brief**, not that upstream feature.
|
||||
|
||||
## Install and use
|
||||
|
||||
Download `site/context-priority.zip`, unzip, and run from the extracted directory. Requires Node 20+. No npm install. macOS, Linux and Colima use the same commands:
|
||||
|
||||
```sh
|
||||
node cli.cjs annotate sample.jsonl priorities.json e001=pinned e003=aside
|
||||
node cli.cjs export sample.jsonl priorities.json 1000 ./out
|
||||
node --test tests/*.test.cjs
|
||||
```
|
||||
|
||||
`annotate` prints the exchange count; IDs are e001, e002… in source order. Unannotated exchanges default to normal. To change priorities, rerun annotate with the complete desired mapping. Existing sidecars and outputs require a terminal and explicit `OVERWRITE`; noninteractive overwrite is refused. No force flag. The source is never opened for writing. Outputs use private file permissions. Back up sensitive source files yourself.
|
||||
|
||||
`out/continuation.md` contains complete exchanges in chronological order inside JSONL code blocks. Pinned JSONL payloads retain their original whitespace and line endings, without paraphrasing. `out/omissions.json` lists every exchange, priority, framed byte cost, selected/omitted status, reason and source SHA-256. Annotations are separate JSON with `version:1`, `sourceSha256`, `priorities`. A changed source invalidates its sidecar.
|
||||
|
||||
## Input contract
|
||||
|
||||
UTF-8 JSONL, one ordered message per line, no blank lines. Supported records:
|
||||
|
||||
- `{ "role":"user", "content":"prompt" }`, assistant records, and `{ "role":"tool", "tool_call_id":"id", "content":"result" }`.
|
||||
- Assistant `tool_calls` IDs paired with tool `tool_call_id`.
|
||||
- Message-wrapped `{ "type":"user", "message":{ "role":"user", "content": [...] } }` / assistant records; content blocks may include `tool_use` with `id` and `tool_result` with `tool_use_id`.
|
||||
|
||||
An exchange starts at a user prompt and ends with an assistant response before the next user prompt. Result-only user records belong to the ongoing exchange. All tool calls must have matching results in the same exchange. Orphaned results, outstanding calls, a final user prompt, missing final assistant response, mixed result/text user blocks, unsupported roles and metadata records fail closed. This is a strict subset of exported JSONL, **not a universal Claude export importer**. Normalize linear message history first; do not feed a branching raw session log.
|
||||
|
||||
## Selection and limits
|
||||
|
||||
`site/engine.js` is the exact same module used by CLI and browser. All pinned exchanges are reserved first. A budget below the pinned content plus Markdown framing is rejected. Then normal exchanges are considered oldest-first, then asides oldest-first, skipping whole exchanges that do not fit. Output restores source chronology. The UTF-8 byte budget includes the full Markdown packet, **not the manifest**, and is not a token count. No exchange splitting, summarization or semantic dependency detection. Separate exchanges may reference omitted material: inspect before continuing. Fences grow to safely contain literal backticks.
|
||||
|
||||
Export stages both artifacts before publishing. A process or filesystem failure during the two renames can leave one new output and one old output; this is not a transactional two-file replace. Remaining staged artifacts stay in the reported `.context-priority-*` directory for recovery. Output aliases of the source/sidecar, symlink files and hard-linked output files are refused. Concurrent writers to the same output directory are unsupported; use a fresh directory per run. No secrets are redacted: exported content is as sensitive as the source.
|
||||
|
||||
## Browser demo / launch
|
||||
|
||||
Open `site/index.html` locally. Only the bundled synthetic transcript is demonstrated: no upload control, no network calls from the demo, no external scripts/fonts/trackers. Change priorities or byte budget; download the packet and omission manifest. The free CLI zip remains available without signup. Signup is a plain POST to the existing `/api/signup` service and therefore needs the published route; it does not work from `file://`. Successful `?joined=1#join` displays thanks. Paid tiers are unbuilt founding-price ideas with no checkout.
|
||||
|
||||
Generate the download after source changes:
|
||||
|
||||
```sh
|
||||
python3 build.py
|
||||
```
|
||||
|
||||
The build includes only CLI, engine, sample, README and core/CLI tests. Screenshots and test receipts are in `evidence/`. There is no Fly, Boring, model preset or live session integration. Publishing is handled by the existing runner after the issue reply.
|
||||
@@ -0,0 +1,12 @@
|
||||
"""Build a deterministic, dependency-free download; no shared files modified."""
|
||||
from pathlib import Path
|
||||
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
|
||||
root = Path(__file__).resolve().parent
|
||||
files = ['README.md', 'cli.cjs', 'sample.jsonl', 'site/engine.js', 'tests/core.test.cjs', 'tests/cli.test.cjs']
|
||||
with ZipFile(root / 'site/context-priority.zip', 'w') as archive:
|
||||
for name in sorted(files):
|
||||
entry = ZipInfo('context-priority/' + name, date_time=(2026, 10, 10, 0, 0, 0))
|
||||
entry.compress_type = ZIP_DEFLATED
|
||||
entry.external_attr = 0o100644 << 16
|
||||
archive.writestr(entry, (root / name).read_bytes())
|
||||
print('Built site/context-priority.zip')
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const readline = require('node:readline/promises');
|
||||
const engine = require('./site/engine.js');
|
||||
const usage = `Usage (Node 20+):
|
||||
node cli.cjs annotate SOURCE.jsonl SIDECAR.json [e001=pinned e002=aside ...]
|
||||
node cli.cjs export SOURCE.jsonl SIDECAR.json BUDGET_BYTES NEW_OUTPUT_DIR
|
||||
Outputs: continuation.md + omissions.json. Existing files require interactive confirmation.
|
||||
Budget includes all Markdown framing; source transcripts are never written.`;
|
||||
function identity(file) { return path.resolve(file); }
|
||||
async function confirm(files) {
|
||||
if (!files.length) return;
|
||||
if (!process.stdin.isTTY) throw Error('Output exists; rerun in a terminal to confirm overwrite');
|
||||
const rl = readline.createInterface({input:process.stdin,output:process.stdout});
|
||||
try { if (await rl.question('Overwrite ' + files.join(', ') + '? Type OVERWRITE: ') !== 'OVERWRITE') throw Error('Cancelled'); }
|
||||
finally { rl.close(); }
|
||||
}
|
||||
function guard(file, protectedFiles) {
|
||||
if (protectedFiles.includes(identity(file))) throw Error('Output would overwrite an input');
|
||||
if (fs.existsSync(file)) {
|
||||
const stat = fs.lstatSync(file);
|
||||
if (!stat.isFile() || stat.nlink > 1) throw Error('Output must be a regular file with no hard links');
|
||||
if (protectedFiles.some(p => fs.existsSync(p) && fs.realpathSync(p) === fs.realpathSync(file))) throw Error('Output aliases an input');
|
||||
}
|
||||
}
|
||||
async function main() {
|
||||
const [command, sourcePath, sidecarPath, ...rest] = process.argv.slice(2);
|
||||
if (!['annotate','export'].includes(command) || !sourcePath || !sidecarPath) throw Error(usage);
|
||||
const buffer = fs.readFileSync(sourcePath);
|
||||
if (buffer.subarray(0,3).equals(Buffer.from([239,187,191]))) throw Error('UTF-8 BOM is unsupported; normalize the export first');
|
||||
const source = new TextDecoder('utf-8', {fatal:true}).decode(buffer);
|
||||
const exchanges = engine.parse(source);
|
||||
const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
|
||||
if (command === 'annotate') {
|
||||
guard(sidecarPath, [identity(sourcePath)]);
|
||||
const priorities = Object.fromEntries(rest.map(value => {
|
||||
const parts = value.split('='); if (parts.length !== 2) throw Error('Use e001=pinned'); return parts;
|
||||
}));
|
||||
engine.select(exchanges, priorities, Number.MAX_SAFE_INTEGER);
|
||||
const existed = fs.existsSync(sidecarPath);
|
||||
await confirm(existed?[sidecarPath]:[]);
|
||||
fs.writeFileSync(sidecarPath, JSON.stringify({version:1,sourceSha256:sha256,priorities},null,2)+'\n', {flag:existed?'w':'wx',mode:0o600});
|
||||
console.log('Sidecar saved; '+exchanges.length+' complete exchanges. Source unchanged.');
|
||||
} else {
|
||||
if (rest.length !== 2 || !/^\d+$/.test(rest[0])) throw Error(usage);
|
||||
const [budget, directory] = rest;
|
||||
const sidecar = JSON.parse(fs.readFileSync(sidecarPath,'utf8'));
|
||||
if (sidecar.version !== 1 || sidecar.sourceSha256 !== sha256) throw Error('Sidecar does not match this transcript');
|
||||
const result = engine.select(exchanges, sidecar.priorities, Number(budget));
|
||||
const outputs = ['continuation.md','omissions.json'].map(name => path.join(directory,name));
|
||||
for (const file of outputs) guard(file, [identity(sourcePath),identity(sidecarPath)]);
|
||||
const existing = new Set(outputs.filter(f => fs.existsSync(f)));
|
||||
await confirm([...existing]);
|
||||
// Build both artifacts in a private staging directory first. Leave it on publication failure.
|
||||
fs.mkdirSync(directory,{recursive:true,mode:0o700});
|
||||
const stage = fs.mkdtempSync(path.join(directory,'.context-priority-'));
|
||||
fs.writeFileSync(path.join(stage,'continuation.md'),result.packet,{mode:0o600});
|
||||
fs.writeFileSync(path.join(stage,'omissions.json'),JSON.stringify({...result.manifest,sourceSha256:sha256},null,2)+'\n',{mode:0o600});
|
||||
try {
|
||||
for (const file of outputs) {
|
||||
guard(file,[identity(sourcePath),identity(sidecarPath)]);
|
||||
const staged = path.join(stage,path.basename(file));
|
||||
if (existing.has(file)) fs.renameSync(staged,file);
|
||||
else { fs.linkSync(staged,file); fs.unlinkSync(staged); }
|
||||
}
|
||||
fs.rmdirSync(stage);
|
||||
} catch (error) { throw Error(error.message+'; recover remaining artifacts from '+stage); }
|
||||
console.log('Exported '+result.manifest.packetBytes+'/'+budget+' bytes to '+directory);
|
||||
}
|
||||
}
|
||||
main().catch(error => { console.error(error.message); process.exitCode = 1; });
|
||||
@@ -0,0 +1,13 @@
|
||||
# Verification
|
||||
|
||||
- `macos-tests.txt`: native hb-5 Node v24.21.0 core/CLI tests.
|
||||
- `bundle-tests.txt`: tests executed from the extracted free zip on macOS.
|
||||
- `colima-tests.txt`: Node 20 Alpine CLI/core tests and synthetic export inside Colima.
|
||||
- `browser.json`, `desktop.png`, `phone.png`: file:// Chromium checks in Colima; both screenshots visually inspected before landing.
|
||||
- `desktop-packet.md`, `phone-packet.md` and manifests: exact browser download evidence.
|
||||
- `signup-tests.txt`: real existing signup handler imported into an isolated temporary filesystem; form POST, record, 303 redirect, invalid email, honeypot, unpublished venture rejection. No production signup written.
|
||||
- `macos-browser.txt`: native browser limitation, recorded separately.
|
||||
- `interactive-confirmation.txt`: native synthetic PTY overwrite confirmation.
|
||||
|
||||
No public route request was made: the factory publishes after the issue reply.
|
||||
All changes are confined to this venture. Browser test requires external development-only Playwright and `CHROME_PATH` if not using hb-5 Chrome; neither is a product dependency. Engine and CLI tests require only Node.
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"platform": "linux",
|
||||
"browser": "149.0.7827.53",
|
||||
"viewports": [
|
||||
"1280x900",
|
||||
"390x844"
|
||||
],
|
||||
"route": "file:// site/index.html (publication happens after reply)",
|
||||
"checks": [
|
||||
"no horizontal overflow",
|
||||
"shared engine parity",
|
||||
"paired tool exchange",
|
||||
"priority changes",
|
||||
"pin budget refusal",
|
||||
"packet + manifest + free bundle downloads",
|
||||
"plain signup form",
|
||||
"thank-you redirect state",
|
||||
"no external requests",
|
||||
"no JS errors"
|
||||
],
|
||||
"htmlSha256": "467ee72b09228237a4250ab821e6fb0d3403ee025eb48c3c7a22d187f72370b6"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
✔ CLI export, hash guard, noninteractive overwrite refusal, source aliases and recovery-safe preflight (402.120875ms)
|
||||
✔ parse four complete exchanges and preserve the exact source (0.535042ms)
|
||||
✔ pins survive verbatim; whole tool exchange stays paired; chronological output (0.752875ms)
|
||||
✔ pinned minimum is exact, and too-small budget refuses (0.314833ms)
|
||||
✔ normal beats an earlier aside; oversized normal can be skipped whole (0.173834ms)
|
||||
✔ bad priorities, budgets, incomplete exchanges and tool mismatches fail closed (0.348042ms)
|
||||
✔ message-wrapped tool blocks belong to the same exchange (0.20375ms)
|
||||
✔ fences grow around hostile Markdown and Unicode byte counts are exact (0.145084ms)
|
||||
✔ selection is deterministic and leaves inputs intact (0.139583ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 455.642458
|
||||
@@ -0,0 +1,95 @@
|
||||
v20.20.2
|
||||
TAP version 13
|
||||
# Subtest: CLI export, hash guard, noninteractive overwrite refusal, source aliases and recovery-safe preflight
|
||||
ok 1 - CLI export, hash guard, noninteractive overwrite refusal, source aliases and recovery-safe preflight
|
||||
---
|
||||
duration_ms: 240.601139
|
||||
...
|
||||
# Subtest: parse four complete exchanges and preserve the exact source
|
||||
ok 2 - parse four complete exchanges and preserve the exact source
|
||||
---
|
||||
duration_ms: 0.853188
|
||||
...
|
||||
# Subtest: pins survive verbatim; whole tool exchange stays paired; chronological output
|
||||
ok 3 - pins survive verbatim; whole tool exchange stays paired; chronological output
|
||||
---
|
||||
duration_ms: 0.850521
|
||||
...
|
||||
# Subtest: pinned minimum is exact, and too-small budget refuses
|
||||
ok 4 - pinned minimum is exact, and too-small budget refuses
|
||||
---
|
||||
duration_ms: 0.363449
|
||||
...
|
||||
# Subtest: normal beats an earlier aside; oversized normal can be skipped whole
|
||||
ok 5 - normal beats an earlier aside; oversized normal can be skipped whole
|
||||
---
|
||||
duration_ms: 0.13583
|
||||
...
|
||||
# Subtest: bad priorities, budgets, incomplete exchanges and tool mismatches fail closed
|
||||
ok 6 - bad priorities, budgets, incomplete exchanges and tool mismatches fail closed
|
||||
---
|
||||
duration_ms: 0.48778
|
||||
...
|
||||
# Subtest: message-wrapped tool blocks belong to the same exchange
|
||||
ok 7 - message-wrapped tool blocks belong to the same exchange
|
||||
---
|
||||
duration_ms: 0.763273
|
||||
...
|
||||
# Subtest: fences grow around hostile Markdown and Unicode byte counts are exact
|
||||
ok 8 - fences grow around hostile Markdown and Unicode byte counts are exact
|
||||
---
|
||||
duration_ms: 0.20612
|
||||
...
|
||||
# Subtest: selection is deterministic and leaves inputs intact
|
||||
ok 9 - selection is deterministic and leaves inputs intact
|
||||
---
|
||||
duration_ms: 0.338159
|
||||
...
|
||||
1..9
|
||||
# tests 9
|
||||
# suites 0
|
||||
# pass 9
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 317.386364
|
||||
Sidecar saved; 4 complete exchanges. Source unchanged.
|
||||
Exported 872/1000 bytes to /tmp/packet
|
||||
{
|
||||
"version": 1,
|
||||
"budgetBytes": 1000,
|
||||
"packetBytes": 872,
|
||||
"ordering": "pinned first; normal next; aside last; oldest fitting first within tier; output chronological",
|
||||
"exchanges": [
|
||||
{
|
||||
"id": "e001",
|
||||
"priority": "pinned",
|
||||
"bytes": 191,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
},
|
||||
{
|
||||
"id": "e002",
|
||||
"priority": "normal",
|
||||
"bytes": 391,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
},
|
||||
{
|
||||
"id": "e003",
|
||||
"priority": "aside",
|
||||
"bytes": 180,
|
||||
"selected": false,
|
||||
"reason": "budget"
|
||||
},
|
||||
{
|
||||
"id": "e004",
|
||||
"priority": "normal",
|
||||
"bytes": 180,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
}
|
||||
],
|
||||
"sourceSha256": "a4369c8a7234d9e8933e62f6643a73c59b571e267d8a65d668a1d827b72b43b5"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": 1,
|
||||
"budgetBytes": 1000,
|
||||
"packetBytes": 872,
|
||||
"ordering": "pinned first; normal next; aside last; oldest fitting first within tier; output chronological",
|
||||
"exchanges": [
|
||||
{
|
||||
"id": "e001",
|
||||
"priority": "pinned",
|
||||
"bytes": 191,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
},
|
||||
{
|
||||
"id": "e002",
|
||||
"priority": "normal",
|
||||
"bytes": 391,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
},
|
||||
{
|
||||
"id": "e003",
|
||||
"priority": "aside",
|
||||
"bytes": 180,
|
||||
"selected": false,
|
||||
"reason": "budget"
|
||||
},
|
||||
{
|
||||
"id": "e004",
|
||||
"priority": "normal",
|
||||
"bytes": 180,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Context priority continuation
|
||||
|
||||
Selected complete exchanges in original order. JSONL payloads are verbatim.
|
||||
|
||||
## e001
|
||||
|
||||
```jsonl
|
||||
{"role":"user","content":"Ship a migration plan. Never delete customer records."}
|
||||
{"role":"assistant","content":"Constraint accepted: preserve every customer record."}
|
||||
```
|
||||
|
||||
## e002
|
||||
|
||||
```jsonl
|
||||
{"role":"user","content":"Check the schema."}
|
||||
{"role":"assistant","content":"Reading schema.","tool_calls":[{"id":"schema-1","type":"function","function":{"name":"read_file","arguments":"schema.sql"}}]}
|
||||
{"role":"tool","tool_call_id":"schema-1","content":"customers(id, email, created_at)"}
|
||||
{"role":"assistant","content":"Add the index concurrently; retain all rows."}
|
||||
```
|
||||
|
||||
## e004
|
||||
|
||||
```jsonl
|
||||
{"role":"user","content":"Continue with the rollback checklist."}
|
||||
{"role":"assistant","content":"Rollback removes the new index only, never customer data."}
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 468 KiB |
@@ -0,0 +1,4 @@
|
||||
Native hb-5 PTY: annotated a synthetic sample into a temporary sidecar.
|
||||
Reran annotate against the existing sidecar: CLI paused at
|
||||
"Overwrite ...? Type OVERWRITE: ". Sent OVERWRITE; CLI succeeded.
|
||||
Noninteractive refusal, source immutability and alias guards also covered by CLI tests.
|
||||
@@ -0,0 +1,5 @@
|
||||
Native macOS Chrome: NOT VERIFIED. Both Playwright launch and the requested
|
||||
Chrome --headless=new --window-size=1280,900 file:// screenshot command aborted
|
||||
with SIGABRT before rendering under the seat sandbox. No native screenshot produced.
|
||||
Desktop and phone were instead inspected in Colima Chromium 149.0.7827.53.
|
||||
Native hb-5 Node v24.21.0 CLI/core and extracted-bundle tests passed.
|
||||
@@ -0,0 +1,17 @@
|
||||
✔ CLI export, hash guard, noninteractive overwrite refusal, source aliases and recovery-safe preflight (384.033125ms)
|
||||
✔ parse four complete exchanges and preserve the exact source (0.61175ms)
|
||||
✔ pins survive verbatim; whole tool exchange stays paired; chronological output (0.765375ms)
|
||||
✔ pinned minimum is exact, and too-small budget refuses (0.316458ms)
|
||||
✔ normal beats an earlier aside; oversized normal can be skipped whole (0.155917ms)
|
||||
✔ bad priorities, budgets, incomplete exchanges and tool mismatches fail closed (0.324042ms)
|
||||
✔ message-wrapped tool blocks belong to the same exchange (0.190083ms)
|
||||
✔ fences grow around hostile Markdown and Unicode byte counts are exact (0.133375ms)
|
||||
✔ selection is deterministic and leaves inputs intact (0.139167ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 435.823875
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": 1,
|
||||
"budgetBytes": 1000,
|
||||
"packetBytes": 872,
|
||||
"ordering": "pinned first; normal next; aside last; oldest fitting first within tier; output chronological",
|
||||
"exchanges": [
|
||||
{
|
||||
"id": "e001",
|
||||
"priority": "pinned",
|
||||
"bytes": 191,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
},
|
||||
{
|
||||
"id": "e002",
|
||||
"priority": "normal",
|
||||
"bytes": 391,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
},
|
||||
{
|
||||
"id": "e003",
|
||||
"priority": "aside",
|
||||
"bytes": 180,
|
||||
"selected": false,
|
||||
"reason": "budget"
|
||||
},
|
||||
{
|
||||
"id": "e004",
|
||||
"priority": "normal",
|
||||
"bytes": 180,
|
||||
"selected": true,
|
||||
"reason": "selected"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Context priority continuation
|
||||
|
||||
Selected complete exchanges in original order. JSONL payloads are verbatim.
|
||||
|
||||
## e001
|
||||
|
||||
```jsonl
|
||||
{"role":"user","content":"Ship a migration plan. Never delete customer records."}
|
||||
{"role":"assistant","content":"Constraint accepted: preserve every customer record."}
|
||||
```
|
||||
|
||||
## e002
|
||||
|
||||
```jsonl
|
||||
{"role":"user","content":"Check the schema."}
|
||||
{"role":"assistant","content":"Reading schema.","tool_calls":[{"id":"schema-1","type":"function","function":{"name":"read_file","arguments":"schema.sql"}}]}
|
||||
{"role":"tool","tool_call_id":"schema-1","content":"customers(id, email, created_at)"}
|
||||
{"role":"assistant","content":"Add the index concurrently; retain all rows."}
|
||||
```
|
||||
|
||||
## e004
|
||||
|
||||
```jsonl
|
||||
{"role":"user","content":"Continue with the rollback checklist."}
|
||||
{"role":"assistant","content":"Rollback removes the new index only, never customer data."}
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 445 KiB |
@@ -0,0 +1,5 @@
|
||||
POST /api/signup 303
|
||||
POST /api/signup 400
|
||||
POST /api/signup 303
|
||||
POST /api/signup 404
|
||||
PASS: real handler, isolated form POST, 303 redirect, record, invalid email, honeypot and unpublished venture guard. No production data written.
|
||||
@@ -0,0 +1,10 @@
|
||||
{"role":"user","content":"Ship a migration plan. Never delete customer records."}
|
||||
{"role":"assistant","content":"Constraint accepted: preserve every customer record."}
|
||||
{"role":"user","content":"Check the schema."}
|
||||
{"role":"assistant","content":"Reading schema.","tool_calls":[{"id":"schema-1","type":"function","function":{"name":"read_file","arguments":"schema.sql"}}]}
|
||||
{"role":"tool","tool_call_id":"schema-1","content":"customers(id, email, created_at)"}
|
||||
{"role":"assistant","content":"Add the index concurrently; retain all rows."}
|
||||
{"role":"user","content":"Aside: what should the mascot wear?"}
|
||||
{"role":"assistant","content":"A very large green hat. This does not affect the migration."}
|
||||
{"role":"user","content":"Continue with the rollback checklist."}
|
||||
{"role":"assistant","content":"Rollback removes the new index only, never customer data."}
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
const sample = [
|
||||
{role:'user',content:'Ship a migration plan. Never delete customer records.'},
|
||||
{role:'assistant',content:'Constraint accepted: preserve every customer record.'},
|
||||
{role:'user',content:'Check the schema.'},
|
||||
{role:'assistant',content:'Reading schema.',tool_calls:[{id:'schema-1',type:'function',function:{name:'read_file',arguments:'schema.sql'}}]},
|
||||
{role:'tool',tool_call_id:'schema-1',content:'customers(id, email, created_at)'},
|
||||
{role:'assistant',content:'Add the index concurrently; retain all rows.'},
|
||||
{role:'user',content:'Aside: what should the mascot wear?'},
|
||||
{role:'assistant',content:'A very large green hat. This does not affect the migration.'},
|
||||
{role:'user',content:'Continue with the rollback checklist.'},
|
||||
{role:'assistant',content:'Rollback removes the new index only, never customer data.'}
|
||||
].map(row=>JSON.stringify(row)).join('\n')+'\n';
|
||||
const engine = ContextPriority;
|
||||
const exchanges = engine.parse(sample);
|
||||
const priorities = {e001:'pinned',e002:'normal',e003:'aside',e004:'normal'};
|
||||
const labels = ['Customer data constraint','Schema + tool call/result','Mascot tangent','Rollback checklist'];
|
||||
let result;
|
||||
const list = document.querySelector('#exchanges');
|
||||
exchanges.forEach((exchange,index)=>{
|
||||
const row=document.createElement('label'); row.className='exchange';
|
||||
const text=document.createElement('span'); text.textContent=exchange.id+' / '+labels[index];
|
||||
const select=document.createElement('select'); select.setAttribute('aria-label',labels[index]+' priority'); select.dataset.id=exchange.id;
|
||||
for(const value of ['pinned','normal','aside']) {const option=document.createElement('option');option.value=value;option.textContent=value;select.append(option);}
|
||||
select.value=priorities[exchange.id]; select.addEventListener('change',()=>{priorities[exchange.id]=select.value;render();});
|
||||
row.append(text,select);list.append(row);
|
||||
});
|
||||
function render(){
|
||||
result=null; document.querySelector('#download').disabled=true;document.querySelector('#manifest-download').disabled=true;
|
||||
try {
|
||||
result=engine.select(exchanges,priorities,Number(document.querySelector('#budget').value));
|
||||
document.querySelector('#packet').textContent=result.packet;
|
||||
document.querySelector('#manifest').textContent=JSON.stringify(result.manifest,null,2);
|
||||
document.querySelector('#status').textContent=result.manifest.packetBytes+' bytes / '+result.manifest.exchanges.filter(e=>e.selected).length+' of 4 exchanges kept';
|
||||
document.querySelector('#download').disabled=false; document.querySelector('#manifest-download').disabled=false;
|
||||
}catch(error){document.querySelector('#packet').textContent='Export refused. Raise the budget or change your pins.';document.querySelector('#manifest').textContent='';document.querySelector('#status').textContent=error.message;}
|
||||
}
|
||||
function download(value,name,type){const url=URL.createObjectURL(new Blob([value],{type}));const a=document.createElement('a');a.href=url;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);}
|
||||
document.querySelector('#budget').addEventListener('input',render);
|
||||
document.querySelector('#download').addEventListener('click',()=>{if(result)download(result.packet,'continuation.md','text/markdown');});
|
||||
document.querySelector('#manifest-download').addEventListener('click',()=>{if(result)download(JSON.stringify(result.manifest,null,2),'omissions.json','application/json');});
|
||||
document.querySelector('#source').textContent=sample;
|
||||
if(new URLSearchParams(location.search).get('joined')==='1'){document.querySelector('#thanks').hidden=false;document.querySelector('#join-form').hidden=true;}
|
||||
render();
|
||||
@@ -0,0 +1,70 @@
|
||||
/* One deterministic engine, used by Node and the browser. No dependencies. */
|
||||
(function (root) {
|
||||
'use strict';
|
||||
const bytes = s => new TextEncoder().encode(s).length;
|
||||
const header = '# Context priority continuation\n\nSelected complete exchanges in original order. JSONL payloads are verbatim.\n\n';
|
||||
function parse(source) {
|
||||
if (typeof source !== 'string' || !source.trim()) throw Error('Empty transcript');
|
||||
const exchanges = []; let current; let pending = new Set(); let assistant = false; let lastRole; let seenCalls = new Set();
|
||||
function finish() {
|
||||
if (!current) return;
|
||||
if (!assistant || pending.size || lastRole !== 'assistant') throw Error('Incomplete exchange ' + current.id + ': missing assistant response or tool result');
|
||||
exchanges.push(current);
|
||||
}
|
||||
for (const [index, raw] of (source.match(/[^\n]*\n|[^\n]+$/g) || []).entries()) {
|
||||
if (!raw.trim()) throw Error('Blank line at ' + (index + 1));
|
||||
let row; try { row = JSON.parse(raw); } catch { throw Error('Invalid JSON at line ' + (index + 1)); }
|
||||
const msg = row.message || row;
|
||||
const role = msg.role || row.type;
|
||||
if (!['user', 'assistant', 'tool'].includes(role) || msg.content === undefined) throw Error('Unsupported record at line ' + (index + 1) + '; use user/assistant/tool JSONL');
|
||||
const blocks = Array.isArray(msg.content) ? msg.content : [];
|
||||
const results = blocks.filter(b => b.type === 'tool_result');
|
||||
const resultOnly = role === 'tool' || (role === 'user' && blocks.length > 0 && results.length === blocks.length);
|
||||
if (role === 'user' && !resultOnly) {
|
||||
if (results.length) throw Error('Mixed user text and tool results: normalize this export first');
|
||||
finish(); current = {id: 'e' + String(exchanges.length + 1).padStart(3, '0'), raw: ''}; pending = new Set(); assistant = false; seenCalls = new Set();
|
||||
}
|
||||
if (!current) throw Error('Transcript must start with a user prompt');
|
||||
if (role === 'assistant') {
|
||||
assistant = true;
|
||||
const calls = [...(msg.tool_calls || []).map(c => c.id), ...blocks.filter(b => b.type === 'tool_use').map(b => b.id)];
|
||||
for (const id of calls) {
|
||||
if (typeof id !== 'string' || !id || seenCalls.has(id)) throw Error('Invalid or duplicate tool call');
|
||||
pending.add(id); seenCalls.add(id);
|
||||
}
|
||||
}
|
||||
const ids = role === 'tool' ? [msg.tool_call_id] : results.map(b => b.tool_use_id);
|
||||
for (const id of ids) { if (!pending.delete(id)) throw Error('Unmatched tool result at line ' + (index + 1)); }
|
||||
current.raw += raw; lastRole = role;
|
||||
}
|
||||
finish(); return exchanges;
|
||||
}
|
||||
function block(exchange) {
|
||||
let length = 3;
|
||||
for (const match of exchange.raw.matchAll(/`+/g)) length = Math.max(length, match[0].length + 1);
|
||||
const fence = '`'.repeat(length);
|
||||
return '## ' + exchange.id + '\n\n' + fence + 'jsonl\n' + exchange.raw + (exchange.raw.endsWith('\n') ? '' : '\n') + fence + '\n\n';
|
||||
}
|
||||
function select(exchanges, priorities, budget) {
|
||||
if (!Number.isSafeInteger(budget) || budget < 0) throw Error('Budget must be a nonnegative integer of UTF-8 bytes');
|
||||
if (!priorities || typeof priorities !== 'object' || Array.isArray(priorities)) throw Error('Invalid priorities');
|
||||
const ids = new Set(exchanges.map(e => e.id));
|
||||
for (const [id, priority] of Object.entries(priorities)) {
|
||||
if (!ids.has(id) || !['pinned', 'normal', 'aside'].includes(priority)) throw Error('Invalid priority entry: ' + id);
|
||||
}
|
||||
const priority = e => priorities[e.id] || 'normal';
|
||||
const selected = new Set(); const costs = new Map(exchanges.map(e => [e.id, bytes(block(e))]));
|
||||
let used = bytes(header);
|
||||
for (const e of exchanges.filter(e => priority(e) === 'pinned')) { selected.add(e.id); used += costs.get(e.id); }
|
||||
if (used > budget) throw Error('Budget too small: pinned content and packet framing require ' + used + ' bytes');
|
||||
for (const tier of ['normal','aside']) for (const e of exchanges.filter(e => priority(e) === tier)) {
|
||||
if (used + costs.get(e.id) <= budget) { selected.add(e.id); used += costs.get(e.id); }
|
||||
}
|
||||
const packet = header + exchanges.filter(e => selected.has(e.id)).map(block).join('');
|
||||
const manifest = {version: 1, budgetBytes: budget, packetBytes: bytes(packet), ordering: 'pinned first; normal next; aside last; oldest fitting first within tier; output chronological', exchanges: exchanges.map(e => ({id:e.id,priority:priority(e),bytes:costs.get(e.id),selected:selected.has(e.id),reason:selected.has(e.id)?'selected':'budget'}))};
|
||||
return {packet,manifest};
|
||||
}
|
||||
const api = {parse,select,bytes,block};
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
||||
else root.ContextPriority = api;
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : this);
|
||||
@@ -0,0 +1,40 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="description" content="Pin the exchanges that matter. A local, deterministic context selector with a free CLI and synthetic browser demo.">
|
||||
<title>Context Priority — KEEP THE IMPORTANT PART.</title>
|
||||
<style>
|
||||
:root{--paper:#f4f1e7;--ink:#171713;--acid:#d7ff00;--orange:#fa5a25}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--paper);color:var(--ink);font-family:Georgia,'Times New Roman',serif}a{color:inherit;text-underline-offset:4px}button,input,select,textarea{font:inherit;border:2px solid var(--ink);border-radius:0;color:var(--ink);background:var(--paper)}button{padding:12px 18px;cursor:pointer;font-family:monospace;font-weight:bold}button:disabled{opacity:.4;cursor:default}a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:4px solid var(--orange);outline-offset:3px}[hidden]{display:none!important}.mast{display:flex;justify-content:space-between;gap:20px;padding:18px 3vw;border-bottom:3px solid;font:700 14px monospace;letter-spacing:1px}.mast span:last-child{text-align:right}.hero{display:grid;grid-template-columns:1.6fr 1fr;border-bottom:3px solid}.hero-title{padding:35px 3vw 40px;border-right:3px solid;overflow:hidden}.stamp{display:inline-block;background:var(--ink);color:var(--paper);padding:8px 12px;transform:rotate(-2deg);font:700 13px monospace}h1{font-family:Impact,'Arial Narrow',sans-serif;font-weight:900;font-size:clamp(64px,8.6vw,145px);letter-spacing:-.045em;line-height:.91;text-transform:uppercase;margin:30px 0 20px}h1 .keep{background:var(--acid);display:inline-block;padding:0 5px}h1 .part{color:var(--orange)}.hero-copy{padding:36px 3vw;display:flex;flex-direction:column;justify-content:space-between;gap:25px;background:var(--acid)}.eyebrow{font:700 13px monospace;text-transform:uppercase;letter-spacing:1px}.hero-copy p{font-size:24px;line-height:1.3;margin:0}.primary{display:block;padding:20px;background:var(--ink);color:var(--paper);font:bold 19px monospace;text-decoration:none;box-shadow:6px 6px 0 var(--orange)}.micro{font:13px/1.5 monospace}.ticker{background:var(--ink);color:var(--paper);padding:15px 3vw;font:bold 14px monospace;line-height:1.6}.section{padding:42px 3vw;border-bottom:3px solid}h2{font:900 clamp(36px,5vw,72px)/1 Impact,'Arial Narrow',sans-serif;text-transform:uppercase;letter-spacing:-1px;margin:12px 0 24px}h3{font:700 22px monospace;margin:0 0 15px}.demo-head{display:flex;justify-content:space-between;align-items:end;gap:30px}.budget{font:700 14px monospace;min-width:210px}.budget input{display:block;width:100%;padding:10px;margin-top:8px}.demo{display:grid;grid-template-columns:1fr 1.1fr;border:3px solid}.controls{padding:20px;border-right:3px solid}.exchange{display:flex;justify-content:space-between;align-items:center;gap:12px;border-bottom:2px solid;padding:18px 0;font:14px/1.5 monospace}.exchange select{padding:8px;flex-shrink:0}#status{padding:15px;background:var(--acid);font:bold 14px/1.5 monospace;min-height:64px}pre{font:12px/1.65 monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:0}#packet{height:360px;overflow:auto;padding:20px;background:#fffdf6}.actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:20px}details{margin-top:20px;border-top:2px solid;padding-top:15px}summary{font:700 14px monospace;cursor:pointer}details pre{margin-top:15px}.how{display:grid;grid-template-columns:1fr 1.2fr;gap:35px}.how p,.limits p{font-size:20px;line-height:1.5}.terminal{background:var(--ink);color:var(--acid);padding:22px;border:3px solid var(--ink)}.download{font:bold 16px monospace;display:inline-block;border-bottom:3px solid;margin-top:15px}.prices{display:grid;grid-template-columns:repeat(3,1fr);border:3px solid}.price{padding:24px;border-right:3px solid}.price:last-child{border-right:0}.amount{font:900 46px Impact,'Arial Narrow',sans-serif;margin:20px 0}.price p{font-size:18px;line-height:1.5}.price .micro{font-size:13px}.join{display:grid;grid-template-columns:1fr 1fr;gap:40px;background:var(--orange)}#join-form{display:grid;gap:18px}#join-form label{font:700 14px monospace}#join-form input,#join-form select,#join-form textarea{display:block;padding:13px;width:100%;margin-top:7px}#join-form button{background:var(--ink);color:var(--paper);font-size:18px}#thanks{padding:22px;background:var(--acid);border:3px solid;font:700 22px/1.4 monospace}.limits{max-width:1000px}footer{display:flex;justify-content:space-between;gap:20px;padding:24px 3vw;font:13px/1.5 monospace}@media(max-width:700px){.mast{font-size:11px}.hero{grid-template-columns:1fr}.hero-title{border-right:0;border-bottom:3px solid;padding:28px 20px}.hero-copy{padding:25px 20px}.hero-copy p{font-size:22px}h1{font-size:clamp(60px,15vw,102px)}.section{padding:30px 20px}.demo-head{display:block}.budget{margin-bottom:20px}.demo,.how,.join,.prices{grid-template-columns:1fr}.controls{border-right:0;border-bottom:3px solid;padding:15px}.exchange{font-size:12px}.exchange select{max-width:110px}.price{border-right:0;border-bottom:3px solid}.price:last-child{border-bottom:0}#packet{height:300px}footer{display:block}footer span{display:block;margin:10px 0}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
|
||||
</style>
|
||||
<script defer src="engine.js"></script><script defer src="demo.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="mast"><span>CONTEXT PRIORITY / 001</span><span>A NOTPLANNED TOOL<br>LOCAL. DETERMINISTIC. YOUR CALL.</span></header>
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="hero-title"><span class="stamp">CONTEXT IS A FINITE RESOURCE.</span><h1><span class="keep">Keep</span> the<br>important<br><span class="part">part.</span></h1><div class="micro">PINNED → NORMAL → ASIDE<br>ONE ENGINE. NO MODEL CALLS.</div></div>
|
||||
<div class="hero-copy"><div><div class="eyebrow">For people who work in long threads</div><p style="margin-top:20px">The constraint you wrote an hour ago should survive the tangent you wrote five minutes ago.</p><p style="margin-top:20px">Mark whole exchanges. Set a byte budget. Get a continuation packet you can inspect.</p></div><div><a class="primary" href="#join">GET EARLY ACCESS ↗</a><p class="micro" style="margin-top:18px">Free CLI available below. Paid plans are ideas, not a checkout.</p></div></div>
|
||||
</section>
|
||||
<div class="ticker">PINS STAY VERBATIM. TOOL PAIRS STAY TOGETHER. TOO SMALL A BUDGET? WE REFUSE THE EXPORT.</div>
|
||||
<section class="section" id="demo"><div class="demo-head"><div><div class="eyebrow">01 / Try the actual selection engine</div><h2>Give the tangent<br>less room.</h2></div><label class="budget" for="budget">PACKET BUDGET / UTF-8 BYTES<input id="budget" type="number" min="0" step="50" value="1000"></label></div>
|
||||
<p>Four synthetic exchanges. Change a priority and watch the packet change. Pin everything, then lower the budget to see a hard refusal. No transcript uploads.</p>
|
||||
<div class="demo"><div class="controls"><h3>BEFORE / PRIORITIES</h3><div id="exchanges"></div><p class="micro">Oldest fitting exchange wins within each tier. Export restores chronology. This is a byte budget, not a model token count.</p><details><summary>Inspect untouched source JSONL</summary><pre id="source"></pre></details></div><div><div id="status" role="status" aria-live="polite">Loading synthetic demo…</div><pre id="packet" aria-label="Continuation packet"></pre></div></div>
|
||||
<div class="actions"><button id="download" disabled>Download this packet ↓</button><button id="manifest-download" disabled>Download omission manifest ↓</button></div><details><summary>Inspect selection / omission manifest</summary><pre id="manifest"></pre></details><noscript><p>The interactive demo needs JavaScript. The free CLI below works offline.</p></noscript>
|
||||
</section>
|
||||
<section class="section"><div class="eyebrow">02 / Run it locally</div><h2>Your files. Your machine.</h2><div class="how"><div><p>No install scripts. No API key. Unzip the free bundle and use Node.js 20 or newer. Annotations live in a separate sidecar tied to the source SHA-256.</p><a class="download" href="context-priority.zip" download>DOWNLOAD FREE CLI (.zip) ↓</a><p class="micro">Includes source, sample, tests and README.<br>Verified on hb-5 macOS and Node 20 in Colima.</p></div><pre class="terminal"># From the extracted context-priority folder:
|
||||
node cli.cjs annotate sample.jsonl priorities.json \
|
||||
e001=pinned e003=aside
|
||||
|
||||
node cli.cjs export sample.jsonl priorities.json \
|
||||
1000 ./out
|
||||
|
||||
# out/continuation.md + out/omissions.json
|
||||
# Existing outputs require typing OVERWRITE.
|
||||
# Run the checks:
|
||||
node --test tests/*.test.cjs</pre></div></section>
|
||||
<section class="section"><div class="eyebrow">03 / Founding plans — no checkout yet</div><h2>Start free. Tell us what’s next.</h2><div class="prices"><div class="price"><h3>LOCAL</h3><div class="amount">$0</div><p>The CLI, synthetic demo, sidecars and packet export. Available today.</p><div class="micro">NO ACCOUNT REQUIRED</div></div><div class="price"><h3>WORKBENCH</h3><div class="amount">$12 <small>/ mo</small></div><p>Proposed saved workflows and richer local import adapters.</p><div class="micro">FOUNDING PRICE IDEA · NOT BUILT</div></div><div class="price"><h3>TEAM</h3><div class="amount">$39 <small>/ mo</small></div><p>Proposed shared priority policies and reviewable packet history.</p><div class="micro">FOUNDING PRICE IDEA · NOT BUILT</div></div></div></section>
|
||||
<section class="section join" id="join"><div><div class="eyebrow">04 / Join the early access list</div><h2>Help pick<br>the next<br>exchange.</h2><p>Choose the tier you’re interested in. No payment, no commitment.</p><p class="micro">We use your email for Context Priority launch updates and early access invitations.</p></div><div><div id="thanks" hidden>YOU’RE ON THE LIST.<br>Thanks. We’ll email when there’s something to try.</div><form id="join-form" method="post" action="/api/signup" enctype="application/x-www-form-urlencoded"><input type="hidden" name="venture" value="context-priority"><input type="hidden" name="website" value=""><label>Email<input type="email" name="email" required autocomplete="email" placeholder="you@example.com"></label><label>Interested tier<select name="plan"><option value="free">Local — free</option><option value="workbench">Workbench — $12/mo idea</option><option value="team">Team — $39/mo idea</option></select></label><label>What would make this useful? (optional)<textarea name="note" rows="3" maxlength="500"></textarea></label><button type="submit">JOIN EARLY ACCESS ↗</button></form></div></section>
|
||||
<section class="section limits"><div class="eyebrow">05 / The fine print, in normal-sized type</div><h2>Selection. Not magic.</h2><p>This is a standalone offline selector, not live compaction and not an integration with Claude Code, Fly or Boring. It accepts ordered user/assistant/tool JSONL and message-wrapped user/assistant records; metadata rows, branching histories and incomplete exchanges are rejected. Normalize unsupported exports first. There is no tokenizer, summarizer, secret scrubber or semantic dependency analysis. Inspect the packet before sharing it.</p><p>Pinned payloads remain verbatim inside Markdown JSONL blocks. Unpinned exchanges can be omitted. The manifest tells you exactly which ones. A selected exchange keeps all its tool calls and results. Selection does not guarantee an assistant will obey the continuation.</p><p>The board’s upstream reference is <a href="https://github.com/anthropics/claude-code/issues/83489">anthropics/claude-code #83489: named model + effort presets</a>, closed as not planned. That issue asks for presets; this venture implements the board’s separate context-priority brief. It does not implement model presets and is not affiliated with Anthropic.</p></section>
|
||||
</main><footer><span>CONTEXT PRIORITY / BUILT BY NOTPLANNED</span><span>PIN IT. INSPECT IT. CARRY IT FORWARD.</span></footer>
|
||||
</body></html>
|
||||
@@ -0,0 +1,33 @@
|
||||
const {chromium}=require('playwright');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path');const {pathToFileURL}=require('node:url');const engine=require('../site/engine.js');
|
||||
(async()=>{
|
||||
const out=path.resolve(__dirname,'../evidence');const url=pathToFileURL(path.resolve(__dirname,'../site/index.html')).href;
|
||||
const browser=await chromium.launch({executablePath:process.env.CHROME_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',args:['--no-sandbox']});const errors=[],external=[];
|
||||
try {
|
||||
for(const [name,width,height] of [['desktop',1280,900],['phone',390,844]]){
|
||||
const page=await browser.newPage({viewport:{width,height},acceptDownloads:true});page.on('pageerror',e=>errors.push(e.message));page.on('request',r=>{if(/^https?:/.test(r.url()))external.push(r.url());});
|
||||
await page.goto(url);await page.waitForFunction(()=>document.querySelector('#status').textContent.includes('exchanges kept'));
|
||||
assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),true);
|
||||
const source=await page.locator('#source').textContent();const priorities={e001:'pinned',e002:'normal',e003:'aside',e004:'normal'};
|
||||
assert.equal(await page.locator('#packet').textContent(),engine.select(engine.parse(source),priorities,1000).packet);
|
||||
assert.match(await page.locator('#packet').textContent(),/tool_call_id/);assert.doesNotMatch(await page.locator('#packet').textContent(),/green hat/);
|
||||
await page.screenshot({path:path.join(out,name+'.png'),fullPage:true});
|
||||
for(const [selector,filename] of [['#download',name+'-packet.md'],['#manifest-download',name+'-manifest.json']]){
|
||||
const [download]=await Promise.all([page.waitForEvent('download'),page.locator(selector).click()]);await download.saveAs(path.join(out,filename));
|
||||
}
|
||||
assert.equal(fs.readFileSync(path.join(out,name+'-packet.md'),'utf8'),await page.locator('#packet').textContent());
|
||||
assert.equal(JSON.parse(fs.readFileSync(path.join(out,name+'-manifest.json'),'utf8')).packetBytes,Buffer.byteLength(fs.readFileSync(path.join(out,name+'-packet.md'),'utf8')));
|
||||
await page.locator('select[data-id="e003"]').selectOption('pinned');assert.match(await page.locator('#packet').textContent(),/green hat/);
|
||||
await page.locator('#budget').fill('100');assert.match(await page.locator('#status').textContent(),/Budget too small/);assert.equal(await page.locator('#download').isDisabled(),true);assert.equal(await page.locator('#manifest-download').isDisabled(),true);
|
||||
await page.locator('#budget').fill('2000');assert.equal(await page.locator('#download').isDisabled(),false);
|
||||
const form=await page.locator('#join-form').evaluate(f=>({method:f.method,action:f.getAttribute('action'),enctype:f.enctype,fields:Object.fromEntries(new FormData(f))}));
|
||||
assert.equal(form.method,'post');assert.equal(form.action,'/api/signup');assert.equal(form.enctype,'application/x-www-form-urlencoded');assert.equal(form.fields.venture,'context-priority');assert.equal(form.fields.website,'');assert.equal(form.fields.plan,'free');
|
||||
assert.equal(await page.locator('a[download]').getAttribute('href'),'context-priority.zip');
|
||||
const [bundle]=await Promise.all([page.waitForEvent('download'),page.locator('a[download]').click()]);
|
||||
const bundlePath='/tmp/priority-'+name+'-download.zip';await bundle.saveAs(bundlePath);
|
||||
assert.deepEqual(fs.readFileSync(bundlePath),fs.readFileSync(path.resolve(__dirname,'../site/context-priority.zip')));
|
||||
await page.goto(url+'?joined=1#join');assert.equal(await page.locator('#thanks').isVisible(),true);assert.equal(await page.locator('#join-form').isVisible(),false);await page.close();
|
||||
}
|
||||
assert.deepEqual(errors,[]);assert.deepEqual(external,[]);
|
||||
fs.writeFileSync(path.join(out,'browser.json'),JSON.stringify({platform:process.platform,browser:await browser.version(),viewports:['1280x900','390x844'],route:'file:// site/index.html (publication happens after reply)',checks:['no horizontal overflow','shared engine parity','paired tool exchange','priority changes','pin budget refusal','packet + manifest + free bundle downloads','plain signup form','thank-you redirect state','no external requests','no JS errors'],htmlSha256:require('node:crypto').createHash('sha256').update(fs.readFileSync(path.resolve(__dirname,'../site/index.html'))).digest('hex')},null,2)+'\n');console.log('PASS: desktop and phone screenshots, download parity, pins, budget refusal, form, thank-you, no external requests or JS errors');
|
||||
}finally{await browser.close();}
|
||||
})().catch(e=>{console.error(e);process.exitCode=1;});
|
||||
@@ -0,0 +1,24 @@
|
||||
const test=require('node:test');const assert=require('node:assert/strict');const fs=require('node:fs');const os=require('node:os');const path=require('node:path');const {spawnSync}=require('node:child_process');
|
||||
const cli=path.resolve(__dirname,'../cli.cjs');const sample=fs.readFileSync(path.resolve(__dirname,'../sample.jsonl'));
|
||||
test('CLI export, hash guard, noninteractive overwrite refusal, source aliases and recovery-safe preflight',()=>{
|
||||
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'priority-test-'));
|
||||
try {
|
||||
const source=path.join(dir,'source.jsonl'),side=path.join(dir,'side.json'),out=path.join(dir,'out');fs.writeFileSync(source,sample);
|
||||
const run=(...args)=>spawnSync(process.execPath,[cli,...args],{encoding:'utf8'});
|
||||
assert.equal(run('annotate',source,side,'e001=pinned','e003=aside').status,0);
|
||||
assert.equal(run('export',source,side,'1000',out).status,0);
|
||||
const packet=fs.readFileSync(path.join(out,'continuation.md'));const manifest=JSON.parse(fs.readFileSync(path.join(out,'omissions.json')));
|
||||
assert.equal(packet.length,manifest.packetBytes);assert.ok(packet.length<=1000);assert.ok(packet.includes(Buffer.from('Never delete customer records.')));
|
||||
assert.equal(fs.statSync(path.join(out,'continuation.md')).mode&0o777,0o600);
|
||||
assert.match(run('export',source,side,'1000',out).stderr,/terminal/);assert.deepEqual(fs.readFileSync(path.join(out,'continuation.md')),packet);
|
||||
assert.match(run('annotate',source,side).stderr,/terminal/);
|
||||
assert.match(run('annotate',source,source).stderr,/overwrite an input/);
|
||||
const alias=path.join(dir,'alias.json');fs.symlinkSync(source,alias);assert.match(run('annotate',source,alias).stderr,/regular file/);
|
||||
const hard=path.join(dir,'hard.json');fs.linkSync(source,hard);assert.match(run('annotate',source,hard).stderr,/regular file/);
|
||||
assert.equal(run('export',source,side,'0',path.join(dir,'tiny')).status,1);assert.ok(!fs.existsSync(path.join(dir,'tiny')));
|
||||
fs.appendFileSync(source,' ');assert.match(run('export',source,side,'1000',path.join(dir,'changed')).stderr,/Blank line|match/);
|
||||
fs.writeFileSync(source,sample);assert.deepEqual(fs.readFileSync(source),sample);
|
||||
const trap=path.join(dir,'trap');fs.mkdirSync(trap);fs.symlinkSync(source,path.join(trap,'omissions.json'));
|
||||
assert.equal(run('export',source,side,'1000',trap).status,1);assert.ok(!fs.existsSync(path.join(trap,'continuation.md')));assert.deepEqual(fs.readFileSync(source),sample);
|
||||
}finally{fs.rmSync(dir,{recursive:true,force:true});}
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const e = require('../site/engine.js');
|
||||
const source = fs.readFileSync(path.join(__dirname,'../sample.jsonl'),'utf8');
|
||||
const exchanges = e.parse(source);
|
||||
test('parse four complete exchanges and preserve the exact source',()=>{
|
||||
assert.equal(exchanges.length,4);assert.equal(exchanges.map(x=>x.raw).join(''),source);
|
||||
assert.match(exchanges[1].raw,/tool_call_id/);
|
||||
});
|
||||
test('pins survive verbatim; whole tool exchange stays paired; chronological output',()=>{
|
||||
const budget=e.bytes(e.select(exchanges,{e001:'pinned',e003:'aside'},99999).packet)-e.bytes(e.block(exchanges[2]));
|
||||
const result=e.select(exchanges,{e001:'pinned',e003:'aside'},budget);
|
||||
assert.deepEqual(result.manifest.exchanges.filter(x=>x.selected).map(x=>x.id),['e001','e002','e004']);
|
||||
assert.ok(result.packet.includes(exchanges[0].raw));assert.ok(result.packet.includes(exchanges[1].raw));assert.ok(result.manifest.packetBytes<=budget);
|
||||
assert.ok(result.packet.indexOf('## e001')<result.packet.indexOf('## e002'));
|
||||
});
|
||||
test('pinned minimum is exact, and too-small budget refuses',()=>{
|
||||
const pinned=e.select([exchanges[1]],{e002:'pinned'},99999);
|
||||
assert.throws(()=>e.select(exchanges,{e002:'pinned'},pinned.manifest.packetBytes-1),/Budget too small/);
|
||||
const result=e.select(exchanges,{e002:'pinned'},pinned.manifest.packetBytes);
|
||||
assert.deepEqual(result.manifest.exchanges.filter(x=>x.selected).map(x=>x.id),['e002']);
|
||||
});
|
||||
test('normal beats an earlier aside; oversized normal can be skipped whole',()=>{
|
||||
const budget=e.select([exchanges[3]],{},99999).manifest.packetBytes;
|
||||
const r=e.select(exchanges,{e001:'aside',e002:'normal',e003:'aside'},budget);
|
||||
assert.deepEqual(r.manifest.exchanges.filter(x=>x.selected).map(x=>x.id),['e004']);
|
||||
});
|
||||
test('bad priorities, budgets, incomplete exchanges and tool mismatches fail closed',()=>{
|
||||
for(const n of [-1,NaN,Infinity,1.5])assert.throws(()=>e.select(exchanges,{},n));
|
||||
assert.throws(()=>e.select(exchanges,{e999:'pinned'},9999));assert.throws(()=>e.select(exchanges,{e001:'urgent'},9999));
|
||||
for(const s of ['', '{broken', '{"role":"user","content":"hi"}\n', '{"type":"system","content":"metadata"}\n', source.replace('"tool_call_id":"schema-1"','"tool_call_id":"wrong"'), source.split('\n').filter(l=>!l.includes('"role":"tool"')).join('\n'), source.split('\n').slice(0,5).join('\n')]) assert.throws(()=>e.parse(s));
|
||||
});
|
||||
test('message-wrapped tool blocks belong to the same exchange',()=>{
|
||||
const rows=[{type:'user',message:{role:'user',content:'read'}},{type:'assistant',message:{role:'assistant',content:[{type:'tool_use',id:'a',name:'read',input:{}}]}},{type:'user',message:{role:'user',content:[{type:'tool_result',tool_use_id:'a',content:'result'}]}},{type:'assistant',message:{role:'assistant',content:'done'}}];
|
||||
const raw=rows.map(r=>JSON.stringify(r)).join('\r\n');
|
||||
assert.equal(e.parse(raw).length,1);assert.equal(e.parse(raw)[0].raw,raw);
|
||||
rows[2].message.content.push({type:'text',text:'new question'});assert.throws(()=>e.parse(rows.map(r=>JSON.stringify(r)).join('\n')),/Mixed/);
|
||||
});
|
||||
test('fences grow around hostile Markdown and Unicode byte counts are exact',()=>{
|
||||
const raw='{"role":"user","content":"```` </script> 🍵"}\n{"role":"assistant","content":"ok"}\n';
|
||||
const r=e.select(e.parse(raw),{e001:'pinned'},9999);
|
||||
assert.ok(r.packet.includes('`````jsonl'));assert.ok(r.packet.includes(raw));assert.equal(r.manifest.packetBytes,Buffer.byteLength(r.packet));
|
||||
});
|
||||
test('selection is deterministic and leaves inputs intact',()=>{
|
||||
const before=JSON.stringify(exchanges), priorities={e003:'aside'};
|
||||
assert.deepEqual(e.select(exchanges,priorities,1000),e.select(exchanges,priorities,1000));assert.equal(JSON.stringify(exchanges),before);assert.deepEqual(priorities,{e003:'aside'});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Check the existing signup service in isolation; never writes production data.
|
||||
Usage: python3 tests/signup.py /path/to/existing/server.py
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from http.server import ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
spec = importlib.util.spec_from_file_location('signup_under_test', sys.argv[1])
|
||||
server = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(server)
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
return None
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='priority-signup-') as directory:
|
||||
root = Path(directory)
|
||||
server.DATA = root / 'signups.jsonl'
|
||||
server.SITES = root / 'sites'
|
||||
(server.SITES / 'context-priority').mkdir(parents=True)
|
||||
(server.SITES / 'context-priority/index.html').write_text('isolated published-page fixture')
|
||||
httpd = ThreadingHTTPServer(('127.0.0.1', 0), server.Handler)
|
||||
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
url = f'http://127.0.0.1:{httpd.server_port}/api/signup'
|
||||
opener = urllib.request.build_opener(NoRedirect)
|
||||
def post(fields):
|
||||
request = urllib.request.Request(url, data=urllib.parse.urlencode(fields).encode(), headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
try:
|
||||
return opener.open(request)
|
||||
except urllib.error.HTTPError as response:
|
||||
return response
|
||||
fields = {'venture':'context-priority','email':'test@example.invalid','plan':'workshop','note':'isolated acceptance','website':''}
|
||||
response = post(fields)
|
||||
assert response.code == 303
|
||||
assert response.headers['Location'] == '/v/context-priority/?joined=1#join'
|
||||
row = json.loads(server.DATA.read_text())
|
||||
assert all(row[key] == fields[key] for key in ['venture','email','plan','note'])
|
||||
before = server.DATA.read_text()
|
||||
assert post({**fields,'email':'invalid'}).code == 400
|
||||
assert post({**fields,'website':'bot'}).code == 303
|
||||
assert server.DATA.read_text() == before
|
||||
assert post({**fields,'venture':'unpublished'}).code == 404
|
||||
httpd.shutdown()
|
||||
httpd.server_close()
|
||||
print('PASS: real handler, isolated form POST, 303 redirect, record, invalid email, honeypot and unpublished venture guard. No production data written.')
|
||||
Reference in new issue
Block a user