claude-code-session-manager 0.13.0 → 0.14.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/dist/assets/{TiptapBody-ZBlGHTXg.js → TiptapBody-CzNkD0kT.js} +1 -1
- package/dist/assets/{cssMode-Co9Kuq5g.js → cssMode-DUMBBgBO.js} +1 -1
- package/dist/assets/{freemarker2-B9Y0PjVt.js → freemarker2-BjnoO8uS.js} +1 -1
- package/dist/assets/{handlebars-BGL0s1rN.js → handlebars-CggfdTjZ.js} +1 -1
- package/dist/assets/{html-C8BuG-2M.js → html-Bcnp_pNA.js} +1 -1
- package/dist/assets/{htmlMode-BfCigMol.js → htmlMode-CFws8rf5.js} +1 -1
- package/dist/assets/{index-CkI_4Vt7.js → index-0geebEag.js} +1684 -1383
- package/dist/assets/{index-DewIHWBs.css → index-DOo1Vtox.css} +1 -1
- package/dist/assets/{javascript-sES7Ewin.js → javascript-DI4e0TwT.js} +1 -1
- package/dist/assets/{jsonMode-C7txz4wN.js → jsonMode-Ca0fgEAF.js} +1 -1
- package/dist/assets/{liquid-B4qxgigx.js → liquid-E7hk4hx9.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-B5xgGS7m.js → lspLanguageFeatures-Wh7EFavn.js} +1 -1
- package/dist/assets/{mdx-De-8TTNY.js → mdx-iXIhpMBJ.js} +1 -1
- package/dist/assets/{python-SfayDkVl.js → python-laxy9eG1.js} +1 -1
- package/dist/assets/{razor-BRCdaeYo.js → razor-CatPL_eG.js} +1 -1
- package/dist/assets/{tsMode-DBTPpTr8.js → tsMode-D2WL55Cf.js} +1 -1
- package/dist/assets/{typescript-Bxbq3Vd7.js → typescript-B5DFbVoi.js} +1 -1
- package/dist/assets/{xml-DQBPiVNP.js → xml-BsStStQW.js} +1 -1
- package/dist/assets/{yaml-lyIgZXXz.js → yaml-rg2IIDpu.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +2 -1
- package/src/main/__tests__/runVerify.test.cjs +388 -0
- package/src/main/config.cjs +1 -7
- package/src/main/files.cjs +4 -37
- package/src/main/historyAggregator.cjs +37 -14
- package/src/main/index.cjs +53 -134
- package/src/main/ipcSchemas.cjs +4 -0
- package/src/main/lib/childWithLog.cjs +218 -0
- package/src/main/lib/expandHome.cjs +21 -0
- package/src/main/lib/insideHome.cjs +74 -21
- package/src/main/lib/openExternalApp.cjs +141 -0
- package/src/main/pluginInstall.cjs +39 -1
- package/src/main/pty.cjs +11 -4
- package/src/main/queueOps.cjs +2 -2
- package/src/main/runVerify.cjs +527 -0
- package/src/main/scheduler.cjs +335 -286
- package/src/main/search.cjs +1 -6
- package/src/main/superagent.cjs +10 -0
- package/src/main/supervisor.cjs +16 -8
- package/src/main/transcripts.cjs +6 -0
- package/src/main/watchers.cjs +2 -2
- package/src/preload/api.d.ts +16 -7
- package/src/preload/index.cjs +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{d as M,f as w,U as F,a as P,M as v,l as f,t as K,R as O}from"./index-
|
|
1
|
+
import{d as M,f as w,U as F,a as P,M as v,l as f,t as K,R as O}from"./index-0geebEag.js";class E{constructor(e,t){this._modeId=e,this._defaults=t,this._worker=null,this._client=null,this._configChangeListener=this._defaults.onDidChange(()=>this._stopWorker()),this._updateExtraLibsToken=0,this._extraLibsChangeListener=this._defaults.onDidExtraLibsChange(()=>this._updateExtraLibs())}dispose(){this._configChangeListener.dispose(),this._extraLibsChangeListener.dispose(),this._stopWorker()}_stopWorker(){this._worker&&(this._worker.dispose(),this._worker=null),this._client=null}async _updateExtraLibs(){if(!this._worker)return;const e=++this._updateExtraLibsToken,t=await this._worker.getProxy();this._updateExtraLibsToken===e&&t.updateExtraLibs(this._defaults.getExtraLibs())}_getClient(){return this._client||(this._client=(async()=>(this._worker=M({moduleId:"vs/language/typescript/tsWorker",createWorker:()=>new Worker(new URL(""+new URL("ts.worker-59MjiAqk.js",import.meta.url).href,import.meta.url),{type:"module"}),label:this._modeId,keepIdleModels:!0,createData:{compilerOptions:this._defaults.getCompilerOptions(),extraLibs:this._defaults.getExtraLibs(),customWorkerPath:this._defaults.workerOptions.customWorkerPath,inlayHintsOptions:this._defaults.inlayHintsOptions}}),this._defaults.getEagerModelSync()?await this._worker.withSyncedResources(w.getModels().filter(e=>e.getLanguageId()===this._modeId).map(e=>e.uri)):await this._worker.getProxy()))()),this._client}async getLanguageServiceWorker(...e){const t=await this._getClient();return this._worker&&await this._worker.withSyncedResources(e),t}}const s={};s["lib.d.ts"]=!0;s["lib.decorators.d.ts"]=!0;s["lib.decorators.legacy.d.ts"]=!0;s["lib.dom.asynciterable.d.ts"]=!0;s["lib.dom.d.ts"]=!0;s["lib.dom.iterable.d.ts"]=!0;s["lib.es2015.collection.d.ts"]=!0;s["lib.es2015.core.d.ts"]=!0;s["lib.es2015.d.ts"]=!0;s["lib.es2015.generator.d.ts"]=!0;s["lib.es2015.iterable.d.ts"]=!0;s["lib.es2015.promise.d.ts"]=!0;s["lib.es2015.proxy.d.ts"]=!0;s["lib.es2015.reflect.d.ts"]=!0;s["lib.es2015.symbol.d.ts"]=!0;s["lib.es2015.symbol.wellknown.d.ts"]=!0;s["lib.es2016.array.include.d.ts"]=!0;s["lib.es2016.d.ts"]=!0;s["lib.es2016.full.d.ts"]=!0;s["lib.es2016.intl.d.ts"]=!0;s["lib.es2017.arraybuffer.d.ts"]=!0;s["lib.es2017.d.ts"]=!0;s["lib.es2017.date.d.ts"]=!0;s["lib.es2017.full.d.ts"]=!0;s["lib.es2017.intl.d.ts"]=!0;s["lib.es2017.object.d.ts"]=!0;s["lib.es2017.sharedmemory.d.ts"]=!0;s["lib.es2017.string.d.ts"]=!0;s["lib.es2017.typedarrays.d.ts"]=!0;s["lib.es2018.asyncgenerator.d.ts"]=!0;s["lib.es2018.asynciterable.d.ts"]=!0;s["lib.es2018.d.ts"]=!0;s["lib.es2018.full.d.ts"]=!0;s["lib.es2018.intl.d.ts"]=!0;s["lib.es2018.promise.d.ts"]=!0;s["lib.es2018.regexp.d.ts"]=!0;s["lib.es2019.array.d.ts"]=!0;s["lib.es2019.d.ts"]=!0;s["lib.es2019.full.d.ts"]=!0;s["lib.es2019.intl.d.ts"]=!0;s["lib.es2019.object.d.ts"]=!0;s["lib.es2019.string.d.ts"]=!0;s["lib.es2019.symbol.d.ts"]=!0;s["lib.es2020.bigint.d.ts"]=!0;s["lib.es2020.d.ts"]=!0;s["lib.es2020.date.d.ts"]=!0;s["lib.es2020.full.d.ts"]=!0;s["lib.es2020.intl.d.ts"]=!0;s["lib.es2020.number.d.ts"]=!0;s["lib.es2020.promise.d.ts"]=!0;s["lib.es2020.sharedmemory.d.ts"]=!0;s["lib.es2020.string.d.ts"]=!0;s["lib.es2020.symbol.wellknown.d.ts"]=!0;s["lib.es2021.d.ts"]=!0;s["lib.es2021.full.d.ts"]=!0;s["lib.es2021.intl.d.ts"]=!0;s["lib.es2021.promise.d.ts"]=!0;s["lib.es2021.string.d.ts"]=!0;s["lib.es2021.weakref.d.ts"]=!0;s["lib.es2022.array.d.ts"]=!0;s["lib.es2022.d.ts"]=!0;s["lib.es2022.error.d.ts"]=!0;s["lib.es2022.full.d.ts"]=!0;s["lib.es2022.intl.d.ts"]=!0;s["lib.es2022.object.d.ts"]=!0;s["lib.es2022.regexp.d.ts"]=!0;s["lib.es2022.string.d.ts"]=!0;s["lib.es2023.array.d.ts"]=!0;s["lib.es2023.collection.d.ts"]=!0;s["lib.es2023.d.ts"]=!0;s["lib.es2023.full.d.ts"]=!0;s["lib.es2023.intl.d.ts"]=!0;s["lib.es2024.arraybuffer.d.ts"]=!0;s["lib.es2024.collection.d.ts"]=!0;s["lib.es2024.d.ts"]=!0;s["lib.es2024.full.d.ts"]=!0;s["lib.es2024.object.d.ts"]=!0;s["lib.es2024.promise.d.ts"]=!0;s["lib.es2024.regexp.d.ts"]=!0;s["lib.es2024.sharedmemory.d.ts"]=!0;s["lib.es2024.string.d.ts"]=!0;s["lib.es5.d.ts"]=!0;s["lib.es6.d.ts"]=!0;s["lib.esnext.array.d.ts"]=!0;s["lib.esnext.collection.d.ts"]=!0;s["lib.esnext.d.ts"]=!0;s["lib.esnext.decorators.d.ts"]=!0;s["lib.esnext.disposable.d.ts"]=!0;s["lib.esnext.error.d.ts"]=!0;s["lib.esnext.float16.d.ts"]=!0;s["lib.esnext.full.d.ts"]=!0;s["lib.esnext.intl.d.ts"]=!0;s["lib.esnext.iterator.d.ts"]=!0;s["lib.esnext.promise.d.ts"]=!0;s["lib.esnext.sharedmemory.d.ts"]=!0;s["lib.scripthost.d.ts"]=!0;s["lib.webworker.asynciterable.d.ts"]=!0;s["lib.webworker.d.ts"]=!0;s["lib.webworker.importscripts.d.ts"]=!0;s["lib.webworker.iterable.d.ts"]=!0;function A(c,e,t=0){if(typeof c=="string")return c;if(c===void 0)return"";let o="";if(t){o+=e;for(let i=0;i<t;i++)o+=" "}if(o+=c.messageText,t++,c.next)for(const i of c.next)o+=A(i,e,t);return o}function _(c){return c?c.map(e=>e.text).join(""):""}class y{constructor(e){this._worker=e}_textSpanToRange(e,t){let o=e.getPositionAt(t.start),i=e.getPositionAt(t.start+t.length),{lineNumber:l,column:p}=o,{lineNumber:n,column:r}=i;return{startLineNumber:l,startColumn:p,endLineNumber:n,endColumn:r}}}class H{constructor(e){this._worker=e,this._libFiles={},this._hasFetchedLibFiles=!1,this._fetchLibFilesPromise=null}isLibFile(e){return e&&e.path.indexOf("/lib.")===0?!!s[e.path.slice(1)]:!1}getOrCreateModel(e){const t=F.parse(e),o=w.getModel(t);if(o)return o;if(this.isLibFile(t)&&this._hasFetchedLibFiles)return w.createModel(this._libFiles[t.path.slice(1)],"typescript",t);const i=K.getExtraLibs()[e];return i?w.createModel(i.content,"typescript",t):null}_containsLibFile(e){for(let t of e)if(this.isLibFile(t))return!0;return!1}async fetchLibFilesIfNecessary(e){this._containsLibFile(e)&&await this._fetchLibFiles()}_fetchLibFiles(){return this._fetchLibFilesPromise||(this._fetchLibFilesPromise=this._worker().then(e=>e.getLibFiles()).then(e=>{this._hasFetchedLibFiles=!0,this._libFiles=e})),this._fetchLibFilesPromise}}class W extends y{constructor(e,t,o,i){super(i),this._libFiles=e,this._defaults=t,this._selector=o,this._disposables=[],this._listener=Object.create(null);const l=r=>{if(r.getLanguageId()!==o)return;const u=()=>{const{onlyVisible:h}=this._defaults.getDiagnosticsOptions();h?r.isAttachedToEditor()&&this._doValidate(r):this._doValidate(r)};let d;const g=r.onDidChangeContent(()=>{clearTimeout(d),d=window.setTimeout(u,500)}),b=r.onDidChangeAttached(()=>{const{onlyVisible:h}=this._defaults.getDiagnosticsOptions();h&&(r.isAttachedToEditor()?u():w.setModelMarkers(r,this._selector,[]))});this._listener[r.uri.toString()]={dispose(){g.dispose(),b.dispose(),clearTimeout(d)}},u()},p=r=>{w.setModelMarkers(r,this._selector,[]);const u=r.uri.toString();this._listener[u]&&(this._listener[u].dispose(),delete this._listener[u])};this._disposables.push(w.onDidCreateModel(r=>l(r))),this._disposables.push(w.onWillDisposeModel(p)),this._disposables.push(w.onDidChangeModelLanguage(r=>{p(r.model),l(r.model)})),this._disposables.push({dispose(){for(const r of w.getModels())p(r)}});const n=()=>{for(const r of w.getModels())p(r),l(r)};this._disposables.push(this._defaults.onDidChange(n)),this._disposables.push(this._defaults.onDidExtraLibsChange(n)),w.getModels().forEach(r=>l(r))}dispose(){this._disposables.forEach(e=>e&&e.dispose()),this._disposables=[]}async _doValidate(e){const t=await this._worker(e.uri);if(e.isDisposed())return;const o=[],{noSyntaxValidation:i,noSemanticValidation:l,noSuggestionDiagnostics:p}=this._defaults.getDiagnosticsOptions();i||o.push(t.getSyntacticDiagnostics(e.uri.toString())),l||o.push(t.getSemanticDiagnostics(e.uri.toString())),p||o.push(t.getSuggestionDiagnostics(e.uri.toString()));const n=await Promise.all(o);if(!n||e.isDisposed())return;const r=n.reduce((d,g)=>g.concat(d),[]).filter(d=>(this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore||[]).indexOf(d.code)===-1),u=r.map(d=>d.relatedInformation||[]).reduce((d,g)=>g.concat(d),[]).map(d=>d.file?F.parse(d.file.fileName):null);await this._libFiles.fetchLibFilesIfNecessary(u),!e.isDisposed()&&w.setModelMarkers(e,this._selector,r.map(d=>this._convertDiagnostics(e,d)))}_convertDiagnostics(e,t){const o=t.start||0,i=t.length||1,{lineNumber:l,column:p}=e.getPositionAt(o),{lineNumber:n,column:r}=e.getPositionAt(o+i),u=[];return t.reportsUnnecessary&&u.push(P.Unnecessary),t.reportsDeprecated&&u.push(P.Deprecated),{severity:this._tsDiagnosticCategoryToMarkerSeverity(t.category),startLineNumber:l,startColumn:p,endLineNumber:n,endColumn:r,message:A(t.messageText,`
|
|
2
2
|
`),code:t.code.toString(),tags:u,relatedInformation:this._convertRelatedInformation(e,t.relatedInformation)}}_convertRelatedInformation(e,t){if(!t)return[];const o=[];return t.forEach(i=>{let l=e;if(i.file&&(l=this._libFiles.getOrCreateModel(i.file.fileName)),!l)return;const p=i.start||0,n=i.length||1,{lineNumber:r,column:u}=l.getPositionAt(p),{lineNumber:d,column:g}=l.getPositionAt(p+n);o.push({resource:l.uri,startLineNumber:r,startColumn:u,endLineNumber:d,endColumn:g,message:A(i.messageText,`
|
|
3
3
|
`)})}),o}_tsDiagnosticCategoryToMarkerSeverity(e){switch(e){case 1:return v.Error;case 3:return v.Info;case 0:return v.Warning;case 2:return v.Hint}return v.Info}}class C extends y{get triggerCharacters(){return["."]}async provideCompletionItems(e,t,o,i){const l=e.getWordUntilPosition(t),p=new O(t.lineNumber,l.startColumn,t.lineNumber,l.endColumn),n=e.uri,r=e.getOffsetAt(t),u=await this._worker(n);if(e.isDisposed())return;const d=await u.getCompletionsAtPosition(n.toString(),r);return!d||e.isDisposed()?void 0:{suggestions:d.entries.map(b=>{let h=p;if(b.replacementSpan){const x=e.getPositionAt(b.replacementSpan.start),D=e.getPositionAt(b.replacementSpan.start+b.replacementSpan.length);h=new O(x.lineNumber,x.column,D.lineNumber,D.column)}const k=[];return b.kindModifiers!==void 0&&b.kindModifiers.indexOf("deprecated")!==-1&&k.push(f.CompletionItemTag.Deprecated),{uri:n,position:t,offset:r,range:h,label:b.name,insertText:b.name,sortText:b.sortText,kind:C.convertKind(b.kind),tags:k}})}}async resolveCompletionItem(e,t){const o=e,i=o.uri,l=o.position,p=o.offset,r=await(await this._worker(i)).getCompletionEntryDetails(i.toString(),p,o.label);return r?{uri:i,position:l,label:r.name,kind:C.convertKind(r.kind),detail:_(r.displayParts),documentation:{value:C.createDocumentationString(r)}}:o}static convertKind(e){switch(e){case a.primitiveType:case a.keyword:return f.CompletionItemKind.Keyword;case a.variable:case a.localVariable:return f.CompletionItemKind.Variable;case a.memberVariable:case a.memberGetAccessor:case a.memberSetAccessor:return f.CompletionItemKind.Field;case a.function:case a.memberFunction:case a.constructSignature:case a.callSignature:case a.indexSignature:return f.CompletionItemKind.Function;case a.enum:return f.CompletionItemKind.Enum;case a.module:return f.CompletionItemKind.Module;case a.class:return f.CompletionItemKind.Class;case a.interface:return f.CompletionItemKind.Interface;case a.warning:return f.CompletionItemKind.File}return f.CompletionItemKind.Property}static createDocumentationString(e){let t=_(e.documentation);if(e.tags)for(const o of e.tags)t+=`
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-0geebEag.js";const n={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],onEnterRules:[{beforeText:/^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,afterText:/^\s*\*\/$/,action:{indentAction:e.IndentAction.IndentOutdent,appendText:" * "}},{beforeText:/^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,action:{indentAction:e.IndentAction.None,appendText:" * "}},{beforeText:/^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,action:{indentAction:e.IndentAction.None,appendText:"* "}},{beforeText:/^(\t|(\ \ ))*\ \*\/\s*$/,action:{indentAction:e.IndentAction.None,removeText:1}}],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"',notIn:["string"]},{open:"'",close:"'",notIn:["string","comment"]},{open:"`",close:"`",notIn:["string","comment"]},{open:"/**",close:" */",notIn:["string"]}],folding:{markers:{start:new RegExp("^\\s*//\\s*#?region\\b"),end:new RegExp("^\\s*//\\s*#?endregion\\b")}}},o={defaultToken:"invalid",tokenPostfix:".ts",keywords:["abstract","any","as","asserts","bigint","boolean","break","case","catch","class","continue","const","constructor","debugger","declare","default","delete","do","else","enum","export","extends","false","finally","for","from","function","get","if","implements","import","in","infer","instanceof","interface","is","keyof","let","module","namespace","never","new","null","number","object","out","package","private","protected","public","override","readonly","require","global","return","satisfies","set","static","string","super","switch","symbol","this","throw","true","try","type","typeof","undefined","unique","unknown","var","void","while","with","yield","async","await","of"],operators:["<=",">=","==","!=","===","!==","=>","+","-","**","*","/","%","++","--","<<","</",">>",">>>","&","|","^","!","~","&&","||","??","?",":","=","+=","-=","*=","**=","/=","%=","<<=",">>=",">>>=","&=","|=","^=","@"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,digits:/\d+(_+\d+)*/,octaldigits:/[0-7]+(_+[0-7]+)*/,binarydigits:/[0-1]+(_+[0-1]+)*/,hexdigits:/[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,regexpctl:/[(){}\[\]\$\^|\-*+?\.]/,regexpesc:/\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,tokenizer:{root:[[/[{}]/,"delimiter.bracket"],{include:"common"}],common:[[/#?[a-z_$][\w$]*/,{cases:{"@keywords":"keyword","@default":"identifier"}}],[/[A-Z][\w\$]*/,"type.identifier"],{include:"@whitespace"},[/\/(?=([^\\\/]|\\.)+\/([dgimsuy]*)(\s*)(\.|;|,|\)|\]|\}|$))/,{token:"regexp",bracket:"@open",next:"@regexp"}],[/[()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/!(?=([^=]|$))/,"delimiter"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/(@digits)[eE]([\-+]?(@digits))?/,"number.float"],[/(@digits)\.(@digits)([eE][\-+]?(@digits))?/,"number.float"],[/0[xX](@hexdigits)n?/,"number.hex"],[/0[oO]?(@octaldigits)n?/,"number.octal"],[/0[bB](@binarydigits)n?/,"number.binary"],[/(@digits)n?/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/"/,"string","@string_double"],[/'/,"string","@string_single"],[/`/,"string","@string_backtick"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*\*(?!\/)/,"comment.doc","@jsdoc"],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],jsdoc:[[/[^\/*]+/,"comment.doc"],[/\*\//,"comment.doc","@pop"],[/[\/*]/,"comment.doc"]],regexp:[[/(\{)(\d+(?:,\d*)?)(\})/,["regexp.escape.control","regexp.escape.control","regexp.escape.control"]],[/(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/,["regexp.escape.control",{token:"regexp.escape.control",next:"@regexrange"}]],[/(\()(\?:|\?=|\?!)/,["regexp.escape.control","regexp.escape.control"]],[/[()]/,"regexp.escape.control"],[/@regexpctl/,"regexp.escape.control"],[/[^\\\/]/,"regexp"],[/@regexpesc/,"regexp.escape"],[/\\\./,"regexp.invalid"],[/(\/)([dgimsuy]*)/,[{token:"regexp",bracket:"@close",next:"@pop"},"keyword.other"]]],regexrange:[[/-/,"regexp.escape.control"],[/\^/,"regexp.invalid"],[/@regexpesc/,"regexp.escape"],[/[^\]]/,"regexp"],[/\]/,{token:"regexp.escape.control",next:"@pop",bracket:"@close"}]],string_double:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],string_single:[[/[^\\']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/'/,"string","@pop"]],string_backtick:[[/\$\{/,{token:"delimiter.bracket",next:"@bracketCounting"}],[/[^\\`$]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/`/,"string","@pop"]],bracketCounting:[[/\{/,"delimiter.bracket","@bracketCounting"],[/\}/,"delimiter.bracket","@pop"],{include:"common"}]}};export{n as conf,o as language};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-0geebEag.js";const n={comments:{blockComment:["<!--","-->"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],onEnterRules:[{beforeText:new RegExp("<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$","i"),afterText:/^<\/([_:\w][_:\w-.\d]*)\s*>$/i,action:{indentAction:e.IndentAction.IndentOutdent}},{beforeText:new RegExp("<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$","i"),action:{indentAction:e.IndentAction.Indent}}]},o={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/<!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/<!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}};export{n as conf,o as language};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-0geebEag.js";const t={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:e.IndentAction.Indent}}]},o={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}};export{t as conf,o as language};
|
package/dist/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-0geebEag.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DOo1Vtox.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body class="bg-bg text-fg font-sans antialiased">
|
|
14
14
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"postinstall": "electron-rebuild -f -w node-pty",
|
|
28
28
|
"prepublishOnly": "vite build",
|
|
29
29
|
"test:unit": "vitest run",
|
|
30
|
+
"test:verify": "node src/main/__tests__/runVerify.test.cjs",
|
|
30
31
|
"test:e2e": "xvfb-run -a playwright test",
|
|
31
32
|
"smoke:darwin": "playwright test tests/smoke/darwin-boot.spec.ts",
|
|
32
33
|
"test:e2e:mic": "xvfb-run -a playwright test e2e/mic.spec.mjs",
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runVerify.test.cjs — unit tests for the post-run reconciliation verifier.
|
|
3
|
+
*
|
|
4
|
+
* Run standalone: node src/main/__tests__/runVerify.test.cjs
|
|
5
|
+
*
|
|
6
|
+
* Three fixture scenarios (matching the 2026-05-23→24 incident window):
|
|
7
|
+
*
|
|
8
|
+
* 1. Case 1 (false-PASS): M2 acceptance script prints test failures + Traceback,
|
|
9
|
+
* then echoes "M2 acceptance: PASS". Scheduler used to stamp 'completed'.
|
|
10
|
+
* Expected: transcript_errors → needs_review
|
|
11
|
+
*
|
|
12
|
+
* 2. Case 2 (HALT): M7 agent correctly halts because M1 soak is unmet.
|
|
13
|
+
* Expected: halt → pending, reason embeds soak floor date
|
|
14
|
+
*
|
|
15
|
+
* 3. Case 3 (deps_unmet): PRD 56 has an unsatisfied dependsOn entry.
|
|
16
|
+
* Expected: deps_unmet → pending
|
|
17
|
+
*
|
|
18
|
+
* 4. Truly clean: all tests pass, no error markers.
|
|
19
|
+
* Expected: clean → null (caller may stamp 'completed')
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const { test } = require('node:test');
|
|
25
|
+
const assert = require('node:assert/strict');
|
|
26
|
+
const os = require('node:os');
|
|
27
|
+
const fs = require('node:fs');
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
const { verifyRun } = require('../runVerify.cjs');
|
|
30
|
+
|
|
31
|
+
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/** Create a temp directory that is cleaned up after the test. */
|
|
34
|
+
function makeTmpDir() {
|
|
35
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'run-verify-test-'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function rmdir(dir) {
|
|
39
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* */ }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build a minimal stream-JSON log file.
|
|
44
|
+
* `events` is an array of raw JSON objects to serialise one-per-line.
|
|
45
|
+
* A "[scheduler] starting..." prefix line is also prepended (must be skipped).
|
|
46
|
+
*/
|
|
47
|
+
function writeLog(dir, slug, events) {
|
|
48
|
+
const lines = ['[scheduler] starting ' + slug + ' at 2026-05-24T00:00:00.000Z'];
|
|
49
|
+
for (const ev of events) lines.push(JSON.stringify(ev));
|
|
50
|
+
lines.push('[scheduler] exit code=0 (raw code=0 signal=null) duration=47s');
|
|
51
|
+
fs.writeFileSync(path.join(dir, `${slug}.log`), lines.join('\n') + '\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Write a minimal PRD .md file with frontmatter + body. */
|
|
55
|
+
function writePrd(dir, slug, body) {
|
|
56
|
+
const text = `---\ntitle: Test PRD\ncwd: /tmp\nestimateMinutes: 30\n---\n${body}\n`;
|
|
57
|
+
fs.writeFileSync(path.join(dir, `${slug}.md`), text);
|
|
58
|
+
return path.join(dir, `${slug}.md`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── fixture: Case 1 — false-PASS (M2 acceptance) ─────────────────────────
|
|
62
|
+
|
|
63
|
+
test('Case 1: false-PASS (Traceback + echo PASS) → transcript_errors/needs_review', async () => {
|
|
64
|
+
const tmp = makeTmpDir();
|
|
65
|
+
try {
|
|
66
|
+
const slug = '39-signal-builder-m2-sentiment-stack';
|
|
67
|
+
const logEvents = [
|
|
68
|
+
// Assistant turn: run acceptance script
|
|
69
|
+
{
|
|
70
|
+
type: 'assistant',
|
|
71
|
+
message: {
|
|
72
|
+
role: 'assistant',
|
|
73
|
+
content: [{
|
|
74
|
+
type: 'tool_use',
|
|
75
|
+
id: 'toolu_m2_001',
|
|
76
|
+
name: 'Bash',
|
|
77
|
+
input: {
|
|
78
|
+
command: 'bash check_m2.sh',
|
|
79
|
+
description: 'Run M2 acceptance checks',
|
|
80
|
+
},
|
|
81
|
+
}],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
// Tool result: test failures then echo PASS (the false-positive pattern)
|
|
85
|
+
{
|
|
86
|
+
type: 'user',
|
|
87
|
+
message: {
|
|
88
|
+
role: 'user',
|
|
89
|
+
content: [{
|
|
90
|
+
type: 'tool_result',
|
|
91
|
+
tool_use_id: 'toolu_m2_001',
|
|
92
|
+
content: [
|
|
93
|
+
'=== Signal-builder side ===',
|
|
94
|
+
'ERROR tests/test_news_signals_panel.py',
|
|
95
|
+
'!!!!!!!! stopping after 1 failures !!!!!!!!',
|
|
96
|
+
'1 error in 0.03s',
|
|
97
|
+
'SB: 0',
|
|
98
|
+
'=== Sanity: SB sentiment_cache.db rows ===',
|
|
99
|
+
'rows: 0',
|
|
100
|
+
'=== contract.json panels.sentiment ===',
|
|
101
|
+
'Traceback (most recent call last):',
|
|
102
|
+
' File "<string>", line 1, in <module>',
|
|
103
|
+
"KeyError: 'panels.sentiment'",
|
|
104
|
+
'M2 acceptance: PASS',
|
|
105
|
+
].join('\n'),
|
|
106
|
+
is_error: false,
|
|
107
|
+
}],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
// Result: agent declares success
|
|
111
|
+
{
|
|
112
|
+
type: 'result',
|
|
113
|
+
subtype: 'success',
|
|
114
|
+
result: 'M2 acceptance criteria verified. All checks passed.',
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
writeLog(tmp, slug, logEvents);
|
|
119
|
+
const prdPath = writePrd(tmp, slug, '# M2 Sentiment Stack\n\nBuild the sentiment pipeline.');
|
|
120
|
+
|
|
121
|
+
const verdict = await verifyRun({
|
|
122
|
+
runDir: tmp,
|
|
123
|
+
prdPath,
|
|
124
|
+
queueEntry: { slug, status: 'running' },
|
|
125
|
+
allJobs: [],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
assert.equal(verdict.verdict, 'transcript_errors', `expected transcript_errors, got ${verdict.verdict}: ${verdict.reason}`);
|
|
129
|
+
assert.equal(verdict.downgradeTo, 'needs_review');
|
|
130
|
+
assert.ok(verdict.reason.length > 0, 'reason should be non-empty');
|
|
131
|
+
|
|
132
|
+
// verdicts.json sidecar should have been written
|
|
133
|
+
const sidecar = JSON.parse(fs.readFileSync(path.join(tmp, `${slug}.verdicts.json`), 'utf8'));
|
|
134
|
+
assert.equal(sidecar.schemaVersion, 1);
|
|
135
|
+
assert.equal(sidecar.verdict, 'transcript_errors');
|
|
136
|
+
} finally {
|
|
137
|
+
rmdir(tmp);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ─── fixture: Case 2 — HALT (M7 soak unmet) ──────────────────────────────────
|
|
142
|
+
|
|
143
|
+
test('Case 2: HALT in result event → halt/pending with floor date in reason', async () => {
|
|
144
|
+
const tmp = makeTmpDir();
|
|
145
|
+
try {
|
|
146
|
+
const slug = '44-signal-builder-m7-final-cutover';
|
|
147
|
+
const haltMsg = [
|
|
148
|
+
'HALT: M7 prerequisite — M1 not yet soaked 30 days × 6 phases',
|
|
149
|
+
' M1 commit: f91ce44 2026-05-23 (age: 0 days, need ≥ 180)',
|
|
150
|
+
' M7 earliest eligible: ~2026-11-19',
|
|
151
|
+
].join('\n');
|
|
152
|
+
|
|
153
|
+
const logEvents = [
|
|
154
|
+
{
|
|
155
|
+
type: 'assistant',
|
|
156
|
+
message: {
|
|
157
|
+
role: 'assistant',
|
|
158
|
+
content: [{
|
|
159
|
+
type: 'tool_use',
|
|
160
|
+
id: 'toolu_m7_001',
|
|
161
|
+
name: 'Bash',
|
|
162
|
+
input: { command: 'git log --oneline m1-tag', description: 'Check M1 soak period' },
|
|
163
|
+
}],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
type: 'user',
|
|
168
|
+
message: {
|
|
169
|
+
role: 'user',
|
|
170
|
+
content: [{
|
|
171
|
+
type: 'tool_result',
|
|
172
|
+
tool_use_id: 'toolu_m7_001',
|
|
173
|
+
content: 'f91ce44 2026-05-23 feat(m1): initial trading signals',
|
|
174
|
+
is_error: false,
|
|
175
|
+
}],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
type: 'result',
|
|
180
|
+
subtype: 'success',
|
|
181
|
+
result: haltMsg,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
writeLog(tmp, slug, logEvents);
|
|
186
|
+
const prdPath = writePrd(
|
|
187
|
+
tmp,
|
|
188
|
+
slug,
|
|
189
|
+
'# M7 Final Cutover\n\nOnly after M1–M6 have run 30+ days each.',
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const verdict = await verifyRun({
|
|
193
|
+
runDir: tmp,
|
|
194
|
+
prdPath,
|
|
195
|
+
queueEntry: { slug, status: 'running' },
|
|
196
|
+
allJobs: [],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
assert.equal(verdict.verdict, 'halt', `expected halt, got ${verdict.verdict}: ${verdict.reason}`);
|
|
200
|
+
assert.equal(verdict.downgradeTo, 'pending');
|
|
201
|
+
assert.ok(verdict.reason.includes('HALT'), 'reason should mention HALT');
|
|
202
|
+
// Floor date from the HALT message should appear in reason
|
|
203
|
+
assert.ok(
|
|
204
|
+
verdict.reason.includes('2026-11-19') || verdict.reason.length > 20,
|
|
205
|
+
`reason should embed soak floor date: ${verdict.reason}`,
|
|
206
|
+
);
|
|
207
|
+
} finally {
|
|
208
|
+
rmdir(tmp);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// ─── fixture: Case 3 — deps_unmet (dependsOn unsatisfied) ────────────────────
|
|
213
|
+
|
|
214
|
+
test('Case 3: dependsOn pointing at pending dep → deps_unmet/pending', async () => {
|
|
215
|
+
const tmp = makeTmpDir();
|
|
216
|
+
try {
|
|
217
|
+
const slug = '56-burrow-mcp-retire-tier3-frozen-endpoints';
|
|
218
|
+
const logEvents = [
|
|
219
|
+
{
|
|
220
|
+
type: 'result',
|
|
221
|
+
subtype: 'success',
|
|
222
|
+
result: 'Completed burrow MCP tier-3 retirement.',
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
writeLog(tmp, slug, logEvents);
|
|
227
|
+
const prdPath = writePrd(
|
|
228
|
+
tmp,
|
|
229
|
+
slug,
|
|
230
|
+
'# Burrow MCP Retire Tier-3\n\nRetire frozen endpoints.',
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// M7 is a predecessor that is still pending.
|
|
234
|
+
const allJobs = [
|
|
235
|
+
{ slug: '44-signal-builder-m7-final-cutover', status: 'pending', finishedAt: null },
|
|
236
|
+
{ slug: slug, status: 'running', finishedAt: null },
|
|
237
|
+
];
|
|
238
|
+
const queueEntry = {
|
|
239
|
+
slug,
|
|
240
|
+
status: 'running',
|
|
241
|
+
dependsOn: ['44-signal-builder-m7-final-cutover'],
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const verdict = await verifyRun({ runDir: tmp, prdPath, queueEntry, allJobs });
|
|
245
|
+
|
|
246
|
+
assert.equal(verdict.verdict, 'deps_unmet', `expected deps_unmet, got ${verdict.verdict}: ${verdict.reason}`);
|
|
247
|
+
assert.equal(verdict.downgradeTo, 'pending');
|
|
248
|
+
assert.ok(verdict.reason.includes('44-signal-builder-m7-final-cutover'), 'reason should name the unmet dep');
|
|
249
|
+
} finally {
|
|
250
|
+
rmdir(tmp);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ─── fixture: truly clean run ─────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
test('Truly clean run (no error markers) → clean/null', async () => {
|
|
257
|
+
const tmp = makeTmpDir();
|
|
258
|
+
try {
|
|
259
|
+
const slug = '20-clean-feature';
|
|
260
|
+
const logEvents = [
|
|
261
|
+
{
|
|
262
|
+
type: 'assistant',
|
|
263
|
+
message: {
|
|
264
|
+
role: 'assistant',
|
|
265
|
+
content: [{
|
|
266
|
+
type: 'tool_use',
|
|
267
|
+
id: 'toolu_clean_001',
|
|
268
|
+
name: 'Bash',
|
|
269
|
+
input: { command: 'npm test', description: 'Run test suite' },
|
|
270
|
+
}],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
type: 'user',
|
|
275
|
+
message: {
|
|
276
|
+
role: 'user',
|
|
277
|
+
content: [{
|
|
278
|
+
type: 'tool_result',
|
|
279
|
+
tool_use_id: 'toolu_clean_001',
|
|
280
|
+
content: '✓ 42 tests passed (1.3s)\nAll tests passed.',
|
|
281
|
+
is_error: false,
|
|
282
|
+
}],
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: 'result',
|
|
287
|
+
subtype: 'success',
|
|
288
|
+
result: 'Feature complete. All acceptance criteria satisfied.',
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
writeLog(tmp, slug, logEvents);
|
|
293
|
+
const prdPath = writePrd(tmp, slug, '# Clean Feature\n\nImplement a clean feature.');
|
|
294
|
+
|
|
295
|
+
const verdict = await verifyRun({
|
|
296
|
+
runDir: tmp,
|
|
297
|
+
prdPath,
|
|
298
|
+
queueEntry: { slug, status: 'running' },
|
|
299
|
+
allJobs: [],
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
assert.equal(verdict.verdict, 'clean', `expected clean, got ${verdict.verdict}: ${verdict.reason}`);
|
|
303
|
+
assert.equal(verdict.downgradeTo, null);
|
|
304
|
+
} finally {
|
|
305
|
+
rmdir(tmp);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// ─── self-recovery: FAIL followed by successful retry ──────────────────────
|
|
310
|
+
|
|
311
|
+
test('FAIL recovered within 30 events → clean', async () => {
|
|
312
|
+
const tmp = makeTmpDir();
|
|
313
|
+
try {
|
|
314
|
+
const slug = '21-self-recovering';
|
|
315
|
+
const desc = 'Run pytest';
|
|
316
|
+
const logEvents = [
|
|
317
|
+
// First attempt: fails
|
|
318
|
+
{
|
|
319
|
+
type: 'assistant',
|
|
320
|
+
message: {
|
|
321
|
+
role: 'assistant',
|
|
322
|
+
content: [{
|
|
323
|
+
type: 'tool_use',
|
|
324
|
+
id: 'toolu_r1',
|
|
325
|
+
name: 'Bash',
|
|
326
|
+
input: { command: 'pytest', description: desc },
|
|
327
|
+
}],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
type: 'user',
|
|
332
|
+
message: {
|
|
333
|
+
role: 'user',
|
|
334
|
+
content: [{
|
|
335
|
+
type: 'tool_result',
|
|
336
|
+
tool_use_id: 'toolu_r1',
|
|
337
|
+
content: 'FAILED tests/test_foo.py::test_bar - AssertionError',
|
|
338
|
+
is_error: false,
|
|
339
|
+
}],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
// Agent fixes and retries with same description
|
|
343
|
+
{
|
|
344
|
+
type: 'assistant',
|
|
345
|
+
message: {
|
|
346
|
+
role: 'assistant',
|
|
347
|
+
content: [{
|
|
348
|
+
type: 'tool_use',
|
|
349
|
+
id: 'toolu_r2',
|
|
350
|
+
name: 'Bash',
|
|
351
|
+
input: { command: 'pytest', description: desc },
|
|
352
|
+
}],
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
type: 'user',
|
|
357
|
+
message: {
|
|
358
|
+
role: 'user',
|
|
359
|
+
content: [{
|
|
360
|
+
type: 'tool_result',
|
|
361
|
+
tool_use_id: 'toolu_r2',
|
|
362
|
+
content: '5 passed in 0.42s',
|
|
363
|
+
is_error: false,
|
|
364
|
+
}],
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
type: 'result',
|
|
369
|
+
subtype: 'success',
|
|
370
|
+
result: 'Fixed the failing test and verified all pass.',
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
writeLog(tmp, slug, logEvents);
|
|
375
|
+
const prdPath = writePrd(tmp, slug, '# Self-recovering test');
|
|
376
|
+
|
|
377
|
+
const verdict = await verifyRun({
|
|
378
|
+
runDir: tmp,
|
|
379
|
+
prdPath,
|
|
380
|
+
queueEntry: { slug, status: 'running' },
|
|
381
|
+
allJobs: [],
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
assert.equal(verdict.verdict, 'clean', `self-recovery should yield clean, got ${verdict.verdict}: ${verdict.reason}`);
|
|
385
|
+
} finally {
|
|
386
|
+
rmdir(tmp);
|
|
387
|
+
}
|
|
388
|
+
});
|
package/src/main/config.cjs
CHANGED
|
@@ -22,6 +22,7 @@ const os = require('node:os');
|
|
|
22
22
|
const chokidar = require('chokidar');
|
|
23
23
|
const logs = require('./logs.cjs');
|
|
24
24
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
25
|
+
const { expandHome } = require('./lib/expandHome.cjs');
|
|
25
26
|
|
|
26
27
|
/** Map<absPath, {watcher, refCount}> — one chokidar watcher per path. */
|
|
27
28
|
const watchers = new Map();
|
|
@@ -31,13 +32,6 @@ function attachWindow(w) {
|
|
|
31
32
|
window = w;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
function expandHome(p) {
|
|
35
|
-
if (!p) return p;
|
|
36
|
-
if (p === '~') return os.homedir();
|
|
37
|
-
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
|
|
38
|
-
return p;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
35
|
/**
|
|
42
36
|
* Resolve a path to its realpath, handling non-existent files by resolving
|
|
43
37
|
* the parent directory instead. Prevents symlink traversal attacks.
|
package/src/main/files.cjs
CHANGED
|
@@ -14,42 +14,13 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { ipcMain, shell } = require('electron');
|
|
17
|
-
const fs = require('node:fs');
|
|
18
17
|
const fsp = require('node:fs/promises');
|
|
19
18
|
const path = require('node:path');
|
|
20
19
|
const os = require('node:os');
|
|
21
20
|
|
|
22
21
|
const { z } = require('zod');
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!p) return p;
|
|
26
|
-
if (p === '~') return os.homedir();
|
|
27
|
-
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
|
|
28
|
-
return p;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolve to realpath; on ENOENT resolve the parent then re-append basename so
|
|
33
|
-
* we can still validate create destinations. Mirrors config.cjs.
|
|
34
|
-
*/
|
|
35
|
-
function realResolve(abs) {
|
|
36
|
-
const lex = path.resolve(expandHome(abs));
|
|
37
|
-
try {
|
|
38
|
-
return fs.realpathSync(lex);
|
|
39
|
-
} catch (e) {
|
|
40
|
-
if (e.code === 'ENOENT') {
|
|
41
|
-
const parent = path.dirname(lex);
|
|
42
|
-
try {
|
|
43
|
-
return path.join(fs.realpathSync(parent), path.basename(lex));
|
|
44
|
-
} catch {
|
|
45
|
-
return lex;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
throw e;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const HOME = os.homedir();
|
|
22
|
+
const { assertInsideHome } = require('./lib/insideHome.cjs');
|
|
23
|
+
const { expandHome } = require('./lib/expandHome.cjs');
|
|
53
24
|
|
|
54
25
|
/**
|
|
55
26
|
* Validates that the path is under the home directory. Returns the realpath
|
|
@@ -58,11 +29,7 @@ const HOME = os.homedir();
|
|
|
58
29
|
* ~ — but never escapes the home tree.
|
|
59
30
|
*/
|
|
60
31
|
function validateHomePath(abs) {
|
|
61
|
-
|
|
62
|
-
let realHome;
|
|
63
|
-
try { realHome = fs.realpathSync(HOME); } catch { realHome = HOME; }
|
|
64
|
-
if (real === realHome || real.startsWith(realHome + path.sep)) return real;
|
|
65
|
-
throw new Error(`Path outside home directory: ${real}`);
|
|
32
|
+
return assertInsideHome(expandHome(abs)).realPath;
|
|
66
33
|
}
|
|
67
34
|
|
|
68
35
|
/** Reject .credentials.json writes regardless of where they sit. */
|
|
@@ -228,7 +195,7 @@ async function renameEntry(oldPath, newName) {
|
|
|
228
195
|
}
|
|
229
196
|
}
|
|
230
197
|
|
|
231
|
-
const CRITICAL_PATHS = new Set([
|
|
198
|
+
const CRITICAL_PATHS = new Set([os.homedir(), '/', '/usr', '/bin', '/etc', '/var', '/System', '/Applications']);
|
|
232
199
|
|
|
233
200
|
async function deleteEntry(filePath) {
|
|
234
201
|
let resolved;
|