@yunhozz/arc 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.sha256 +75 -69
- package/claude/agents/arc-implementer.md +8 -23
- package/claude/agents/arc-planner.md +14 -23
- package/claude/agents/arc-qa.md +9 -19
- package/claude/agents/arc-reviewer.md +11 -20
- package/claude/agents/arc-scouter.md +10 -17
- package/claude/skill/SKILL.md +8 -8
- package/claude/skill/references/arc-visual-companion.md +3 -3
- package/claude/skill/references/brainstorming.md +60 -38
- package/claude/skill/references/commit.md +6 -6
- package/claude/skill/references/coordination.md +18 -6
- package/claude/skill/references/git-recovery.md +2 -2
- package/claude/skill/references/handoff-prep.md +7 -5
- package/claude/skill/references/handoff.md +62 -9
- package/claude/skill/references/limits.md +5 -5
- package/claude/skill/references/planning.md +18 -12
- package/claude/skill/references/scripts/start-server.sh +7 -0
- package/claude/skill/references/superpowers-provenance.json +4 -4
- package/claude/skill/references/verification.md +6 -4
- package/claude/skill/runtime/bootstrap.mjs +2 -2
- package/claude/skill/runtime/cooperative-schema.mjs +32 -1
- package/claude/skill/runtime/cooperative.mjs +68 -13
- package/claude/skill/runtime/evaluate.mjs +85 -6
- package/claude/skill/runtime/git-evidence.mjs +98 -0
- package/claude/skill/runtime/operations.mjs +2 -2
- package/claude/skill/runtime/role-policy.json +6 -6
- package/claude/skill/runtime/store.mjs +10 -0
- package/claude/skill/runtime/target-preparation.mjs +110 -0
- package/claude/skill/runtime/verification.mjs +6 -4
- package/codex/agents/arc-implementer.toml +10 -25
- package/codex/agents/arc-planner.toml +12 -19
- package/codex/agents/arc-qa.toml +7 -15
- package/codex/agents/arc-reviewer.toml +9 -16
- package/codex/agents/arc-scouter.toml +8 -13
- package/codex/skill/SKILL.md +8 -8
- package/codex/skill/references/arc-visual-companion.md +3 -3
- package/codex/skill/references/brainstorming.md +60 -38
- package/codex/skill/references/commit.md +6 -6
- package/codex/skill/references/coordination.md +18 -6
- package/codex/skill/references/git-recovery.md +2 -2
- package/codex/skill/references/handoff-prep.md +5 -5
- package/codex/skill/references/handoff-worktree.md +4 -4
- package/codex/skill/references/handoff.md +61 -8
- package/codex/skill/references/limits.md +5 -5
- package/codex/skill/references/planning.md +19 -13
- package/codex/skill/references/scripts/start-server.sh +7 -0
- package/codex/skill/references/superpowers-provenance.json +4 -4
- package/codex/skill/references/verification.md +6 -4
- package/codex/skill/runtime/bootstrap.mjs +2 -2
- package/codex/skill/runtime/cooperative-schema.mjs +32 -1
- package/codex/skill/runtime/cooperative.mjs +68 -13
- package/codex/skill/runtime/evaluate.mjs +85 -6
- package/codex/skill/runtime/git-evidence.mjs +98 -0
- package/codex/skill/runtime/operations.mjs +2 -2
- package/codex/skill/runtime/role-policy.json +6 -6
- package/codex/skill/runtime/store.mjs +10 -0
- package/codex/skill/runtime/target-preparation.mjs +110 -0
- package/codex/skill/runtime/verification.mjs +6 -4
- package/lib/installer/manifest.mjs +6 -2
- package/lib/installer/plan.mjs +3 -1
- package/lib/installer/runtime-assets.mjs +1 -0
- package/lib/policy/role-policy.json +6 -6
- package/lib/prompts/agent-fragments.json +254 -0
- package/lib/prompts/render.mjs +199 -0
- package/lib/runtime/bootstrap.mjs +2 -2
- package/lib/runtime/cooperative-schema.mjs +32 -1
- package/lib/runtime/cooperative.mjs +68 -13
- package/lib/runtime/evaluate.mjs +85 -6
- package/lib/runtime/git-evidence.mjs +98 -0
- package/lib/runtime/operations.mjs +2 -2
- package/lib/runtime/store.mjs +10 -0
- package/lib/runtime/target-preparation.mjs +110 -0
- package/lib/runtime/verification.mjs +6 -4
- package/lib/tui/setup.mjs +3 -2
- package/package.json +1 -1
package/lib/runtime/store.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {verifiedCooperativeReconciliation} from './acceptance.mjs';
|
|
2
2
|
import {verifiedReconciliation} from './gate.mjs';
|
|
3
|
+
import {applyTargetPreparationTransition} from './target-preparation.mjs';
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import crypto from 'node:crypto';
|
|
@@ -82,3 +83,12 @@ export function applyCooperativeCommitTransition({state,decision}){
|
|
|
82
83
|
next.target=structuredClone(transition.target);next.commits.push(structuredClone(transition.commit));next.tasks.find(t=>t.id===transition.commit.taskId).status='committed';
|
|
83
84
|
return next;
|
|
84
85
|
}
|
|
86
|
+
|
|
87
|
+
/** Target preparation changes identity only after re-evaluating its captured observation. */
|
|
88
|
+
export function applyCooperativeTargetTransition({state,decision,messageRef}){
|
|
89
|
+
const next=applyTargetPreparationTransition({state,decision});
|
|
90
|
+
if(next.targetVersion!==state.targetVersion+1)throw Error('Verified target preparation transition required');
|
|
91
|
+
next.targetHistory.push({target:structuredClone(state.target),revision:state.revision+1,source:'local-observation',kind:'target-preparation-source'});
|
|
92
|
+
next.targetTransitions.at(-1).messageRef=messageRef;
|
|
93
|
+
return next;
|
|
94
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const same=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
|
|
2
|
+
const text=value=>typeof value==='string'&&value.length>0;
|
|
3
|
+
const relative=value=>text(value)&&!/[\\\x00\r\n:]/.test(value)&&!value.startsWith('/')&&!value.split('/').some(part=>!part||part==='.'||part==='..');
|
|
4
|
+
|
|
5
|
+
export async function readTargetPreparationEvidence({state,targetIntent,destination,observe=observeCooperativeTarget,readChanges=readCooperativeChanges,gitRunner}={}){
|
|
6
|
+
const sourceTarget=await observe(state.target.realpath);
|
|
7
|
+
const snapshot=await readChanges({state:{...state,target:sourceTarget},...(gitRunner?{gitRunner}:{})});
|
|
8
|
+
const stage=state.plannerLedger?.stages?.find(s=>s.id===targetIntent?.firstStageId);
|
|
9
|
+
const source={target:sourceTarget,fingerprint:snapshot.final,snapshot:snapshot.transitionSnapshot,preserved:!!targetIntent?.sourceSnapshot&&same(snapshot.transitionSnapshot,targetIntent.sourceSnapshot),inputVersion:stage?.inputVersion,candidate:targetIntent?.candidate,base:targetIntent?.base};
|
|
10
|
+
if(!destination)return {source};
|
|
11
|
+
const target=await observe(destination.realpath),tasks=state.tasks.map(task=>({...task,paths:task.paths.map(p=>{if(!within(state.target.realpath,p))throw Error('Task path escapes source');return path.join(target.realpath,path.relative(state.target.realpath,p));})})),destinationSnapshot=await readChanges({state:{...state,target,tasks},...(gitRunner?{gitRunner}:{})}),result={target,snapshot:destinationSnapshot.transitionSnapshot};
|
|
12
|
+
source.preserved=source.preserved&&same(destinationSnapshot.transitionSnapshot,targetIntent.sourceSnapshot);
|
|
13
|
+
if(target.realpath!==state.target.realpath){
|
|
14
|
+
const query=await (gitRunner??defaultRunner)({cwd:sourceTarget.realpath,args:['worktree','list','--porcelain','-z']});
|
|
15
|
+
if(query?.status!==0||query.error||!Buffer.isBuffer(query.stdout))throw Error('Worktree registration query failed');
|
|
16
|
+
const raw=new TextDecoder('utf-8',{fatal:true}).decode(query.stdout);if(raw&&!raw.endsWith('\0\0'))throw Error('Incomplete worktree registration');
|
|
17
|
+
const entries=raw?raw.slice(0,-2).split('\0\0').map(entry=>entry.split('\0')):[];
|
|
18
|
+
result.registered=entries.some(entry=>entry.includes('worktree '+target.realpath)&&entry.includes('HEAD '+target.head)&&entry.includes('branch refs/heads/'+target.branch));
|
|
19
|
+
const attachment=state.records?.find(r=>r.kind==='note'&&r.messageRef===destination.attachmentRef&&r.targetVersion===state.targetVersion&&r.source==='operator-attested'&&r.data?.type==='worktree-attachment'&&r.data.realpath===target.realpath&&r.data.gitDir===target.gitDir&&r.data.gitCommonDir===target.gitCommonDir);
|
|
20
|
+
if(attachment)result.attachmentRef=attachment.messageRef;
|
|
21
|
+
}
|
|
22
|
+
if(destination.path!==undefined){
|
|
23
|
+
if(!relative(destination.path))throw Error('Invalid destination path');
|
|
24
|
+
const filename=path.join(target.realpath,...destination.path.split('/'));
|
|
25
|
+
if(!within(target.realpath,filename))throw Error('Destination path escapes workspace');
|
|
26
|
+
await inspectPath(filename);const handle=await fs.open(filename,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);
|
|
27
|
+
let bytes,after;
|
|
28
|
+
try{const before=await handle.stat();if(!before.isFile())throw Error('Destination must be a regular file');bytes=await handle.readFile();after=await handle.stat();const named=await fs.lstat(filename);if(before.dev!==after.dev||before.ino!==after.ino||before.size!==after.size||before.mtimeMs!==after.mtimeMs||before.mode!==after.mode||after.dev!==named.dev||after.ino!==named.ino)throw Error('Destination changed while reading');}finally{await handle.close();}
|
|
29
|
+
await inspectPath(filename);if(!within(target.realpath,await fs.realpath(filename)))throw Error('Destination path escapes workspace');
|
|
30
|
+
result.document={path:destination.path,hash:'sha256:'+createHash('sha256').update(bytes).digest('hex'),mode:0o100000|(after.mode&0o777),verification:{status:'verified',source:'local-bytes'}};
|
|
31
|
+
}
|
|
32
|
+
return {source,destination:result};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateTargetPreparationRecord(kind,data){
|
|
36
|
+
if(!data||data.type!==kind||!['intent','checkpoint','transition','destination'].includes(kind))throw Error('Invalid target preparation type');
|
|
37
|
+
if(kind==='intent'&&(!text(data.id)||!text(data.workflowId)||!data.sourceTarget||!Number.isSafeInteger(data.sourceTargetVersion)||!text(data.selectionRef)||!['current','branch','worktree'].includes(data.kind)||!text(data.candidate)||!text(data.base)||!text(data.firstStageId)||!Array.isArray(data.documents)||!data.documents.length||data.documents.some(d=>!['spec','plan'].includes(d.kind)||!relative(d.path))||new Set(data.documents.map(d=>d.kind)).size!==data.documents.length))throw Error('Invalid target preparation intent');
|
|
38
|
+
if(kind==='intent'&&(!Number.isSafeInteger(data.sourceTargetVersion)||data.sourceTargetVersion<1||!['realpath','gitCommonDir','gitDir'].every(k=>typeof data.sourceTarget[k]==='string'&&path.isAbsolute(data.sourceTarget[k])&&path.normalize(data.sourceTarget[k])===data.sourceTarget[k])||!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(data.sourceTarget.head)||!(data.sourceTarget.branch===null||text(data.sourceTarget.branch))||!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(data.base)||new Set(data.documents.map(d=>d.path)).size!==data.documents.length))throw Error('Invalid target preparation intent identity');
|
|
39
|
+
if(kind==='checkpoint'&&(!text(data.intentId)||!text(data.stageId)||!text(data.resultRef)||!text(data.selfReviewRef)))throw Error('Invalid target preparation checkpoint');
|
|
40
|
+
if(kind==='transition'&&(!text(data.intentId)||!text(data.checkpointRef)||!Number.isSafeInteger(data.expectedRevision)||data.expectedRevision<0))throw Error('Invalid target preparation transition');
|
|
41
|
+
if(kind==='destination'&&(!text(data.intentId)||!text(data.transitionRef)||!text(data.stageId)||!text(data.resultRef)||!text(data.selfReviewRef)||!relative(data.path)||!/^sha256:[a-f0-9]{64}$/.test(data.hash)||![0o100644,0o100755].includes(data.mode)||!text(data.reviewRef)||!text(data.approvalRef)||!Number.isSafeInteger(data.targetVersion)||data.targetVersion<1))throw Error('Invalid target preparation destination');
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function evaluateTargetPreparation({state,request,evidence}={}){
|
|
46
|
+
const hold=reason=>({decision:'hold',ruleIds:['cooperative.target-preparation'],reason});
|
|
47
|
+
try{validateTargetPreparationRecord(request?.type,request);}catch{return hold('Invalid target preparation record');}
|
|
48
|
+
if(request.type==='intent'){
|
|
49
|
+
if(state.targetIntents?.some(intent=>!state.targetTransitions?.some(transition=>transition.intentId===intent.id)))return hold('Existing target preparation intent is still active');
|
|
50
|
+
const approval=state.records?.find(r=>r.kind==='approval'&&r.messageRef===request.approvalRef&&r.source==='operator-attested'&&r.actor?.role==='orchestrator'&&r.targetVersion===state.targetVersion&&r.scopeVersion===state.scopeVersion);
|
|
51
|
+
const approved=approval?.data?.targetPreparation;
|
|
52
|
+
if(!['Bounded','Architectural'].includes(request.depth)||(request.depth==='Architectural'&&(!request.documents.some(d=>d.kind==='spec')||!request.documents.some(d=>d.kind==='plan'))))return hold('Approved depth requires its complete document set');
|
|
53
|
+
if(!text(request.approvalRef)||!text(request.depth)||['depth','selectionRef','kind','candidate','base','workflowId','firstStageId','sourceTargetVersion'].some(k=>approved?.[k]!==request[k])||!same(approved?.sourceTarget,request.sourceTarget)||!same(approved?.documents,request.documents)||request.workflowId!==state.workflowId||request.sourceTargetVersion!==state.targetVersion||!same(request.sourceTarget,state.target)||!same(evidence?.source?.target,state.target)||!text(evidence?.source?.fingerprint)||!state.selections?.some(s=>s.messageRef===request.selectionRef&&s.targetVersion===state.targetVersion)||state.targetIntents?.some(i=>i.id===request.id)||request.base!==state.target.head)return hold('Current approval, document set, source, explicit choice and preservation snapshot required');
|
|
54
|
+
return {decision:'allow',ruleIds:['cooperative.target-preparation'],proposedTransition:{intent:{...structuredClone(request),scopeVersion:state.scopeVersion,sourceFingerprint:evidence.source.fingerprint,...(evidence.source.snapshot?{sourceSnapshot:structuredClone(evidence.source.snapshot)}:{}),source:'operator-attested'}}};
|
|
55
|
+
}
|
|
56
|
+
if(request.type==='transition'){
|
|
57
|
+
const intent=state?.targetIntents?.find(i=>i.id===request.intentId),checkpoint=state?.targetCheckpoints?.find(c=>c.intentId===request.intentId&&c.messageRef===request.checkpointRef);
|
|
58
|
+
const source=evidence?.source,destination=evidence?.destination?.target;
|
|
59
|
+
if(state.plannerLedger?.stages?.some(s=>s.pending||s.calls?.some(c=>['reserved','unknown'].includes(c.status))))return hold('Pending or unknown Planner execution');
|
|
60
|
+
const stage=state.plannerLedger?.stages?.find(s=>s.id===intent?.firstStageId),result=stage?.result,chat=result?.chatDraft;
|
|
61
|
+
if(!intent||intent.scopeVersion!==state.scopeVersion||stage?.scopeVersion!==state.scopeVersion||stage.targetVersion!==state.targetVersion||!state.records?.some(r=>r.kind==='approval'&&r.messageRef===intent.approvalRef&&r.scopeVersion===state.scopeVersion&&r.targetVersion===state.targetVersion&&r.actor?.role==='orchestrator'&&r.source==='operator-attested'))return hold('Current scope approval and source stage required');
|
|
62
|
+
if(!stage||stage.targetIntentId!==intent.id||checkpoint?.stageId!==stage.id||checkpoint.resultRef!==result?.ref||checkpoint.selfReviewRef!==chat?.selfReviewRef||checkpoint.candidate!==intent.candidate||checkpoint.base!==intent.base||checkpoint.inputVersion!==stage.inputVersion||result.status!=='succeeded'||chat?.intentId!==intent.id||chat.complete!==true||!text(chat.draftRef)||!stage.calls?.some(c=>c.requestId===result.requestId&&c.status==='succeeded'))return hold('Checkpoint source result changed');
|
|
63
|
+
if(!intent||!checkpoint||state.targetTransitions?.some(t=>t.intentId===intent.id)||request.expectedRevision!==state.revision||intent.sourceTargetVersion!==state.targetVersion||!same(intent.sourceTarget,state.target)||!same(evidence?.versions,state.versions)||!source?.preserved||!destination||source.target?.realpath!==state.target.realpath||source.target?.gitCommonDir!==state.target.gitCommonDir||source.target?.gitDir!==state.target.gitDir||destination.gitCommonDir!==state.target.gitCommonDir||state.unresolved?.some(u=>!u.resolvedBy)||state.assignments?.some(a=>a.status==='active')||state.intents?.some(i=>['prepared','unknown','mismatch'].includes(i.status)))return hold('Current checkpoint, preservation and independent target evidence required');
|
|
64
|
+
if(intent.kind==='branch'&&!(destination.realpath===state.target.realpath&&destination.gitDir===state.target.gitDir&&destination.branch===intent.candidate&&destination.head===intent.base&&source.target.branch===intent.candidate&&source.target.head===intent.base))return hold('Branch target differs from intent');
|
|
65
|
+
if(intent.kind==='current'&&(!same(destination,state.target)||destination.branch!==intent.candidate||destination.head!==intent.base))return hold('Current target changed');
|
|
66
|
+
if(intent.kind==='worktree'&&state.tasks?.some(task=>task.paths?.length))return hold('Worktree transition with source-bound task paths is unsupported');
|
|
67
|
+
if(intent.kind==='worktree'&&!(same(source.target,state.target)&&destination.realpath!==state.target.realpath&&destination.gitDir!==state.target.gitDir&&destination.branch===intent.candidate&&destination.head===intent.base&&evidence.destination.registered===true&&text(evidence.destination.attachmentRef)&&state.records?.some(r=>r.kind==='note'&&r.messageRef===evidence.destination.attachmentRef&&r.targetVersion===state.targetVersion&&r.source==='operator-attested'&&r.data?.type==='worktree-attachment'&&r.data.realpath===destination.realpath&&r.data.gitDir===destination.gitDir&&r.data.gitCommonDir===destination.gitCommonDir)))return hold('Worktree registration and attachment required');
|
|
68
|
+
return {decision:'allow',ruleIds:['cooperative.target-preparation'],proposedTransition:{target:structuredClone(destination),intentId:intent.id,checkpointRef:request.checkpointRef,sourceTargetVersion:state.targetVersion,expectedRevision:state.revision,validation:{request:structuredClone(request),evidence:structuredClone(evidence)}}};
|
|
69
|
+
}
|
|
70
|
+
if(request.type==='destination'){
|
|
71
|
+
const intent=state.targetIntents?.find(i=>i.id===request.intentId),document=intent?.documents?.find(d=>d.path===request.path),transition=state.targetTransitions?.find(t=>t.intentId===request.intentId&&t.messageRef===request.transitionRef&&t.targetVersion===state.targetVersion),checkpoint=state.targetCheckpoints?.find(c=>c.intentId===request.intentId&&c.messageRef===transition?.checkpointRef),stage=state.plannerLedger?.stages?.find(s=>s.id===request.stageId),chat=stage?.result?.chatDraft,observed=evidence?.destination?.document;
|
|
72
|
+
const review=state.records?.find(r=>r.kind==='planner-review'&&r.messageRef===request.reviewRef&&r.scopeVersion===state.scopeVersion&&r.targetVersion===state.targetVersion&&r.source==='operator-attested'),approval=state.records?.find(r=>r.kind==='approval'&&r.messageRef===request.approvalRef&&r.scopeVersion===state.scopeVersion&&r.targetVersion===state.targetVersion&&r.source==='operator-attested');
|
|
73
|
+
if(!intent||!document||intent.firstStageId!==request.stageId||!transition||!same(transition.target,state.target)||request.targetVersion!==state.targetVersion||!checkpoint||checkpoint.stageId!==request.stageId||checkpoint.resultRef!==request.resultRef||checkpoint.selfReviewRef!==request.selfReviewRef||stage?.targetIntentId!==intent.id||stage.result?.ref!==request.resultRef||stage.result.status!=='succeeded'||chat?.intentId!==intent.id||chat.complete!==true||chat.selfReviewRef!==request.selfReviewRef||!same(evidence?.destination?.target,state.target)||observed?.verification?.status!=='verified'||observed.verification.source!=='local-bytes'||observed.path!==request.path||observed.hash!==request.hash||observed.mode!==request.mode)return hold('Current transition, source attestation and observed destination bytes required');
|
|
74
|
+
if(stage.scopeVersion!==state.scopeVersion||review?.data?.resultRef!==request.resultRef||review.data.full!==true||review.data.complete!==true||review.data.hash!==request.hash||!Array.isArray(stage.criteria)||!Array.isArray(review.data.criteria)||review.data.criteria.length!==stage.criteria.length||new Set(review.data.criteria.map(c=>c.id)).size!==stage.criteria.length||review.data.criteria.some(c=>!stage.criteria.some(s=>s.id===c.id)||c.status!=='resolved'||!text(c.judgmentRef))||approval?.data?.kind!==document.kind||approval.data.contentHash!==request.hash||![request.path,path.join(state.target.realpath,request.path)].includes(approval.data.path))return hold('Current full review and document approval required');
|
|
75
|
+
return {decision:'allow',ruleIds:['cooperative.target-preparation'],proposedTransition:{destination:{...structuredClone(request),scopeVersion:state.scopeVersion,sourceLink:'operator-attested',verification:{status:'verified',source:'local-bytes'}}}};
|
|
76
|
+
}
|
|
77
|
+
if(request?.type!=='checkpoint')return hold('Unsupported target preparation request');
|
|
78
|
+
const intent=state?.targetIntents?.find(i=>i.id===request.intentId);
|
|
79
|
+
const stage=state?.plannerLedger?.stages?.find(s=>s.id===request.stageId);
|
|
80
|
+
const source=evidence?.source,result=stage?.result,chat=result?.chatDraft;
|
|
81
|
+
if(!intent||intent.scopeVersion!==state.scopeVersion||stage?.scopeVersion!==state.scopeVersion||stage.targetVersion!==state.targetVersion||!state.records?.some(r=>r.kind==='approval'&&r.messageRef===intent.approvalRef&&r.scopeVersion===state.scopeVersion&&r.targetVersion===state.targetVersion&&r.actor?.role==='orchestrator'&&r.source==='operator-attested'))return hold('Current scope approval and source stage required');
|
|
82
|
+
if(!intent||!stage||intent.firstStageId!==stage.id||stage.targetIntentId!==intent.id||intent.sourceTargetVersion!==state.targetVersion||!same(intent.sourceTarget,state.target)||!same(source?.target,state.target)||source.preserved!==true||source.inputVersion!==stage.inputVersion||source.candidate!==intent.candidate||source.base!==intent.base)return hold('Current source and intent evidence required');
|
|
83
|
+
if(stage.pending||result?.ref!==request.resultRef||result?.status!=='succeeded'||!stage.calls?.some(c=>c.requestId===result.requestId&&c.status==='succeeded')||stage.calls.some(c=>['reserved','unknown'].includes(c.status))||chat?.intentId!==intent.id||!chat.draftRef||!chat.selfReviewRef||chat.selfReviewRef!==request.selfReviewRef||chat.complete!==true)return hold('Complete confirmed chat draft and self-review required');
|
|
84
|
+
return {decision:'allow',ruleIds:['cooperative.target-preparation'],reason:'Chat checkpoint attested',proposedTransition:{checkpoint:{intentId:intent.id,stageId:stage.id,resultRef:result.ref,selfReviewRef:chat.selfReviewRef,candidate:intent.candidate,base:intent.base,inputVersion:stage.inputVersion}}};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function applyTargetPreparationTransition({state,decision}={}){
|
|
88
|
+
const next=structuredClone(state),transition=decision?.proposedTransition;
|
|
89
|
+
if(decision?.decision!=='allow'||!transition?.target||transition.sourceTargetVersion!==state.targetVersion||transition.expectedRevision!==state.revision||state.targetTransitions?.some(t=>t.intentId===transition.intentId))return next;
|
|
90
|
+
if(!transition.validation)return next;
|
|
91
|
+
const checked=evaluateTargetPreparation({state,...transition.validation});
|
|
92
|
+
if(checked.decision!=='allow'||!same(checked.proposedTransition,transition))return next;
|
|
93
|
+
const intent=state.targetIntents?.find(i=>i.id===transition.intentId),checkpoint=state.targetCheckpoints?.find(c=>c.intentId===transition.intentId&&c.messageRef===transition.checkpointRef);
|
|
94
|
+
if(!intent||!checkpoint||intent.sourceTargetVersion!==state.targetVersion||!same(intent.sourceTarget,state.target))return next;
|
|
95
|
+
next.target=structuredClone(transition.target);next.targetVersion++;
|
|
96
|
+
(next.targetTransitions??=[]).push({...structuredClone(transition),targetVersion:next.targetVersion});
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
import fs from 'node:fs/promises';
|
|
100
|
+
import {constants} from 'node:fs';
|
|
101
|
+
import path from 'node:path';
|
|
102
|
+
import {createHash} from 'node:crypto';
|
|
103
|
+
import {inspectPath,within,observeCooperativeTarget} from './scope.mjs';
|
|
104
|
+
import {readCooperativeChanges} from './verification.mjs';
|
|
105
|
+
import {spawnSync} from 'node:child_process';
|
|
106
|
+
function defaultRunner({cwd,args}){
|
|
107
|
+
const env=Object.fromEntries(Object.entries(process.env).filter(([key])=>!key.toUpperCase().startsWith('GIT_')));
|
|
108
|
+
Object.assign(env,{GIT_CONFIG_NOSYSTEM:'1',GIT_CONFIG_GLOBAL:process.platform==='win32'?'NUL':'/dev/null',GIT_OPTIONAL_LOCKS:'0',GIT_NO_REPLACE_OBJECTS:'1',GIT_NO_LAZY_FETCH:'1',GIT_TERMINAL_PROMPT:'0'});
|
|
109
|
+
return spawnSync('git',['--no-pager','-c','core.fsmonitor=false',...args],{cwd,env,encoding:'buffer',timeout:10000,maxBuffer:1024*1024});
|
|
110
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import {constants} from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {createHash} from 'node:crypto';
|
|
4
5
|
import {spawnSync} from 'node:child_process';
|
|
@@ -108,16 +109,17 @@ export async function readCooperativeChanges({state,gitRunner=defaultRunner}){
|
|
|
108
109
|
if(raw&&!raw.endsWith('\0'))throw Error('Incomplete verification inventory');
|
|
109
110
|
const index=await gitRunner({cwd:state.target.realpath,args:['ls-files','--stage','-z']});
|
|
110
111
|
if(index?.status!==0||index.error||!Buffer.isBuffer(index.stdout))throw Error('Verification index query failed');
|
|
111
|
-
const indexText=new TextDecoder('utf-8',{fatal:true}).decode(index.stdout),indexRows=[];
|
|
112
|
+
const indexText=new TextDecoder('utf-8',{fatal:true}).decode(index.stdout),indexRows=[],transitionIndex=[];
|
|
112
113
|
if(indexText&&!indexText.endsWith('\0'))throw Error('Incomplete verification index');
|
|
113
|
-
for(const entry of indexText?indexText.slice(0,-1).split('\0'):[]){const match=/^(100644|100755) ([a-f0-9]{40}|[a-f0-9]{64}) 0\t([\s\S]+)$/.exec(entry);if(!match)throw Error('Verification requires regular stage-0 index entries');indexRows.push({path:path.join(state.target.realpath,match[3]),entry});}
|
|
114
|
+
for(const entry of indexText?indexText.slice(0,-1).split('\0'):[]){const match=/^(100644|100755) ([a-f0-9]{40}|[a-f0-9]{64}) 0\t([\s\S]+)$/.exec(entry);if(!match||!match[3]||/[\\\x00\r\n:]/.test(match[3])||match[3].startsWith('/')||match[3].split('/').some(p=>!p||p==='.'||p==='..'))throw Error('Verification requires regular stage-0 index entries');indexRows.push({path:path.join(state.target.realpath,match[3]),entry});transitionIndex.push({path:match[3],mode:match[1],blob:match[2],stage:0});}
|
|
114
115
|
const names=[...new Set(raw?raw.slice(0,-1).split('\0'):[])].sort(),rows=[];
|
|
115
116
|
for(const name of names){
|
|
116
117
|
if(!name||name.includes('\\')||name.includes('\n')||name.includes('\r')||path.isAbsolute(name)||name.split('/').some(p=>!p||p==='.'||p==='..'))throw Error('Invalid verification path');
|
|
117
118
|
const file=path.join(state.target.realpath,...name.split('/'));
|
|
118
|
-
try{await inspectPath(file);const
|
|
119
|
+
try{await inspectPath(file);const handle=await fs.open(file,constants.O_RDONLY|constants.O_NOFOLLOW|constants.O_NONBLOCK);let stat,bytes;try{stat=await handle.stat();if(!stat.isFile())throw Error('Verification requires regular files');bytes=await handle.readFile();const after=await handle.stat(),named=await fs.lstat(file);if(stat.dev!==after.dev||stat.ino!==after.ino||stat.size!==after.size||stat.mtimeMs!==after.mtimeMs||stat.mode!==after.mode||after.dev!==named.dev||after.ino!==named.ino)throw Error('Verification file changed while reading');}finally{await handle.close();}await inspectPath(file);rows.push({path:file,mode:stat.mode&0o777,hash:'sha256:'+createHash('sha256').update(bytes).digest('hex')});}
|
|
119
120
|
catch(error){if(error.code!=='ENOENT')throw error;rows.push({path:file,deleted:true});}
|
|
120
121
|
}
|
|
121
122
|
const tasks={},taskSnapshots={};for(const task of state.tasks){if(!list(task.paths)||!task.paths.every(p=>absolute(p)&&within(state.target.realpath,p)))throw Error('Invalid task paths');taskSnapshots[task.id]={scope:'task',taskId:task.id,target:Object.fromEntries(Object.entries(state.target).filter(([key])=>key!=='head')),paths:task.paths,index:indexRows.filter(row=>task.paths.some(p=>within(p,row.path))),rows:rows.filter(row=>task.paths.some(p=>within(p,row.path)))};tasks[task.id]=fingerprintRequest(taskSnapshots[task.id]);}
|
|
122
|
-
|
|
123
|
+
const transitionSnapshot={index:transitionIndex.sort((a,b)=>a.path.localeCompare(b.path)),files:rows.map(row=>({...row,path:path.relative(state.target.realpath,row.path).split(path.sep).join('/')}))};
|
|
124
|
+
return {tasks,taskSnapshots,final:fingerprintRequest({scope:'final',target:state.target,index:indexRows,rows}),transitionSnapshot};
|
|
123
125
|
}
|
package/lib/tui/setup.mjs
CHANGED
|
@@ -52,8 +52,8 @@ export async function runSetup({ui,statuses,draft,kind,prepare,loadPreviewConfig
|
|
|
52
52
|
}
|
|
53
53
|
let selectedTarget=0;
|
|
54
54
|
while(true){const targetIndex=await ui.menu(kind==='preset'?'Change Preset':'Install / Update',['Codex','Claude'],{selected:selectedTarget,backRow:true});if(targetIndex===null)return null;selectedTarget=targetIndex;const target=['codex','claude'][targetIndex];
|
|
55
|
-
const settings=draft[target];let editing=true,selectedField=0;
|
|
56
|
-
while(editing){const labels=kind==='preset'?['Preset: '+presetLabel(settings.preset),'Apply','Back']:['Preset: '+presetLabel(settings.preset),'Harness language: '+languageLabel(settings.language),'Total subagents: '+settings.maxTotalSubagents,'Concurrent subagents: '+settings.maxConcurrentSubagents,'Apply global settings: '+!settings.skipGlobal,'Apply','Back'];const field=await ui.menu('Settings — '+target,labels,{selected:selectedField,separatorBefore:labels.length-2});if(field===null||field===labels.length-1)break;selectedField=field;
|
|
55
|
+
const settings=draft[target];let editing=true,selectedField=0,openPresetOnEntry=kind==='preset';
|
|
56
|
+
while(editing){const labels=kind==='preset'?['Preset: '+presetLabel(settings.preset),'Apply','Back']:['Preset: '+presetLabel(settings.preset),'Harness language: '+languageLabel(settings.language),'Total subagents: '+settings.maxTotalSubagents,'Concurrent subagents: '+settings.maxConcurrentSubagents,'Apply global settings: '+!settings.skipGlobal,'Apply','Back'];const enteredDirectly=openPresetOnEntry,field=enteredDirectly?0:await ui.menu('Settings — '+target,labels,{selected:selectedField,separatorBefore:labels.length-2});openPresetOnEntry=false;if(field===null||field===labels.length-1)break;selectedField=field;
|
|
57
57
|
if(field===0){
|
|
58
58
|
const config=loadPreviewConfig(target);let preview=presets.indexOf(settings.preset);if(preview<0)preview=1;
|
|
59
59
|
const value=await ui.menu('Performance preset',['Economy','Balanced','Smart','Custom'],{backRow:true,selected:preview,info:({selected,columns,rows})=>{
|
|
@@ -62,6 +62,7 @@ export async function runSetup({ui,statuses,draft,kind,prepare,loadPreviewConfig
|
|
|
62
62
|
const previewConfig=choice==='custom'&&custom?applyCustomRoles(config,custom):config;
|
|
63
63
|
return presetPreviewRows(presetRoleRows(previewConfig,choice),{columns,rows,compact:true});
|
|
64
64
|
}});
|
|
65
|
+
if(value===null&&enteredDirectly)break;
|
|
65
66
|
if(value!==null&&value<3)settings.preset=presets[value];
|
|
66
67
|
else if(value===3){const catalog=await catalogFor(target);if(catalog){const initial=settings.customRoles??config.custom??resolveModelSettings(applyPreset(config,settings.preset==='custom'?'balanced':settings.preset)).roles;const roles=await editCustomPreset({ui,config,initialRoles:initial,catalog});if(roles){settings.customRoles=roles;settings.customEdited=true;settings.preset='custom';}}}
|
|
67
68
|
if(kind==='preset')selectedField=1;
|