Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f52610f
Add shared drift engine and Skill Loop QA plugin
Sep 8, 2026
ace20f5
Add explicit skill version selection and one-command QA demo
Sep 8, 2026
502c52e
Add interactive skill version and QA review
Sep 9, 2026
9d9b11b
Use installed Humanizer for live QA and match Skill Loop branding
Sep 9, 2026
e08dcb1
Record live Humanizer verification and label saved baselines accurately
Sep 9, 2026
f6cd3ed
Assess skill packages by default and bind QA to package versions
Sep 9, 2026
e19c9a1
Add Chumbo Supabase history and authenticated QA review app
Sep 9, 2026
ca536b1
Add file-backed QA history handoff and verified Cloudflare checkpoints
Sep 10, 2026
fc79a9f
Bound native Cloudflare transfers and preserve prior checkpoint formats
Sep 10, 2026
c63f42f
Encode repetitive storage payloads deterministically and bound readba…
Sep 10, 2026
3c54b8b
Require actual fetched bytes for cloud verification receipts
Sep 10, 2026
3304ac0
Support flexible skill formats and source-grounded initial checks
Sep 10, 2026
f436103
Fix reference traversal depth and classify context omissions
Sep 10, 2026
a1b8072
Add compact QA history save with exact readback guidance
Sep 10, 2026
847039a
Add verified file-backed D1 package backup and restore
Sep 10, 2026
a0b07ff
Use approved all-skills layout for Claude artifacts
Sep 10, 2026
0340dc6
Default short Claude launch to all accessible skills
Sep 10, 2026
efb32a7
Use participant skills for terminal and desktop first runs
Sep 10, 2026
d2b345f
Add deterministic first overview for accessible skills
Sep 10, 2026
3bcb7ee
Keep first overview output setup non-destructive
Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "organized-ai",
"interface": {
"displayName": "Organized Ai"
},
"plugins": [
{
"name": "skill-loop",
"source": {
"source": "local",
"path": "./plugins/skill-loop"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
5 changes: 5 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@
"name": "organized-meta-wiring",
"source": "./organized-meta-wiring",
"description": "Framework for wiring Meta APIs into a Cloudflare Worker \u2014 spin up Meta Apps and system users, the self-refreshing 60-day token vault, Business Use Case rate limiting, and multi-ad-account fan-out. Covers Marketing API, Conversions API, Business Management, Catalog, Instagram, WhatsApp, Pages and Lead Ads, plus the Meta Ads CLI and hosted Ads MCP server."
},
{
"name": "skill-loop",
"source": "./plugins/skill-loop",
"description": "Detect skill effectiveness drift and stage tested fixes. By Jordaaan; shared engine with GTM Autoresearch."
}
]
}
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -828,3 +828,12 @@ Simplified branded social carousel workflow: capture the source argument, turn i
- optional HyperFrames-style preview MP4 wrapper

**Triggers:** "social carousel", "turn this into slides", "instagram carousel", "linkedin carousel", "info-only carousel"

### Skill Loop — effectiveness drift

[Skill Loop by Jordaaan](plugins/skill-loop/README.md) runs fixed skill tests,
saves baselines, flags lost passing checks, and stages researched improvements
for review. Its bounded iteration core is shared with GTM Autoresearch; GTM's
configuration checks and allowed mutations remain domain-specific.
Start with the no-login offline demo. See the plugin README for CLI/MCP setup,
current compatibility limits, and the local HTML review report.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { iterate, dominates } from './shared/core.mjs';
import { createHash } from 'node:crypto';

export const groups = { tag: 'tagId', trigger: 'triggerId', variable: 'variableId', folder: 'folderId' };
Expand Down Expand Up @@ -117,23 +118,12 @@ export function applyOperations(input, operations) {
return container(c);
}

export async function optimize(input, propose, {maxRounds=5, maxFailures=2, plateauRounds=2}={}) {
for (const n of [maxRounds,maxFailures,plateauRounds]) if (!Number.isInteger(n)||n<1||n>30) throw Error('Loop bounds must be integers from 1 to 30');
let best=container(input), report=audit(best), failures=0, plateau=0;
const baseline=report, rounds=[];
for(let round=1;round<=maxRounds;round++) {
try {
const operations=await propose({container:structuredClone(best),audit:structuredClone(report),round});
const candidate=applyOperations(best,operations), next=audit(candidate);
// No dimension may regress, even when the aggregate score rises.
const accepted=next.score>report.score && next.criticalCount<=report.criticalCount && dimensions.every(d=>next.dimensions[d]>=report.dimensions[d]);
rounds.push({round,accepted,score:next.score,operations});
if(accepted){best=candidate;report=next;plateau=0;}else plateau++;
if(plateau>=plateauRounds) break;
} catch(error) {
rounds.push({round,accepted:false,error:error.message});
if(++failures>=maxFailures) break;
}
}
return {baseline,report,candidate:best,rounds,published:false};
export async function optimize(input, propose, options={}) {
const result=await iterate(container(input), {
evaluate:audit,
propose:({candidate,report,round})=>propose({container:candidate,audit:report,round}),
apply:applyOperations,
accept:(before,after)=>dominates(before,after)&&after.criticalCount<=before.criticalCount
},options);
return {...result,rounds:result.rounds.map(({change,...round})=>change===undefined?round:{...round,operations:change})};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,8 @@ import { resolve, join, dirname } from 'node:path';
import { spawn } from 'node:child_process';
import { container, fingerprint, audit, optimize, hash } from './audit.mjs';

export async function atomic(path, data) {
await fs.mkdir(dirname(path),{recursive:true,mode:0o700});
const tmp=`${path}.${process.pid}.tmp`;
await fs.writeFile(tmp,typeof data==='string'?data:JSON.stringify(data,null,2)+'\n',{mode:0o600});
await fs.rename(tmp,path);
}
export async function readJSON(path, fallback) {
try{return JSON.parse(await fs.readFile(path,'utf8'));}catch(e){if(e.code==='ENOENT'&&fallback!==undefined)return fallback;throw e;}
}
export function command(argv, input='', {timeoutMs=120000,cwd,env=process.env}={}) {
if(!Array.isArray(argv)||!argv.length||argv.some(a=>typeof a!=='string')) throw Error('Command must be an argv array');
return new Promise((res,rej)=>{
const child=spawn(argv[0],argv.slice(1),{cwd,env,shell:false,stdio:['pipe','pipe','pipe'],detached:process.platform!=='win32'});
let out='',size=0,done=false;
const kill=()=>{try{if(process.platform!=='win32')process.kill(-child.pid,'SIGKILL');else child.kill('SIGKILL');}catch{}};
const end=(error)=>{if(done)return;done=true;clearTimeout(timer);error?rej(error):res(out.trim());};
const timer=setTimeout(()=>{kill();end(Error('Command timed out'));},timeoutMs);
child.stdout.on('data',chunk=>{size+=chunk.length;if(size>5_000_000){kill();end(Error('Command output limit exceeded'));}else out+=chunk;});
child.stderr.on('data',()=>{}); // May contain credentials or raw client data; never mirror it to logs.
child.stdin.on('error',()=>{});
child.on('error',()=>end(Error('Command could not start; check executable and host configuration')));
child.on('close',code=>end(code===0?null:Error(`Command failed with exit code ${code}`)));
child.stdin.end(input);
});
}
export { atomic, readJSON, command } from './shared/io.mjs';
import { atomic, readJSON, command } from './shared/io.mjs';
export async function loadConfig(file) {
const config=await readJSON(file),base=dirname(resolve(file));
if(!config.source||!['file','gtm'].includes(config.source.type))throw Error('source.type must be file or gtm');
Expand Down Expand Up @@ -92,11 +69,14 @@ export function markdown(report) {
const escape=s=>String(s).replace(/[\r\n|]/g,' ');
return `# GTM configuration audit\n\n${report.scope}\n\nScore: ${report.score}/100\n\n`+
report.findings.map(f=>`- **${f.severity}** ${escape(f.kind)} ${escape(f.id)} (${escape(f.name)}): ${escape(f.message)}`).join('\n')+
'\n\n## Not verified\n\n'+report.skipped.map(s=>`- ${s}`).join('\n')+'\n';
'\n\n## Configuration drift\n\n'+(report.drift?.detected===null?'Initial snapshot; no prior baseline.':report.drift?.detected?'Configuration changed since the prior snapshot.':'No configuration change detected.')+' This is not a live tracking correctness test.\n\n## Not verified\n\n'+report.skipped.map(s=>`- ${s}`).join('\n')+'\n';
}
export async function runSnapshot(config,snapshot,{propose}={}) {
const id=fingerprint(snapshot),folder=join(config.stateDir,'runs',`${Date.now()}-${id.slice(0,12)}`);
const report=audit(snapshot);
const previous=await readJSON(join(config.stateDir,'latest.json'),null);
const previousReport=previous?await readJSON(join(previous.folder,'audit.json'),null):null;
report.drift={kind:'configuration',detected:previous?previous.fingerprint!==id:null,qualityRegressed:previousReport?Object.keys(report.dimensions).some(key=>report.dimensions[key]<previousReport.dimensions[key]):null,meaning:'A changed configuration is drift; it does not by itself prove broken tracking.'};
await atomic(join(folder,'snapshot.json'),snapshot);
await atomic(join(folder,'audit.json'),report);await atomic(join(folder,'audit.md'),markdown(report));
await atomic(join(folder,'questions.md'),'# Workshop questions\n\n'+report.findings.slice(0,3).map((f,i)=>`${i+1}. How should I investigate ${f.kind} ${f.id}: ${f.message}?`).join('\n')+'\n');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Shared mechanics; adapters own evaluation, permissible changes and acceptance.
export async function iterate(input, adapter, {maxRounds=5,maxFailures=2,plateauRounds=2}={}) {
for(const n of [maxRounds,maxFailures,plateauRounds])if(!Number.isInteger(n)||n<1||n>30)throw Error('Loop bounds must be integers from 1 to 30');
let best=structuredClone(input),report=await adapter.evaluate(best),failures=0,plateau=0;
const baseline=structuredClone(report),rounds=[];
for(let round=1;round<=maxRounds;round++) {
try {
const change=await adapter.propose({candidate:structuredClone(best),report:structuredClone(report),round});
const candidate=await adapter.apply(structuredClone(best),change),next=await adapter.evaluate(candidate);
const accepted=await adapter.accept(report,next);
rounds.push({round,accepted:!!accepted,score:next.score,change});
if(accepted){best=candidate;report=next;plateau=0;}else plateau++;
if(plateau>=plateauRounds)break;
}catch(error){rounds.push({round,accepted:false,error:error.message});if(++failures>=maxFailures)break;}
}
return {baseline,report,candidate:best,rounds,published:false};
}
export function dominates(before,after) {
if(!Number.isFinite(before.score)||!Number.isFinite(after.score)||after.score<=before.score)return false;
const keys=Object.keys(before.dimensions);
return keys.length===Object.keys(after.dimensions).length&&keys.every(k=>Object.hasOwn(after.dimensions,k)&&Number.isFinite(after.dimensions[k])&&after.dimensions[k]>=before.dimensions[k]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Adapted from GTM Autoresearch runner.mjs, codex/reuse-gtm-autoresearch.
import { promises as fs } from 'node:fs';
import { dirname } from 'node:path';
import { spawn } from 'node:child_process';
import { randomUUID, createHash } from 'node:crypto';
export const hash = value => createHash('sha256').update(typeof value === 'string' ? value : JSON.stringify(value)).digest('hex');
export async function atomic(path, data) {
await fs.mkdir(dirname(path), {recursive:true, mode:0o700});
const tmp = `${path}.${randomUUID()}.tmp`;
const mode=await fs.stat(path).then(s=>s.mode&0o777,e=>{if(e.code==='ENOENT')return 0o600;throw e;});
try { await fs.writeFile(tmp, typeof data === 'string' ? data : JSON.stringify(data,null,2)+'\n', {mode}); await fs.chmod(tmp,mode); await fs.rename(tmp,path); }
finally { await fs.rm(tmp,{force:true}); }
}
export async function readJSON(path, fallback) {
try { return JSON.parse(await fs.readFile(path,'utf8')); }
catch(e) { if(e.code==='ENOENT' && fallback!==undefined)return fallback; throw e; }
}
export function command(argv, input='', {timeoutMs=120000,cwd,env=process.env}={}) {
if(!Array.isArray(argv)||!argv.length||argv.some(a=>typeof a!=='string'))throw Error('Runner command must be an argv array');
return new Promise((resolve,reject)=>{
const child=spawn(argv[0],argv.slice(1),{cwd,env,shell:false,stdio:['pipe','pipe','pipe'],detached:process.platform!=='win32'});
let output='',bytes=0,done=false;
const kill=()=>{try{process.platform==='win32'?child.kill('SIGKILL'):process.kill(-child.pid,'SIGKILL');}catch{}};
const finish=error=>{if(done)return;done=true;clearTimeout(timer);error?reject(error):resolve(output.trim());};
const timer=setTimeout(()=>{kill();finish(Error('Runner timed out'));},timeoutMs);
child.stdout.on('data',chunk=>{bytes+=chunk.length;if(bytes>5_000_000){kill();finish(Error('Runner output exceeds 5 MB'));}else output+=chunk;});
child.stderr.on('data',()=>{});
child.stdin.on('error',()=>{});
child.on('error',()=>finish(Error('Runner could not start; check its executable')));
child.on('close',code=>finish(code===0?null:Error(`Runner failed with exit code ${code}`)));
child.stdin.end(input);
});
}
export async function locked(folder, fn) {
await fs.mkdir(folder,{recursive:true,mode:0o700});
const path=folder+'/lock.json';
const handle=await fs.open(path,'wx',0o600).catch(e=>{if(e.code==='EEXIST')throw Error('Workspace busy; inspect lock.json before recovering a stopped process');throw e;});
await handle.writeFile(JSON.stringify({pid:process.pid}));await handle.close();
try{return await fn();}finally{await fs.unlink(path);}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { iterate, dominates } from './shared/core.mjs';
import { createHash } from 'node:crypto';

export const groups = { tag: 'tagId', trigger: 'triggerId', variable: 'variableId', folder: 'folderId' };
Expand Down Expand Up @@ -117,23 +118,12 @@ export function applyOperations(input, operations) {
return container(c);
}

export async function optimize(input, propose, {maxRounds=5, maxFailures=2, plateauRounds=2}={}) {
for (const n of [maxRounds,maxFailures,plateauRounds]) if (!Number.isInteger(n)||n<1||n>30) throw Error('Loop bounds must be integers from 1 to 30');
let best=container(input), report=audit(best), failures=0, plateau=0;
const baseline=report, rounds=[];
for(let round=1;round<=maxRounds;round++) {
try {
const operations=await propose({container:structuredClone(best),audit:structuredClone(report),round});
const candidate=applyOperations(best,operations), next=audit(candidate);
// No dimension may regress, even when the aggregate score rises.
const accepted=next.score>report.score && next.criticalCount<=report.criticalCount && dimensions.every(d=>next.dimensions[d]>=report.dimensions[d]);
rounds.push({round,accepted,score:next.score,operations});
if(accepted){best=candidate;report=next;plateau=0;}else plateau++;
if(plateau>=plateauRounds) break;
} catch(error) {
rounds.push({round,accepted:false,error:error.message});
if(++failures>=maxFailures) break;
}
}
return {baseline,report,candidate:best,rounds,published:false};
export async function optimize(input, propose, options={}) {
const result=await iterate(container(input), {
evaluate:audit,
propose:({candidate,report,round})=>propose({container:candidate,audit:report,round}),
apply:applyOperations,
accept:(before,after)=>dominates(before,after)&&after.criticalCount<=before.criticalCount
},options);
return {...result,rounds:result.rounds.map(({change,...round})=>change===undefined?round:{...round,operations:change})};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,8 @@ import { resolve, join, dirname } from 'node:path';
import { spawn } from 'node:child_process';
import { container, fingerprint, audit, optimize, hash } from './audit.mjs';

export async function atomic(path, data) {
await fs.mkdir(dirname(path),{recursive:true,mode:0o700});
const tmp=`${path}.${process.pid}.tmp`;
await fs.writeFile(tmp,typeof data==='string'?data:JSON.stringify(data,null,2)+'\n',{mode:0o600});
await fs.rename(tmp,path);
}
export async function readJSON(path, fallback) {
try{return JSON.parse(await fs.readFile(path,'utf8'));}catch(e){if(e.code==='ENOENT'&&fallback!==undefined)return fallback;throw e;}
}
export function command(argv, input='', {timeoutMs=120000,cwd,env=process.env}={}) {
if(!Array.isArray(argv)||!argv.length||argv.some(a=>typeof a!=='string')) throw Error('Command must be an argv array');
return new Promise((res,rej)=>{
const child=spawn(argv[0],argv.slice(1),{cwd,env,shell:false,stdio:['pipe','pipe','pipe'],detached:process.platform!=='win32'});
let out='',size=0,done=false;
const kill=()=>{try{if(process.platform!=='win32')process.kill(-child.pid,'SIGKILL');else child.kill('SIGKILL');}catch{}};
const end=(error)=>{if(done)return;done=true;clearTimeout(timer);error?rej(error):res(out.trim());};
const timer=setTimeout(()=>{kill();end(Error('Command timed out'));},timeoutMs);
child.stdout.on('data',chunk=>{size+=chunk.length;if(size>5_000_000){kill();end(Error('Command output limit exceeded'));}else out+=chunk;});
child.stderr.on('data',()=>{}); // May contain credentials or raw client data; never mirror it to logs.
child.stdin.on('error',()=>{});
child.on('error',()=>end(Error('Command could not start; check executable and host configuration')));
child.on('close',code=>end(code===0?null:Error(`Command failed with exit code ${code}`)));
child.stdin.end(input);
});
}
export { atomic, readJSON, command } from './shared/io.mjs';
import { atomic, readJSON, command } from './shared/io.mjs';
export async function loadConfig(file) {
const config=await readJSON(file),base=dirname(resolve(file));
if(!config.source||!['file','gtm'].includes(config.source.type))throw Error('source.type must be file or gtm');
Expand Down Expand Up @@ -92,11 +69,14 @@ export function markdown(report) {
const escape=s=>String(s).replace(/[\r\n|]/g,' ');
return `# GTM configuration audit\n\n${report.scope}\n\nScore: ${report.score}/100\n\n`+
report.findings.map(f=>`- **${f.severity}** ${escape(f.kind)} ${escape(f.id)} (${escape(f.name)}): ${escape(f.message)}`).join('\n')+
'\n\n## Not verified\n\n'+report.skipped.map(s=>`- ${s}`).join('\n')+'\n';
'\n\n## Configuration drift\n\n'+(report.drift?.detected===null?'Initial snapshot; no prior baseline.':report.drift?.detected?'Configuration changed since the prior snapshot.':'No configuration change detected.')+' This is not a live tracking correctness test.\n\n## Not verified\n\n'+report.skipped.map(s=>`- ${s}`).join('\n')+'\n';
}
export async function runSnapshot(config,snapshot,{propose}={}) {
const id=fingerprint(snapshot),folder=join(config.stateDir,'runs',`${Date.now()}-${id.slice(0,12)}`);
const report=audit(snapshot);
const previous=await readJSON(join(config.stateDir,'latest.json'),null);
const previousReport=previous?await readJSON(join(previous.folder,'audit.json'),null):null;
report.drift={kind:'configuration',detected:previous?previous.fingerprint!==id:null,qualityRegressed:previousReport?Object.keys(report.dimensions).some(key=>report.dimensions[key]<previousReport.dimensions[key]):null,meaning:'A changed configuration is drift; it does not by itself prove broken tracking.'};
await atomic(join(folder,'snapshot.json'),snapshot);
await atomic(join(folder,'audit.json'),report);await atomic(join(folder,'audit.md'),markdown(report));
await atomic(join(folder,'questions.md'),'# Workshop questions\n\n'+report.findings.slice(0,3).map((f,i)=>`${i+1}. How should I investigate ${f.kind} ${f.id}: ${f.message}?`).join('\n')+'\n');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Shared mechanics; adapters own evaluation, permissible changes and acceptance.
export async function iterate(input, adapter, {maxRounds=5,maxFailures=2,plateauRounds=2}={}) {
for(const n of [maxRounds,maxFailures,plateauRounds])if(!Number.isInteger(n)||n<1||n>30)throw Error('Loop bounds must be integers from 1 to 30');
let best=structuredClone(input),report=await adapter.evaluate(best),failures=0,plateau=0;
const baseline=structuredClone(report),rounds=[];
for(let round=1;round<=maxRounds;round++) {
try {
const change=await adapter.propose({candidate:structuredClone(best),report:structuredClone(report),round});
const candidate=await adapter.apply(structuredClone(best),change),next=await adapter.evaluate(candidate);
const accepted=await adapter.accept(report,next);
rounds.push({round,accepted:!!accepted,score:next.score,change});
if(accepted){best=candidate;report=next;plateau=0;}else plateau++;
if(plateau>=plateauRounds)break;
}catch(error){rounds.push({round,accepted:false,error:error.message});if(++failures>=maxFailures)break;}
}
return {baseline,report,candidate:best,rounds,published:false};
}
export function dominates(before,after) {
if(!Number.isFinite(before.score)||!Number.isFinite(after.score)||after.score<=before.score)return false;
const keys=Object.keys(before.dimensions);
return keys.length===Object.keys(after.dimensions).length&&keys.every(k=>Object.hasOwn(after.dimensions,k)&&Number.isFinite(after.dimensions[k])&&after.dimensions[k]>=before.dimensions[k]);
}
Loading