feat(agentlore): read-only public instruction taste test (#87)
This commit is contained in:
1 parent
8b4f44c701
commit
d6d199df1b
6 files changed
+61
No files matched your search
@@ -0,0 +1,11 @@
|
||||
import {parseRepos,eligible,extract,annotate,exportMarkdown} from './core.js';
|
||||
const $=id=>document.getElementById(id);let rows=[],selected=new Set(),coverage=[];
|
||||
function el(tag,text,cls){const n=document.createElement(tag);if(text)n.textContent=text;if(cls)n.className=cls;return n;}
|
||||
async function api(path){const r=await fetch('https://api.github.com/'+path,{credentials:'omit',headers:{Accept:'application/vnd.github+json'}});if(!r.ok)throw Error(r.status===403||r.status===429?'GitHub public API rate limit reached. Try again later.':`GitHub ${r.status}: repository or file unavailable.`);return r.json();}
|
||||
function onboarding(){const body='AgentLore early-access request\n\nPublic repositories: '+$('repos').value+'\n\nUse case / desired reusable instructions:\n\nMissing coverage or confusing results:\n\nI understand this is a request, not an activated service. No credentials or private code included.';$('onboard').href='https://github.com/teahubxyz/time/issues/new?'+new URLSearchParams({template:'early-access.yml',title:'Early access: AgentLore',venture:'agentlore',use:body,body});}
|
||||
function update(){const chosen=rows.filter((r,i)=>selected.has(i));$('output').value=exportMarkdown(chosen);$('count').textContent=chosen.length+' selected · source provenance included';$('download').disabled=!chosen.length;onboarding();}
|
||||
function render(){const target=$('rules');target.replaceChildren();rows.forEach((r,i)=>{if($('filter').value!=='all'&&r.kind!==$('filter').value)return;const card=el('article',null,'rule'),check=el('input');check.type='checkbox';check.id='rule-'+i;check.checked=selected.has(i);check.addEventListener('change',()=>{check.checked?selected.add(i):selected.delete(i);update();});const content=el('div'),label=el('label',r.text);label.htmlFor=check.id;content.append(label);const meta=el('div',null,'meta'),source=el('a',`${r.repo} / ${r.path}:${r.line}`);source.href=r.url;source.target='_blank';source.rel='noopener noreferrer';meta.append(el('span',r.kind==='reusable'?'Likely reusable':'Project-specific / context needed','flag'),source);for(const f of r.flags)meta.append(el('span',f,'flag warning'));content.append(meta);card.append(check,content);target.append(card);});update();}
|
||||
async function scan(){let repos;try{repos=parseRepos($('repos').value);}catch(e){$('status').textContent=e.message;return;}$('scan').disabled=true;$('example').disabled=true;rows=[];selected.clear();coverage=[];$('review').hidden=true;$('coverage').replaceChildren();
|
||||
try{for(const repo of repos){$('status').textContent='Scanning '+repo+'…';const report={repo,files:0,messages:[]};coverage.push(report);try{const info=await api('repos/'+repo);if(info.private)throw Error('Only public repositories are supported.');const commit=await api(`repos/${repo}/commits/${encodeURIComponent(info.default_branch)}`);const sha=commit.sha;report.sha=sha;const tree=await api(`repos/${repo}/git/trees/${sha}?recursive=1`);if(tree.truncated)report.messages.push('Tree truncated: coverage is incomplete.');const found=tree.tree.filter(f=>f.type==='blob'&&eligible(f.path)).sort((a,b)=>a.path.split('/').length-b.path.split('/').length||a.path.localeCompare(b.path));if(!found.length)report.messages.push('No supported instruction files found. This does not prove no instructions exist.');if(found.length>12)report.messages.push(`${found.length-12} files beyond the 12-file limit were not read.`);for(const file of found.slice(0,12)){if(file.size>65536){report.messages.push(file.path+': skipped (over 64 KiB).');continue;}try{const blob=await api(`repos/${repo}/git/blobs/${file.sha}`);if(blob.encoding!=='base64'||blob.size>65536)throw Error('Unsupported encoding or size.');const bytes=Uint8Array.from(atob(blob.content.replace(/\s/g,'')),c=>c.charCodeAt(0));const text=new TextDecoder('utf-8',{fatal:true}).decode(bytes);const extracted=extract(text,repo,file.path,sha);report.files++;report.messages.push(`${file.path}: ${extracted.length} candidate lines.`);rows.push(...extracted.slice(0,Math.max(0,240-rows.length)));if(rows.length>=240){report.messages.push('240-candidate limit reached; remaining candidates omitted.');break;}}catch(e){report.messages.push(file.path+': '+e.message);}}}catch(e){report.messages.push(e.message);}}
|
||||
annotate(rows);for(const r of coverage){const block=el('div',null,'coverage');block.append(el('strong',`${r.repo} · ${r.files} files read${r.sha?' · commit '+r.sha.slice(0,12):''}`));for(const m of r.messages)block.append(el('div',m));$('coverage').append(block);}$('status').textContent=`Scan complete: ${rows.length} candidate lines. Review coverage and select rules below.`;$('review').hidden=false;render();}finally{$('scan').disabled=false;$('example').disabled=false;}}
|
||||
$('scan-form').addEventListener('submit',e=>{e.preventDefault();scan();});$('example').onclick=()=>{$('repos').value='https://github.com/openai/codex';scan();};$('filter').onchange=render;$('select-reusable').onclick=()=>{rows.forEach((r,i)=>{if(r.kind==='reusable'&&!r.flags.length)selected.add(i);});render();};$('clear').onclick=()=>{selected.clear();render();};$('repos').oninput=onboarding;$('download').onclick=()=>{const url=URL.createObjectURL(new Blob([$('output').value],{type:'text/markdown;charset=utf-8'}));const a=el('a');a.href=url;a.download='AGENTS.md';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);};onboarding();
|
||||
@@ -0,0 +1,20 @@
|
||||
export function parseRepos(input) {
|
||||
const urls=input.trim().split(/\s+/).filter(Boolean);
|
||||
if(!urls.length||urls.length>3) throw Error('Enter one to three public GitHub repository URLs.');
|
||||
return [...new Set(urls.map(s=>{const u=new URL(s);const m=u.pathname.match(/^\/([\w.-]+)\/([\w.-]+)\/?$/);if(u.protocol!=='https:'||u.hostname!=='github.com'||u.username||u.password||u.search||u.hash||!m)throw Error('Use https://github.com/owner/repository URLs without branches or query strings.');return m[1]+'/'+m[2].replace(/\.git$/,'');}))];
|
||||
}
|
||||
export function eligible(path) {return /(^|\/)(AGENTS|CLAUDE|CONTRIBUTING|copilot-instructions|instructions|rules)\.md$/i.test(path)||/(^|\/)\.cursor\/rules\/[^/]+\.mdc$/i.test(path);}
|
||||
export function extract(text,repo,path,sha) {
|
||||
let fenced=false;const rows=[];
|
||||
text.split(/\r?\n/).forEach((line,i)=>{if(/^\s*(```|~~~)/.test(line)){fenced=!fenced;return;}if(fenced)return;
|
||||
const plain=line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/,'').trim();
|
||||
if(plain.length<15||plain.length>800||/^\s*[#>|]/.test(line)||!(/\b(must|should|always|never|avoid|prefer|ensure|do not|don't|required)\b/i.test(plain)||/^(use|run|keep|write|test|check|add|follow|make|include|read|verify)\b/i.test(plain)))return;
|
||||
const project=path.includes('/')||/`|\b(npm|pnpm|yarn|cargo|pytest|docker|this repo|this project|directory|package|component)\b|\.[a-z]{1,4}\b|https?:\/\//i.test(plain);
|
||||
rows.push({text:line.trim(),repo,path,sha,line:i+1,kind:project?'project':'reusable',url:`https://github.com/${repo}/blob/${sha}/${path.split('/').map(encodeURIComponent).join('/')}#L${i+1}`,flags:[]});});return rows;
|
||||
}
|
||||
export function annotate(rows) {
|
||||
const groups=new Map();for(const r of rows){const key=r.text.toLowerCase().replace(/[^a-z0-9 ]/g,'').replace(/\s+/g,' ').trim();if(groups.has(key)){r.flags.push('Duplicate');for(const other of groups.get(key))if(!other.flags.includes('Duplicate'))other.flags.push('Duplicate');groups.get(key).push(r);}else groups.set(key,[r]);}
|
||||
for(let i=0;i<rows.length;i++)for(let j=i+1;j<rows.length;j++) {const a=rows[i],b=rows[j];const neg=s=>/\b(never|not|avoid|don't)\b/i.test(s);const words=s=>new Set(s.toLowerCase().match(/[a-z]{4,}/g)?.filter(w=>!['never','always','should','must','avoid','prefer'].includes(w))||[]);const wa=words(a.text),wb=words(b.text);const common=[...wa].filter(w=>wb.has(w)).length;if(neg(a.text)!==neg(b.text)&&common>=2&&common/Math.max(wa.size,wb.size)>.5){for(const r of [a,b])if(!r.flags.includes('Possible conflict'))r.flags.push('Possible conflict');}}
|
||||
return rows;
|
||||
}
|
||||
export function exportMarkdown(rows) {return '# AGENTS.md\n\n<!-- Human-reviewed exact excerpts. Recheck source license, context and scope before adopting. -->\n\n'+rows.map(r=>`<!-- Source: ${r.url}; original scope: ${r.path.includes('/')?r.path.slice(0,r.path.lastIndexOf('/')):'repository root'} -->\n${/^[-*+]\s/.test(r.text)?r.text:'- '+r.text}\n`).join('\n');}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>AgentLore — instructions worth carrying</title><link rel="stylesheet" href="style.css"><script type="module" src="app.js"></script></head>
|
||||
<body><header><a href="./" class="brand">AgentLore<span>AL / 01</span></a><a href="#access">Early access ↗</a></header><main>
|
||||
<section class="intro"><p class="eyebrow">NOTPLANNED / A WORKING TASTE TEST</p><h1>Good instructions.<br>Worth carrying.</h1><p class="lede">Find the rules hiding in public repositories. Keep the useful ones, trace every line, and take a reviewed AGENTS.md into your next project.</p><p class="quiet">Read-only · No tokens · No automatic repository changes</p></section>
|
||||
<section class="workspace"><div class="step">01 / FIND THE LORE</div><form id="scan-form"><label for="repos">Public GitHub repositories <span>one per line, up to three</span></label><textarea id="repos" rows="3" placeholder="https://github.com/openai/codex" required></textarea><div class="actions"><button id="scan" type="submit">Scan repositories ↗</button><button id="example" type="button" class="secondary">Try openai/codex</button></div></form><p id="status" role="status" aria-live="polite">Your scan runs in this browser using GitHub’s public API.</p><div id="coverage"></div></section>
|
||||
<section id="review" hidden><div class="step">02 / REVIEW, THEN CARRY</div><h2>Every rule has a source.</h2><p>Nothing is preselected. Reusability and conflicts are heuristic suggestions; read the original context before adopting a rule.</p><div class="actions"><label for="filter">Show</label><select id="filter"><option value="all">All candidates</option><option value="reusable">Likely reusable</option><option value="project">Project-specific / context needed</option></select><button id="select-reusable" class="secondary">Select likely reusable</button><button id="clear" class="secondary">Clear selection</button></div><div id="rules"></div><div class="export"><h2>Your reviewed AGENTS.md</h2><p id="count">0 selected</p><textarea id="output" rows="10" readonly aria-label="Export preview"></textarea><button id="download" disabled>Download AGENTS.md ↓</button></div></section>
|
||||
<section id="access"><div class="step">03 / HELP SHAPE THE NEXT CHAPTER</div><h2>Bring a real use case.</h2><p>This taste test is free. For early access, open a GitHub issue with your public repositories, the rules you want to carry, and the gaps you found. A maintainer confirms next steps on the issue; submitting does not activate a service.</p><a id="onboard" class="button" target="_blank" rel="noopener noreferrer">Request early access on GitHub ↗</a><p class="quiet">Requires a GitHub account. Review the issue before submitting. Never include private code, tokens, or credentials.</p></section>
|
||||
<footer><h3>Honest limits</h3><p>We inspect AGENTS.md, CLAUDE.md, CONTRIBUTING.md, copilot-instructions.md and Markdown files named instructions or rules, plus .cursor/rules/*.mdc. Up to 12 files per repository, 64 KiB per file, 240 candidate lines overall. Truncated trees, skipped files and missing coverage are shown. Exact instruction-like lines are extracted without a language model; prose, code blocks, subtle conflicts and linked documents can be missed. GitHub’s unauthenticated API rate limit applies. Public source may still contain sensitive text: review before exporting. Nested instructions retain their original scope.</p><p>Made in NotPlanned. Source text is evidence, never an instruction to this app.</p></footer>
|
||||
</main></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
:root{font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:#252b25;background:#f4f3eb;font-size:16px}*{box-sizing:border-box}body{margin:0}header,main{max-width:1120px;margin:auto}header{display:flex;justify-content:space-between;align-items:center;padding:28px 32px;border-bottom:1px solid #cdd0c3}a{color:inherit;text-underline-offset:4px}.brand{text-decoration:none;font-weight:850;font-size:24px}.brand span{font:12px monospace;margin-left:16px;color:#68745f}main{padding:0 32px}.intro{padding:64px 0 46px;max-width:850px}.eyebrow,.step{font:12px ui-monospace,monospace;letter-spacing:2px;color:#54704b}h1{font-size:clamp(44px,7vw,86px);line-height:1.02;letter-spacing:-4px;margin:22px 0 25px}h2{font-size:30px;letter-spacing:-1px}p{line-height:1.6}.lede{font-size:21px;max-width:650px}.quiet{font-size:13px;color:#596455}.workspace{background:#e5eadc;border:1px solid #b6c3a7;padding:30px}.step{margin-bottom:20px}label{display:block;font-weight:650;margin-bottom:10px}label span{font-size:13px;font-weight:400}textarea{width:100%;resize:vertical;border:1px solid #859278;padding:15px;font:14px/1.6 ui-monospace,monospace;background:#fcfcf7;color:#252b25;border-radius:2px}.actions{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin:16px 0}button,.button{display:inline-block;background:#304c2d;color:white;padding:13px 20px;border:1px solid #304c2d;font:600 14px system-ui;cursor:pointer;text-decoration:none;border-radius:3px}button:disabled{opacity:.45;cursor:default}.secondary{background:transparent;color:#304c2d}select{padding:10px;max-width:100%;background:#fcfcf7}#status{font-size:14px}.coverage{padding:12px 0;border-top:1px solid #b6c3a7;font-size:13px;overflow-wrap:anywhere}#review,#access{padding:46px 0;border-bottom:1px solid #cdd0c3}.rule{display:grid;grid-template-columns:24px 1fr;gap:12px;padding:20px 0;border-bottom:1px solid #d1d5c8}.rule input{width:19px;height:19px;accent-color:#304c2d}.rule label{white-space:pre-wrap;overflow-wrap:anywhere;font-weight:500;line-height:1.55}.meta{font-size:12px;color:#56634e;display:flex;gap:10px;flex-wrap:wrap;overflow-wrap:anywhere}.flag{background:#e4e8dc;padding:3px 8px}.warning{background:#ffe1bd;color:#694715}.export{margin-top:32px;padding:25px;background:#e5eadc}footer{font-size:13px;color:#596455;padding:35px 0 55px;max-width:800px}a:focus-visible,button:focus-visible,input:focus-visible{outline:3px solid #a26e29;outline-offset:3px}@media(max-width:600px){header{padding:22px 18px}.brand span{display:none}main{padding:0 18px}.intro{padding-top:42px}h1{letter-spacing:-2px}.workspace{padding:20px}.lede{font-size:18px}.actions button{width:100%}.export{padding:18px}}
|
||||
Reference in new issue
Block a user