allwright 1.133.0 → 1.135.0
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/bin/allwright.js +7 -6
- package/package.json +1 -1
package/bin/allwright.js
CHANGED
|
@@ -101,7 +101,8 @@ IF YOU ASK (the ${h.NEEDS_INPUT_MARKER} case above) and the sensible answers are
|
|
|
101
101
|
READ WITH INTENT - tokens are the customer's money:
|
|
102
102
|
- When the ticket names files or paths, open those FIRST; sweep the wider tree only when they fall short.
|
|
103
103
|
- When a DOCUMENT INDEX section is present below, use it: Read a long document's relevant section with offset/limit instead of the whole file.
|
|
104
|
-
- CLAUDE.md has already told you where things live - trust it before running discovery searches
|
|
104
|
+
- CLAUDE.md has already told you where things live - trust it before running discovery searches.
|
|
105
|
+
- A whole-file \`cat\`/\`sed -n\` dump through Bash costs exactly as many tokens as the same content read any other way - there is no free lunch in fewer, bigger reads. Grep for the function or section first, then read just that range; catting three files up front "in case they're relevant" costs three files whether or not two of them turn out to be (seen live, CPA-712: 39 whole-file/whole-section Bash reads against 12 targeted Reads, 77 turns before the first edit).`;h.REVISION_PROMPT=`
|
|
105
106
|
|
|
106
107
|
THIS TICKET HAS ALREADY BEEN IMPLEMENTED ONCE - you are REVISING it:
|
|
107
108
|
- The previous implementation is ALREADY IN your base branch. Do not build it again, and do not undo it. Read what is there before deciding anything.
|
|
@@ -1722,14 +1723,14 @@ ${(ee.description||"").slice(0,500)}`)}catch{_.push(`${z}: (could not read from
|
|
|
1722
1723
|
|
|
1723
1724
|
`)+`
|
|
1724
1725
|
|
|
1725
|
-
Answer with ONLY a fenced json block:
|
|
1726
|
-
{"order": ["KEY", ...], "chains": [["KEY","KEY"], ...], "dependencies": [["BLOCKED","BLOCKED_BY"], ...]
|
|
1726
|
+
Answer with ONLY a fenced json block, "reasoning" FIRST - work out and state your groupings in words before you commit to the arrays, so the arrays actually match what you just said instead of drifting from it:
|
|
1727
|
+
{"reasoning": "...", "order": ["KEY", ...], "chains": [["KEY","KEY"], ...], "dependencies": [["BLOCKED","BLOCKED_BY"], ...]}
|
|
1727
1728
|
|
|
1728
1729
|
Rules:
|
|
1730
|
+
- "reasoning": 2-3 short lines a person can check your plan against. Every pairing or dependency you name here MUST show up in "chains" or "dependencies" below - a reasoning line with no matching array entry means the group never actually happens (found 2026-08-29: reasoning named a real schema dependency between two tickets, "chains" left them both solo, and neither got the head start the other one needed).
|
|
1729
1731
|
- "order": every key exactly once. Prerequisites before the work that needs them.
|
|
1730
|
-
- "chains": tickets one engineer would do in one sitting, in the same part of the code, ADJACENT in your order.
|
|
1731
|
-
- "dependencies": ONLY hard prerequisites, where the blocked ticket CANNOT pass if the other failed. An empty list is the normal answer.
|
|
1732
|
-
- "reasoning": 2-3 short lines a person can check your plan against.`,G=await N.adapter.run({repoPath:g.repoPath,prompt:U,timeoutSeconds:90,bin:N.bin,mode:"read_only",maxTurns:3,model:N.adapter.fastModel,sessionId:(0,Vn.randomUUID)(),onLog:()=>{}});_e({surface:"autopilot_plan",projectId:g.project.id,agentId:N.adapter.id,refKind:"none",refLabel:`${d.issueKeys.length} tickets`,...ve(G)});let H=th(G.resultText,d.issueKeys);if(!H){S.sendAutopilotPlanResult(d.requestId,{ok:!1,error:"the planner's answer could not be parsed - the queue keeps its given order"});return}let{saveBatchPlan:C}=await Promise.resolve().then(()=>(Nc(),Bb));C({batchId:d.batchId,projectId:d.projectId,order:H.order,chains:H.chains,dependencies:H.dependencies,reasoning:H.reasoning}),S.sendAutopilotPlanResult(d.requestId,{ok:!0,...H})}catch(g){S.sendAutopilotPlanResult(d.requestId,{ok:!1,error:g instanceof Error?g.message:String(g)})}},onChatDiagnostic:async d=>{try{let{runDiagnostic:g}=await Promise.resolve().then(()=>(Uh(),Lv)),y=await g({command:d.command,args:d.args});b.info(`chat diagnostic (user-approved): ${y.commandLine}`),S.sendChatDiagnosticResult(d.requestId,y)}catch(g){S.sendChatDiagnosticResult(d.requestId,{ok:!1,output:g instanceof Error?g.message:String(g)})}},onChatStepRun:async d=>{let g=D.lookup(d.projectId,d.environmentName);if(!g){S.sendChatStepRunResult(d.requestId,{ok:!1,error:`this runner has no environment "${d.environmentName}" for that project`});return}let{environment:y}=g,_=y.steps[d.stepIndex];if(!_||_.name!==d.stepName){S.sendChatStepRunResult(d.requestId,{ok:!1,error:`step ${d.stepIndex+1} of "${y.name}" is ${_?`"${_.name}"`:"not there"} on this machine, not "${d.stepName}" - the recipe changed since the chat read it. Reopen the environment and ask again.`});return}let N=`${d.projectId}:${y.name}:${d.stepIndex}`;if(P.has(N)){S.sendChatStepRunResult(d.requestId,{ok:!1,stepName:_.name,error:"that step is already running here - wait for it to finish"});return}P.add(N),b.info(`chat step re-run (user-approved): [${y.name}] ${_.name}`);try{let{runSavedStep:U}=await Promise.resolve().then(()=>(Is(),xy)),G=await U({repoPath:g.repoPath,repoName:g.project.repoName,environmentName:y.name,workdir:y.workdir,branch:y.branch,step:_,cfg:t,timeoutMs:Math.min(y.timeoutSeconds*1e3,YC)});S.sendChatStepRunResult(d.requestId,{ok:!0,stepName:_.name,...G})}catch(U){S.sendChatStepRunResult(d.requestId,{ok:!1,stepName:_.name,error:U instanceof Error?U.message:String(U)})}finally{P.delete(N)}},onChatCancel:d=>{let g=L.get(d.turnId);g&&(g.abort(),b.info(`chat turn ${d.turnId} stopped from the dashboard`))},onChatTurn:async d=>{let g=A(d.projectId);if(!g){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:d.sessionId??"",reply:"",error:x(d.projectId)});return}let y=d.sessionId||(0,Vn.randomUUID)(),_=!d.sessionId,N=d.sessionId?Ms(d.sessionId):null,U=N?.agentId||d.agentId||g.project.defaultAgent||xe.DEFAULT_AGENT_ID;if(!_&&d.userId){let qe=N?.ownerUserId??"";if(qe&&qe!==d.userId){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:y,reply:"",error:"this chat belongs to someone else - you can read it, but only its owner can continue it. Start your own chat for this."});return}}let G=d.operator===!0;if(G&&!t.operatorEnabled){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:y,reply:"",error:"operator mode is not enabled on this machine. Whoever administers the server must run `allwright operator on` there once (and `allwright operator off` turns it back off) - a dashboard permission alone is deliberately not enough."});return}let H=_?0:N?.messages.length??0,C=!1,z=0,ee="";if(!_&&H>=aw){let qe=await uw(g,y,t);if(qe)C=!0,z=H,ee=qe,y=(0,Vn.randomUUID)(),_=!0,b.info(`chat compacted: ${H} message(s) summarized into a fresh session`);else if(H>=cw){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:y,reply:"",error:"this chat is very long and could not be compacted automatically - start a new chat (it keeps the repo knowledge; only the conversation resets)."});return}}let M=d.files.length?wg(g.repoPath,d.requestId,d.files):null,te=(0,xe.replyPreferenceAppendix)({language:d.replyLanguage,languageInstruction:d.replyLanguageInstruction,style:d.replyStyle})+(0,xe.pendingMigrationsAppendix)(d.pendingMigrations)+(0,xe.monitorChecksAppendix)(d.monitorChecks)+(0,xe.deployTargetsAppendix)(d.environments)+(0,xe.savedStepChecksAppendix)(d.environmentSteps)+(0,xe.composeCommandAppendix)(d.command)+ow(d.sessionId??""),ae=XC(g.repoPath)+dr("compose",g.repoPath,{summary:d.message,ruleName:""});ec(g,t);let pe="```",$e=_?`You are the project assistant for "${g.project.name}" - this working directory is its repository, and you have read-only access.
|
|
1732
|
+
- "chains": tickets one engineer would do in one sitting, in the same part of the code, ADJACENT in your order. A chained ticket gets a short handoff note from the one before it (not the full prior work) - group real neighbours, and leave a ticket out of every chain when unsure. Solo tickets appear in no chain.
|
|
1733
|
+
- "dependencies": ONLY hard prerequisites, where the blocked ticket CANNOT pass if the other failed. An empty list is the normal answer.`,G=await N.adapter.run({repoPath:g.repoPath,prompt:U,timeoutSeconds:90,bin:N.bin,mode:"read_only",maxTurns:3,model:N.adapter.fastModel,sessionId:(0,Vn.randomUUID)(),onLog:()=>{}});_e({surface:"autopilot_plan",projectId:g.project.id,agentId:N.adapter.id,refKind:"none",refLabel:`${d.issueKeys.length} tickets`,...ve(G)});let H=th(G.resultText,d.issueKeys);if(!H){S.sendAutopilotPlanResult(d.requestId,{ok:!1,error:"the planner's answer could not be parsed - the queue keeps its given order"});return}let{saveBatchPlan:C}=await Promise.resolve().then(()=>(Nc(),Bb));C({batchId:d.batchId,projectId:d.projectId,order:H.order,chains:H.chains,dependencies:H.dependencies,reasoning:H.reasoning}),S.sendAutopilotPlanResult(d.requestId,{ok:!0,...H})}catch(g){S.sendAutopilotPlanResult(d.requestId,{ok:!1,error:g instanceof Error?g.message:String(g)})}},onChatDiagnostic:async d=>{try{let{runDiagnostic:g}=await Promise.resolve().then(()=>(Uh(),Lv)),y=await g({command:d.command,args:d.args});b.info(`chat diagnostic (user-approved): ${y.commandLine}`),S.sendChatDiagnosticResult(d.requestId,y)}catch(g){S.sendChatDiagnosticResult(d.requestId,{ok:!1,output:g instanceof Error?g.message:String(g)})}},onChatStepRun:async d=>{let g=D.lookup(d.projectId,d.environmentName);if(!g){S.sendChatStepRunResult(d.requestId,{ok:!1,error:`this runner has no environment "${d.environmentName}" for that project`});return}let{environment:y}=g,_=y.steps[d.stepIndex];if(!_||_.name!==d.stepName){S.sendChatStepRunResult(d.requestId,{ok:!1,error:`step ${d.stepIndex+1} of "${y.name}" is ${_?`"${_.name}"`:"not there"} on this machine, not "${d.stepName}" - the recipe changed since the chat read it. Reopen the environment and ask again.`});return}let N=`${d.projectId}:${y.name}:${d.stepIndex}`;if(P.has(N)){S.sendChatStepRunResult(d.requestId,{ok:!1,stepName:_.name,error:"that step is already running here - wait for it to finish"});return}P.add(N),b.info(`chat step re-run (user-approved): [${y.name}] ${_.name}`);try{let{runSavedStep:U}=await Promise.resolve().then(()=>(Is(),xy)),G=await U({repoPath:g.repoPath,repoName:g.project.repoName,environmentName:y.name,workdir:y.workdir,branch:y.branch,step:_,cfg:t,timeoutMs:Math.min(y.timeoutSeconds*1e3,YC)});S.sendChatStepRunResult(d.requestId,{ok:!0,stepName:_.name,...G})}catch(U){S.sendChatStepRunResult(d.requestId,{ok:!1,stepName:_.name,error:U instanceof Error?U.message:String(U)})}finally{P.delete(N)}},onChatCancel:d=>{let g=L.get(d.turnId);g&&(g.abort(),b.info(`chat turn ${d.turnId} stopped from the dashboard`))},onChatTurn:async d=>{let g=A(d.projectId);if(!g){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:d.sessionId??"",reply:"",error:x(d.projectId)});return}let y=d.sessionId||(0,Vn.randomUUID)(),_=!d.sessionId,N=d.sessionId?Ms(d.sessionId):null,U=N?.agentId||d.agentId||g.project.defaultAgent||xe.DEFAULT_AGENT_ID;if(!_&&d.userId){let qe=N?.ownerUserId??"";if(qe&&qe!==d.userId){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:y,reply:"",error:"this chat belongs to someone else - you can read it, but only its owner can continue it. Start your own chat for this."});return}}let G=d.operator===!0;if(G&&!t.operatorEnabled){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:y,reply:"",error:"operator mode is not enabled on this machine. Whoever administers the server must run `allwright operator on` there once (and `allwright operator off` turns it back off) - a dashboard permission alone is deliberately not enough."});return}let H=_?0:N?.messages.length??0,C=!1,z=0,ee="";if(!_&&H>=aw){let qe=await uw(g,y,t);if(qe)C=!0,z=H,ee=qe,y=(0,Vn.randomUUID)(),_=!0,b.info(`chat compacted: ${H} message(s) summarized into a fresh session`);else if(H>=cw){S.sendChatTurnResult(d.requestId,{ok:!1,sessionId:y,reply:"",error:"this chat is very long and could not be compacted automatically - start a new chat (it keeps the repo knowledge; only the conversation resets)."});return}}let M=d.files.length?wg(g.repoPath,d.requestId,d.files):null,te=(0,xe.replyPreferenceAppendix)({language:d.replyLanguage,languageInstruction:d.replyLanguageInstruction,style:d.replyStyle})+(0,xe.pendingMigrationsAppendix)(d.pendingMigrations)+(0,xe.monitorChecksAppendix)(d.monitorChecks)+(0,xe.deployTargetsAppendix)(d.environments)+(0,xe.savedStepChecksAppendix)(d.environmentSteps)+(0,xe.composeCommandAppendix)(d.command)+ow(d.sessionId??""),ae=XC(g.repoPath)+dr("compose",g.repoPath,{summary:d.message,ruleName:""});ec(g,t);let pe="```",$e=_?`You are the project assistant for "${g.project.name}" - this working directory is its repository, and you have read-only access.
|
|
1733
1734
|
|
|
1734
1735
|
DEFAULT MODE IS DISCUSSION. Answer questions, explain code, weigh approaches, review ideas - like a teammate who knows this repo. Most conversations never produce a ticket, and that is fine. Do NOT propose a Jira ticket unless the user explicitly asks for one in this conversation ("create a ticket", "make this a ticket", "draft the ticket" or similar). Never end a normal answer with a ticket proposal.
|
|
1735
1736
|
Be fast: answer from CLAUDE.md and what you already know first; open files only when the question genuinely needs code-level detail. Keep replies short.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "allwright",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.135.0",
|
|
4
4
|
"description": "Allwright — the AI teammate that runs on YOUR machine. Polls your Jira, runs jailed agents in your local checkouts, opens PRs, executes approved deploys, and audits security/testing/architecture. Credentials and code never leave your host.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"allwright": "bin/allwright.js",
|