@dezycro-ai/agent-plugin 2.3.1 → 2.4.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/active-feature.d.ts +51 -0
- package/dist/active-feature.js +140 -0
- package/dist/cli/dezycro.js +25 -18
- package/dist/dezycro-config.d.ts +6 -0
- package/dist/dezycro-config.js +1 -1
- package/dist/gateway/proxy.js +2 -2
- package/dist/hooks/lib/router.js +9 -8
- package/hooks/companion-runner +2 -2
- package/hooks/hooks.json +10 -0
- package/package.json +1 -1
- package/skills/work/SKILL.md +13 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* active-feature.ts — owns the lifecycle of `.dezycro/active-feature.json`.
|
|
3
|
+
*
|
|
4
|
+
* The file is two-layered:
|
|
5
|
+
* - `featureId` (+ name/workspace/selectedAt) — the DURABLE resume pointer.
|
|
6
|
+
* Survives across sessions; only an explicit `dezycro feature --clear` wipes it,
|
|
7
|
+
* so `/dezycro:work` (no arg) can resume the last feature.
|
|
8
|
+
* - `active` — the SESSION-SCOPED signal the nudge gate keys on. Driven by the
|
|
9
|
+
* SessionStart/SessionEnd hooks so a fresh session starts inactive (no nudge
|
|
10
|
+
* until the agent binds), `/resume` restores it, and ending a session closes it.
|
|
11
|
+
*
|
|
12
|
+
* `DezycroConfig.activeFeature()` reads the same file for the gate; this module owns
|
|
13
|
+
* the WRITES (flip `active`, wipe) so the file shape lives in one typed place rather
|
|
14
|
+
* than being hand-assembled by every caller.
|
|
15
|
+
*/
|
|
16
|
+
/** The on-disk shape of `.dezycro/active-feature.json`. */
|
|
17
|
+
export interface ActiveFeatureRecord {
|
|
18
|
+
featureId: string;
|
|
19
|
+
featureName?: string;
|
|
20
|
+
workspaceId?: string;
|
|
21
|
+
selectedAt?: string;
|
|
22
|
+
/** Session-scoped: true only while a session is actively working this feature. */
|
|
23
|
+
active?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What a SessionStart `source` implies for the `active` flag:
|
|
27
|
+
* - `true` → activate (restore)
|
|
28
|
+
* - `false` → force inactive
|
|
29
|
+
* - `null` → leave unchanged
|
|
30
|
+
*
|
|
31
|
+
* `resume` reactivates the pointer (the user explicitly resumed the session, so they
|
|
32
|
+
* mean to keep working it). `startup`/`clear` force inactive — a fresh session must
|
|
33
|
+
* not nudge until the agent binds, and forcing it also self-heals a missed SessionEnd.
|
|
34
|
+
* `compact` is the SAME session continuing (auto-compaction), so it must not change
|
|
35
|
+
* activation. Unknown/legacy (no source) is treated as a fresh start: inactive.
|
|
36
|
+
*/
|
|
37
|
+
export declare function activeFromSource(source: string | undefined): boolean | null;
|
|
38
|
+
export declare class ActiveFeatureStore {
|
|
39
|
+
private readonly file;
|
|
40
|
+
constructor(repoRoot: string);
|
|
41
|
+
/** Read the record, or null if absent/unparseable/missing a featureId. Never throws. */
|
|
42
|
+
read(): ActiveFeatureRecord | null;
|
|
43
|
+
/**
|
|
44
|
+
* Flip the session-active flag, preserving the resume pointer. No-op (returns false)
|
|
45
|
+
* when there's no bound feature to (de)activate. Never throws — hooks must not break
|
|
46
|
+
* the user's session.
|
|
47
|
+
*/
|
|
48
|
+
setActive(active: boolean): boolean;
|
|
49
|
+
/** Wipe the pointer entirely — the explicit "done with this feature". Never throws. */
|
|
50
|
+
clear(): boolean;
|
|
51
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* active-feature.ts — owns the lifecycle of `.dezycro/active-feature.json`.
|
|
4
|
+
*
|
|
5
|
+
* The file is two-layered:
|
|
6
|
+
* - `featureId` (+ name/workspace/selectedAt) — the DURABLE resume pointer.
|
|
7
|
+
* Survives across sessions; only an explicit `dezycro feature --clear` wipes it,
|
|
8
|
+
* so `/dezycro:work` (no arg) can resume the last feature.
|
|
9
|
+
* - `active` — the SESSION-SCOPED signal the nudge gate keys on. Driven by the
|
|
10
|
+
* SessionStart/SessionEnd hooks so a fresh session starts inactive (no nudge
|
|
11
|
+
* until the agent binds), `/resume` restores it, and ending a session closes it.
|
|
12
|
+
*
|
|
13
|
+
* `DezycroConfig.activeFeature()` reads the same file for the gate; this module owns
|
|
14
|
+
* the WRITES (flip `active`, wipe) so the file shape lives in one typed place rather
|
|
15
|
+
* than being hand-assembled by every caller.
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.ActiveFeatureStore = void 0;
|
|
52
|
+
exports.activeFromSource = activeFromSource;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
/**
|
|
56
|
+
* What a SessionStart `source` implies for the `active` flag:
|
|
57
|
+
* - `true` → activate (restore)
|
|
58
|
+
* - `false` → force inactive
|
|
59
|
+
* - `null` → leave unchanged
|
|
60
|
+
*
|
|
61
|
+
* `resume` reactivates the pointer (the user explicitly resumed the session, so they
|
|
62
|
+
* mean to keep working it). `startup`/`clear` force inactive — a fresh session must
|
|
63
|
+
* not nudge until the agent binds, and forcing it also self-heals a missed SessionEnd.
|
|
64
|
+
* `compact` is the SAME session continuing (auto-compaction), so it must not change
|
|
65
|
+
* activation. Unknown/legacy (no source) is treated as a fresh start: inactive.
|
|
66
|
+
*/
|
|
67
|
+
function activeFromSource(source) {
|
|
68
|
+
switch (source) {
|
|
69
|
+
case 'resume':
|
|
70
|
+
return true;
|
|
71
|
+
case 'compact':
|
|
72
|
+
return null;
|
|
73
|
+
case 'startup':
|
|
74
|
+
case 'clear':
|
|
75
|
+
return false;
|
|
76
|
+
default:
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
class ActiveFeatureStore {
|
|
81
|
+
file;
|
|
82
|
+
constructor(repoRoot) {
|
|
83
|
+
this.file = path.join(repoRoot, '.dezycro', 'active-feature.json');
|
|
84
|
+
}
|
|
85
|
+
/** Read the record, or null if absent/unparseable/missing a featureId. Never throws. */
|
|
86
|
+
read() {
|
|
87
|
+
try {
|
|
88
|
+
const raw = JSON.parse(fs.readFileSync(this.file, 'utf8'));
|
|
89
|
+
const featureId = typeof raw.featureId === 'string' ? raw.featureId : null;
|
|
90
|
+
if (!featureId) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
featureId,
|
|
95
|
+
featureName: typeof raw.featureName === 'string' ? raw.featureName : undefined,
|
|
96
|
+
workspaceId: typeof raw.workspaceId === 'string' ? raw.workspaceId : undefined,
|
|
97
|
+
selectedAt: typeof raw.selectedAt === 'string' ? raw.selectedAt : undefined,
|
|
98
|
+
active: raw.active === true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Flip the session-active flag, preserving the resume pointer. No-op (returns false)
|
|
107
|
+
* when there's no bound feature to (de)activate. Never throws — hooks must not break
|
|
108
|
+
* the user's session.
|
|
109
|
+
*/
|
|
110
|
+
setActive(active) {
|
|
111
|
+
const record = this.read();
|
|
112
|
+
if (!record) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if ((record.active ?? false) === active) {
|
|
116
|
+
return false; // already in the desired state — avoid a pointless rewrite
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
fs.writeFileSync(this.file, `${JSON.stringify({ ...record, active }, null, 2)}\n`);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Wipe the pointer entirely — the explicit "done with this feature". Never throws. */
|
|
127
|
+
clear() {
|
|
128
|
+
try {
|
|
129
|
+
if (fs.existsSync(this.file)) {
|
|
130
|
+
fs.unlinkSync(this.file);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* fall through */
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.ActiveFeatureStore = ActiveFeatureStore;
|
package/dist/cli/dezycro.js
CHANGED
|
@@ -1,41 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
2
|
+
"use strict";var Se=Object.create;var R=Object.defineProperty;var xe=Object.getOwnPropertyDescriptor;var Ce=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,Ee=Object.prototype.hasOwnProperty;var Ie=(o,e)=>{for(var t in e)R(o,t,{get:e[t],enumerable:!0})},te=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Ce(e))!Ee.call(o,r)&&r!==t&&R(o,r,{get:()=>e[r],enumerable:!(n=xe(e,r))||n.enumerable});return o};var l=(o,e,t)=>(t=o!=null?Se(Re(o)):{},te(e||!o||!o.__esModule?R(t,"default",{value:o,enumerable:!0}):t,o)),Ae=o=>te(R({},"__esModule",{value:!0}),o);var Be={};Ie(Be,{Command:()=>we});module.exports=Ae(Be);var se=l(require("node:http")),S=l(require("node:path")),E=l(require("node:fs")),ie=l(require("node:os")),H=require("node:child_process");var h={MessageStart:"message_start",ContentBlockStart:"content_block_start",ContentBlockDelta:"content_block_delta",ContentBlockStop:"content_block_stop",MessageDelta:"message_delta",MessageStop:"message_stop"};var ne={UpdateTask:"update_task"},re={Status:"status"},j={Done:"DONE",Pushed:"PUSHED"};var f={SpecPublished:"specPublished",VerifiedRecently:"verifiedRecently"};var L={Claude:"claude",Codex:"codex"},J={Health:"/__dz/health",Stop:"/__dz/stop"};var oe=l(require("node:fs")),g=l(require("node:path")),w=class o{static MAX_DEPTH=6;static dir(){let e=__dirname;for(let t=0;t<o.MAX_DEPTH;t+=1){if(oe.existsSync(g.join(e,"package.json")))return e;e=g.dirname(e)}return g.resolve(__dirname,"..","..")}static file(...e){return g.join(o.dir(),...e)}};var I=class o{static DEFAULT_SHIM_CMDS=["npm","pnpm","yarn","pip","pip3","poetry","apt-get","apt","brew","gem","bundle","mvn","gradle","make"];port=Number(process.env.DZ_PORT||13579);proxyPath=w.file("dist","gateway","proxy.js");compactPath=w.file("dist","gateway","compact-cli.js");async run(e){let t=this.applyPortFlag(e),n=t[0];if(n==="--stop"){console.error(await this.control(J.Stop)?"dezycro: stopped":"dezycro: not running");return}if(n==="--status"){console.error(await this.health()?"dezycro: up":"dezycro: down");return}let r=n||L.Claude,i=t.slice(1),s=await this.ensureDaemon();console.error(s?`dezycro: gateway on :${this.port} \u2014 launching ${r}`:`dezycro: gateway unavailable \u2014 launching ${r} DIRECTLY (fail-open)`);let a=this.setupShims(),c={...process.env,...s?this.gatewayUrlEnv(r):{},...a?.env??{}},T=()=>{if(a)try{E.default.rmSync(a.dir,{recursive:!0,force:!0})}catch{}},F=(0,H.spawn)(r,i,{env:c,stdio:"inherit"});F.on("exit",b=>{T(),process.exit(b??0)}),F.on("error",b=>{T(),console.error(`dezycro: failed to launch ${r}: ${b.message}`),process.exit(127)})}applyPortFlag(e){let t=[];for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="--port"){let i=Number(e[n+1]);e[n+1]!==void 0&&Number.isInteger(i)&&i>0&&(this.port=i,n+=1);continue}if(r.startsWith("--port=")){let i=Number(r.slice(7));Number.isInteger(i)&&i>0&&(this.port=i);continue}t.push(r)}return t}gatewayUrlEnv(e){let t=`http://127.0.0.1:${this.port}`;return e===L.Codex?{OPENAI_BASE_URL:`${t}/v1`}:{ANTHROPIC_BASE_URL:t}}async ensureDaemon(){if(await this.health())return!0;let e={...process.env,PORT:String(this.port),INJECT:process.env.INJECT||"1",GATE:process.env.GATE||"0",CAPTURE:process.env.CAPTURE||"0",DZ_STATE_FILE:process.env.DZ_STATE_FILE||S.default.join(process.cwd(),".dezycro","companion-state.json")};(0,H.spawn)(process.execPath,[this.proxyPath],{env:e,detached:!0,stdio:"ignore"}).unref();for(let n=0;n<30;n+=1){if(await this.health())return!0;await o.sleep(100)}return!1}setupShims(){if(process.env.DZ_NO_SHIM==="1"||process.env.DZ_SHIMS==="0")return null;let e=(process.env.DZ_SHIM_CMDS||o.DEFAULT_SHIM_CMDS.join(",")).split(",").map(t=>t.trim()).filter(Boolean);if(!e.length)return null;try{let t=E.default.mkdtempSync(S.default.join(ie.default.tmpdir(),"dz-shims-")),n=process.env.PATH||"";for(let r of e){let i=`#!/usr/bin/env bash
|
|
3
3
|
real="$(PATH="$DZ_REAL_PATH" command -v ${r} 2>/dev/null)"
|
|
4
4
|
if [ -z "$real" ] || [ "\${DZ_NO_SHIM:-0}" = "1" ]; then exec "${r}" "$@"; fi
|
|
5
5
|
out="$("$real" "$@" 2>&1)"; rc=$?
|
|
6
6
|
printf '%s\\n' "$out" | DZ_CMD=${r} node "$DZ_COMPACT_PATH"
|
|
7
7
|
exit $rc
|
|
8
|
-
`;E.default.writeFileSync(
|
|
8
|
+
`;E.default.writeFileSync(S.default.join(t,r),i,{mode:493})}return{dir:t,env:{PATH:`${t}${S.default.delimiter}${n}`,DZ_REAL_PATH:n,DZ_COMPACT_PATH:this.compactPath}}}catch{return null}}health(){return this.get(J.Health)}control(e){return this.get(e,()=>!0)}get(e,t=n=>n.statusCode===200){return new Promise(n=>{let r=se.default.get({host:"127.0.0.1",port:this.port,path:e,timeout:800},i=>{i.resume(),n(t(i))});r.on("error",()=>n(!1)),r.on("timeout",()=>{r.destroy(),n(!1)})})}static sleep(e){return new Promise(t=>setTimeout(t,e))}};var ae=require("child_process"),W=l(require("fs")),ce=l(require("os")),Z=l(require("path")),le=l(require("readline")),A=class o{static MCP_URL="https://mcp.dezycro.ai";run(){console.log(`
|
|
9
9
|
Dezycro Setup`),console.log(` Get your API key from Dezycro \u2192 Settings \u2192 API Keys
|
|
10
|
-
`);let e=
|
|
10
|
+
`);let e=le.default.createInterface({input:process.stdin,output:process.stdout});e.question(" API key (dzy_...): ",t=>{let n=(t||"").trim();(!n||!n.startsWith("dzy_"))&&(console.log(`
|
|
11
11
|
Invalid key. Must start with dzy_
|
|
12
12
|
`),e.close(),process.exit(1)),this.configureMcp(n),this.saveToken(n),console.log(`
|
|
13
13
|
You're all set! In Claude Code run: /dezycro:import-features
|
|
14
|
-
`),e.close()})}configureMcp(e){let t=`claude mcp add --transport http dezycro "${o.MCP_URL}/mcp" --header "Authorization: Bearer ${e}"`;try{(0,
|
|
14
|
+
`),e.close()})}configureMcp(e){let t=`claude mcp add --transport http dezycro "${o.MCP_URL}/mcp" --header "Authorization: Bearer ${e}"`;try{(0,ae.execSync)(t,{stdio:"inherit"}),console.log(`
|
|
15
15
|
\u2713 Dezycro MCP configured`)}catch{console.error(`
|
|
16
16
|
Failed to configure MCP. Run manually:`),console.error(` ${t}
|
|
17
|
-
`)}}saveToken(e){try{let t=
|
|
18
|
-
`)}}};var
|
|
17
|
+
`)}}saveToken(e){try{let t=Z.default.join(ce.default.homedir(),".dezycro"),n=Z.default.join(t,"token.json");W.default.mkdirSync(t,{recursive:!0,mode:448}),W.default.writeFileSync(n,JSON.stringify({token:e},null,2),{mode:384}),console.log(` \u2713 Companion token saved to ${n}`)}catch(t){console.error(` Failed to save companion token: ${t.message}`),console.error(` Drop it manually at ~/.dezycro/token.json: {"token": "${e}"}
|
|
18
|
+
`)}}};var $=l(require("fs")),ue=l(require("os")),pe=require("child_process"),D=class o{static DEFAULT_PATH=".dezycro/auth.local.json";static PLACEHOLDER=/\$\{([A-Z0-9_]+)\}/g;run(e=o.DEFAULT_PATH){if(!$.default.existsSync(e)){this.warn(`no auth file at ${e}; emitting nothing`);return}let t;try{t=JSON.parse($.default.readFileSync(e,"utf8"))}catch(i){this.warn(`parse error in ${e}: ${i.message}`),process.exit(1)}let n=this.resolveSecrets(t.secrets||{}),r=this.personaExports(t.personas||{},n);r.length&&process.stdout.write(r.join(`
|
|
19
19
|
`)+`
|
|
20
|
-
`)}resolveSecrets(e){let t={};for(let[n,r]of Object.entries(e))try{t[n]=this.resolveSecret(r)}catch(i){this.warn(`secret ${n}: ${i.message}`)}return t}personaExports(e,t){let n=[];for(let[r,i]of Object.entries(e))if(!(!i||!i.envVar))try{let s=this.substitute(i.config||{},t);n.push(`export ${i.envVar}=${o.shQuote(JSON.stringify(s))}`)}catch(s){this.warn(`persona '${r}' skipped: ${s.message}`)}return n}resolveSecret(e){if(typeof e!="string")return e;if(e.startsWith("!file:")){let t=e.slice(6);return(t==="~"||t.startsWith("~/"))&&(t=
|
|
21
|
-
`)}static shQuote(e){return"'"+String(e).replaceAll("'","'\\''")+"'"}};var
|
|
20
|
+
`)}resolveSecrets(e){let t={};for(let[n,r]of Object.entries(e))try{t[n]=this.resolveSecret(r)}catch(i){this.warn(`secret ${n}: ${i.message}`)}return t}personaExports(e,t){let n=[];for(let[r,i]of Object.entries(e))if(!(!i||!i.envVar))try{let s=this.substitute(i.config||{},t);n.push(`export ${i.envVar}=${o.shQuote(JSON.stringify(s))}`)}catch(s){this.warn(`persona '${r}' skipped: ${s.message}`)}return n}resolveSecret(e){if(typeof e!="string")return e;if(e.startsWith("!file:")){let t=e.slice(6);return(t==="~"||t.startsWith("~/"))&&(t=ue.default.homedir()+t.slice(1)),$.default.readFileSync(t,"utf8").trimEnd()}if(e.startsWith("!env:")){let t=e.slice(5);if(!(t in process.env))throw new Error(`env var not set: ${t}`);return process.env[t]}if(e.startsWith("!cmd:")){let t=e.slice(5),r=(0,pe.execSync)(t,{encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trimEnd();if(!r)throw new Error(`empty stdout from: ${t}`);return r}return e}substitute(e,t){if(typeof e=="string")return e.replace(o.PLACEHOLDER,(n,r)=>{if(!(r in t))throw new Error(`\${${r}} not in secrets`);return String(t[r])});if(Array.isArray(e))return e.map(n=>this.substitute(n,t));if(e&&typeof e=="object"){let n={};for(let[r,i]of Object.entries(e))n[r]=this.substitute(i,t);return n}return e}warn(e){process.stderr.write(`[dezycro resolve-auth] ${e}
|
|
21
|
+
`)}static shQuote(e){return"'"+String(e).replaceAll("'","'\\''")+"'"}};var P=class o{run(e){let t=e[0],n=o.scriptFor(t);n===null&&(process.stderr.write(`Usage: dezycro completion <bash|zsh|fish>
|
|
22
22
|
`),process.stderr.write(`Then load it in your shell, e.g.: eval "$(dezycro completion zsh)"
|
|
23
23
|
`),process.exit(2)),process.stdout.write(n)}static scriptFor(e){switch(e){case"bash":return o.bash();case"zsh":return o.zsh();case"fish":return o.fish();default:return null}}static bash(){return["_dezycro() {"," local cur prev cmd",' cur="${COMP_WORDS[COMP_CWORD]}"',' prev="${COMP_WORDS[COMP_CWORD-1]}"',' if [ "$COMP_CWORD" -eq 1 ]; then',' COMPREPLY=( $(compgen -W "run setup resolve-auth completion help --help" -- "$cur") )'," return 0"," fi",' cmd="${COMP_WORDS[1]}"',' case "$cmd" in'," run)",' if [ "$prev" = "--port" ]; then return 0; fi',' COMPREPLY=( $(compgen -W "claude --status --stop --port" -- "$cur") ) ;;'," completion)",' COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ) ;;'," resolve-auth)",' COMPREPLY=( $(compgen -f -- "$cur") ) ;;'," esac"," return 0","}","complete -F _dezycro dezycro",""].join(`
|
|
24
24
|
`)}static zsh(){return["_dezycro() {"," local -a commands"," commands=(run setup resolve-auth completion help)"," if (( CURRENT == 2 )); then"," compadd -- $commands"," return"," fi"," case ${words[2]} in"," run)"," if [[ ${words[CURRENT-1]} == --port ]]; then return; fi"," compadd -- claude --status --stop --port ;;"," completion) compadd -- bash zsh fish ;;"," resolve-auth) _files ;;"," esac","}","compdef _dezycro dezycro",""].join(`
|
|
25
25
|
`)}static fish(){return["complete -c dezycro -f","complete -c dezycro -n __fish_use_subcommand -a run -d 'Run an agent through the gateway'","complete -c dezycro -n __fish_use_subcommand -a setup -d 'Configure the Dezycro MCP connection'","complete -c dezycro -n __fish_use_subcommand -a resolve-auth -d 'Print verifier credential exports'","complete -c dezycro -n __fish_use_subcommand -a completion -d 'Print a shell completion script'","complete -c dezycro -n '__fish_seen_subcommand_from run' -a 'claude --status --stop --port'","complete -c dezycro -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'",""].join(`
|
|
26
|
-
`)}};var
|
|
27
|
-
`)}catch{}}static prepare(e){if(!o.enabled())return null;try{let t=o.dir();return
|
|
28
|
-
`))}static aggregate(e){let t=o.empty();for(let n of e){let r=o.parse(n);r&&(r.type==="turn"?o.addTurn(t,r):r.type==="compaction"&&o.addCompaction(t,r),o.trackTime(t,r.ts))}return t}static addTurn(e,t){e.turns+=1,e.inputTokens+=o.num(t.inputTokens),e.outputTokens+=o.num(t.outputTokens),e.cacheReadTokens+=o.num(t.cacheReadTokens),e.cacheCreationTokens+=o.num(t.cacheCreationTokens),e.totalDurationMs+=o.num(t.durationMs),e.gateBlocks+=o.num(t.blockedToolCalls);let n=o.num(t.freshToolChars);if(e.maxFreshToolChars=Math.max(e.maxFreshToolChars,n),n>o.BIG_TOOL_CHARS&&(e.bigToolTurns+=1),e.maxToolDefChars=Math.max(e.maxToolDefChars,o.num(t.toolDefChars)),e.maxSystemChars=Math.max(e.maxSystemChars,o.num(t.systemChars)),e.tailResultChars+=o.num(t.tailResultChars),e.tailTextChars+=o.num(t.tailTextChars),e.maxCacheCreationTokens=Math.max(e.maxCacheCreationTokens,o.num(t.cacheCreationTokens)),typeof t.status=="number"&&t.status!==200&&(e.errorTurns+=1),o.bump(e.models,typeof t.model=="string"?t.model:"(unknown)"),Array.isArray(t.skillsTriggered))for(let r of t.skillsTriggered)o.bump(e.skillsTriggered,String(r))}static addCompaction(e,t){e.compactions+=1,e.charsSaved+=o.num(t.charsSaved),e.estTokensSaved+=o.num(t.estTokensSaved),e.linesElided+=o.num(t.elidedLines),o.bump(e.commands,typeof t.command=="string"?t.command:"(unknown)")}static trackTime(e,t){typeof t=="string"&&((!e.firstTs||t<e.firstTs)&&(e.firstTs=t),(!e.lastTs||t>e.lastTs)&&(e.lastTs=t))}static parse(e){if(!e.trim())return null;try{return JSON.parse(e)}catch{return null}}static bump(e,t){e[t]=(e[t]??0)+1}static num(e){return typeof e=="number"&&Number.isFinite(e)?e:0}static empty(){return{turns:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalDurationMs:0,errorTurns:0,gateBlocks:0,maxFreshToolChars:0,bigToolTurns:0,maxToolDefChars:0,maxSystemChars:0,tailResultChars:0,tailTextChars:0,maxCacheCreationTokens:0,models:{},skillsTriggered:{},compactions:0,charsSaved:0,estTokensSaved:0,linesElided:0,commands:{},firstTs:null,lastTs:null}}};var u=l(require("node:fs")),
|
|
29
|
-
`);if(a<0)return{lines:[],newOffset:e};let c=s.slice(0,a+1),
|
|
30
|
-
`).filter(
|
|
26
|
+
`)}};var ve=l(require("node:path"));var z=l(require("node:fs")),de=l(require("node:os")),y=l(require("node:path")),$e="DZ_METRICS_DIR",De="DZ_METRICS",k=class o{file;constructor(e="gateway"){this.file=o.prepare(e)}static enabled(){return process.env[De]!=="0"}static dir(){let e=process.env[$e];if(e)return e;let t=de.homedir();switch(process.platform){case"win32":return y.join(process.env.LOCALAPPDATA||y.join(t,"AppData","Local"),"Dezycro","metrics");case"darwin":return y.join(t,"Library","Application Support","Dezycro","metrics");default:return y.join(process.env.XDG_STATE_HOME||y.join(t,".local","state"),"dezycro","metrics")}}record(e){if(this.file)try{z.appendFileSync(this.file,`${JSON.stringify({ts:new Date().toISOString(),...e})}
|
|
27
|
+
`)}catch{}}static prepare(e){if(!o.enabled())return null;try{let t=o.dir();return z.mkdirSync(t,{recursive:!0}),y.join(t,`${e}.jsonl`)}catch{return null}}};var Pe={rules:[{nameIncludes:ne.UpdateTask,inputField:re.Status,inputValues:[j.Done,j.Pushed],requireAll:[f.SpecPublished,f.VerifiedRecently],reason:"Cannot mark a task DONE/PUSHED yet."}]},G=class o{constructor(e,t){this.policy=e;this.state=t}policy;state;static STATE_PREDICATES={[f.SpecPublished]:e=>!!e&&e.needsSpecPublish!==!0,[f.VerifiedRecently]:e=>!e||!e.lastVerifyCompletionTs?!1:e.lastEditTs?Date.parse(e.lastVerifyCompletionTs)>=Date.parse(e.lastEditTs):!0};static FAILED_HINT={[f.SpecPublished]:"spec not published (controller/API changed) \u2014 run /dezycro:publish-spec",[f.VerifiedRecently]:"no passing verify since last edit \u2014 run /dezycro:verify"};decide(e,t){for(let n of this.policy?.rules??[]){if(!e||!e.includes(n.nameIncludes))continue;if(n.inputField){let a=t?.[n.inputField],c=(n.inputValues??[]).map(T=>String(T).toUpperCase());if(a==null||!c.includes(String(a).toUpperCase()))continue}let r=n.requireAll??[];if(r.length===0)return this.blocked(e,n.reason);let i=r.filter(a=>!o.STATE_PREDICATES[a]?.(this.state));if(i.length===0)continue;let s=i.map(a=>o.FAILED_HINT[a]||a).join("; ");return this.blocked(e,`${n.reason||"Blocked by Dezycro policy."} ${s}.`)}return null}blocked(e,t){return{toolName:e||"?",detail:t||"Blocked by Dezycro policy."}}};var st=new Set([h.ContentBlockStart,h.ContentBlockDelta,h.ContentBlockStop]);var fe=l(require("node:fs")),ge=l(require("node:path"));var v=class o{static BIG_TOOL_CHARS=4e3;static defaultFile(){return ge.join(k.dir(),"gateway.jsonl")}static fromFile(e=o.defaultFile()){let t;try{t=fe.readFileSync(e,"utf8")}catch{return o.empty()}return o.aggregate(t.split(`
|
|
28
|
+
`))}static aggregate(e){let t=o.empty();for(let n of e){let r=o.parse(n);r&&(r.type==="turn"?o.addTurn(t,r):r.type==="compaction"&&o.addCompaction(t,r),o.trackTime(t,r.ts))}return t}static addTurn(e,t){e.turns+=1,e.inputTokens+=o.num(t.inputTokens),e.outputTokens+=o.num(t.outputTokens),e.cacheReadTokens+=o.num(t.cacheReadTokens),e.cacheCreationTokens+=o.num(t.cacheCreationTokens),e.totalDurationMs+=o.num(t.durationMs),e.gateBlocks+=o.num(t.blockedToolCalls);let n=o.num(t.freshToolChars);if(e.maxFreshToolChars=Math.max(e.maxFreshToolChars,n),n>o.BIG_TOOL_CHARS&&(e.bigToolTurns+=1),e.maxToolDefChars=Math.max(e.maxToolDefChars,o.num(t.toolDefChars)),e.maxSystemChars=Math.max(e.maxSystemChars,o.num(t.systemChars)),e.tailResultChars+=o.num(t.tailResultChars),e.tailTextChars+=o.num(t.tailTextChars),e.maxCacheCreationTokens=Math.max(e.maxCacheCreationTokens,o.num(t.cacheCreationTokens)),typeof t.status=="number"&&t.status!==200&&(e.errorTurns+=1),o.bump(e.models,typeof t.model=="string"?t.model:"(unknown)"),Array.isArray(t.skillsTriggered))for(let r of t.skillsTriggered)o.bump(e.skillsTriggered,String(r))}static addCompaction(e,t){e.compactions+=1,e.charsSaved+=o.num(t.charsSaved),e.estTokensSaved+=o.num(t.estTokensSaved),e.linesElided+=o.num(t.elidedLines),o.bump(e.commands,typeof t.command=="string"?t.command:"(unknown)")}static trackTime(e,t){typeof t=="string"&&((!e.firstTs||t<e.firstTs)&&(e.firstTs=t),(!e.lastTs||t>e.lastTs)&&(e.lastTs=t))}static parse(e){if(!e.trim())return null;try{return JSON.parse(e)}catch{return null}}static bump(e,t){e[t]=(e[t]??0)+1}static num(e){return typeof e=="number"&&Number.isFinite(e)?e:0}static empty(){return{turns:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalDurationMs:0,errorTurns:0,gateBlocks:0,maxFreshToolChars:0,bigToolTurns:0,maxToolDefChars:0,maxSystemChars:0,tailResultChars:0,tailTextChars:0,maxCacheCreationTokens:0,models:{},skillsTriggered:{},compactions:0,charsSaved:0,estTokensSaved:0,linesElided:0,commands:{},firstTs:null,lastTs:null}}};var u=l(require("node:fs")),ye=l(require("node:path")),C=class o{apiUrl;workspaceId;token;metricsFile;cursorFile;batchSize;timeoutMs;fetchImpl;constructor(e){this.apiUrl=e.apiUrl,this.workspaceId=e.workspaceId,this.token=e.token,this.metricsFile=e.metricsFile,this.cursorFile=e.cursorFile,this.batchSize=e.batchSize??1e3,this.timeoutMs=e.timeoutMs??15e3,this.fetchImpl=e.fetchImpl??fetch}async upload({dryRun:e=!1}={}){let{lines:t,newOffset:n}=this.readNewLines(),r=o.toBatch(t),i=r.turns.length+r.compactions.length,s={turns:r.turns.length,compactions:r.compactions.length,newOffset:n,sent:!1,dryRun:e};return e?s:i===0?(n!==this.readCursor()&&this.writeCursor(n),s):(await this.postAll(r),this.writeCursor(n),{...s,sent:!0})}static toBatch(e){let t=[],n=[];for(let r of e){let i;try{i=JSON.parse(r)}catch{continue}typeof i.ts=="string"&&(i.type==="turn"?t.push(o.mapTurn(i)):i.type==="compaction"&&n.push(o.mapCompaction(i)))}return{turns:t,compactions:n}}static mapTurn(e){return{timestamp:e.ts,provider:e.provider??null,model:e.model??null,status:e.status??null,durationMs:e.durationMs??null,inputTokens:e.inputTokens??null,outputTokens:e.outputTokens??null,cacheReadTokens:e.cacheReadTokens??null,cacheCreationTokens:e.cacheCreationTokens??null,toolDefChars:e.toolDefChars??null,systemChars:e.systemChars??null,freshToolChars:e.freshToolChars??null,tailResultChars:e.tailResultChars??null,tailTextChars:e.tailTextChars??null,blockedToolCalls:e.blockedToolCalls??null,skillsFired:Array.isArray(e.skillsTriggered)?e.skillsTriggered:[]}}static mapCompaction(e){return{timestamp:e.ts,command:e.command??null,originalChars:e.originalChars??null,outputChars:e.outputChars??null,charsSaved:e.charsSaved??null,estTokensSaved:e.estTokensSaved??null,originalLines:e.originalLines??null,outputLines:e.outputLines??null,elidedLines:e.elidedLines??null}}readNewLines(){let e=this.readCursor(),t;try{t=u.statSync(this.metricsFile).size}catch{return{lines:[],newOffset:e}}if(e>t&&(e=0),e===t)return{lines:[],newOffset:e};let n=u.openSync(this.metricsFile,"r");try{let r=t-e,i=Buffer.alloc(r);u.readSync(n,i,0,r,e);let s=i.toString("utf8"),a=s.lastIndexOf(`
|
|
29
|
+
`);if(a<0)return{lines:[],newOffset:e};let c=s.slice(0,a+1),T=e+Buffer.byteLength(c,"utf8");return{lines:c.split(`
|
|
30
|
+
`).filter(b=>b.trim().length>0),newOffset:T}}finally{u.closeSync(n)}}readCursor(){try{let e=JSON.parse(u.readFileSync(this.cursorFile,"utf8"));return typeof e.offset=="number"&&e.offset>=0?e.offset:0}catch{return 0}}writeCursor(e){try{u.mkdirSync(ye.dirname(this.cursorFile),{recursive:!0}),u.writeFileSync(this.cursorFile,JSON.stringify({offset:e,uploadedAt:new Date().toISOString()}))}catch{}}async postAll(e){for(let t=0;t<e.turns.length;t+=this.batchSize)await this.postBatch({turns:e.turns.slice(t,t+this.batchSize),compactions:[]});for(let t=0;t<e.compactions.length;t+=this.batchSize)await this.postBatch({turns:[],compactions:e.compactions.slice(t,t+this.batchSize)})}async postBatch(e){let t=`${this.apiUrl}/api/v1/workspaces/${this.workspaceId}/usage-telemetry`,n=new AbortController,r=setTimeout(()=>n.abort(),this.timeoutMs);try{let i=await this.fetchImpl(t,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"content-type":"application/json",accept:"application/json"},body:JSON.stringify(e),signal:n.signal});if(!i.ok)throw new Error(`usage-telemetry upload returned ${i.status}`)}finally{clearTimeout(r)}}};var M=l(require("fs")),ke=l(require("os")),p=l(require("path")),d=class o{constructor(e){this.repoRoot=e;this.config=o.readJson(p.join(e,".dezycro","config.json"))}repoRoot;config;static at(e=process.cwd()){return new o(o.findRepoRoot(e)??p.resolve(e))}get workspaceId(){return o.env("DZ_WORKSPACE_ID")??o.str(this.config?.workspaceId)}get tenantId(){return o.env("DZ_TENANT_ID")??o.str(this.config?.tenantId)}get projectId(){return o.str(this.config?.projectId)}get apiUrl(){let e=o.env("DZ_API_URL");if(e)return o.trimSlash(e);let t=Array.isArray(this.config?.environments)?this.config?.environments:[],n=t.find(i=>o.isObj(i)&&i.default===!0)??t[0],r=o.isObj(n)?o.str(n.apiUrl):null;return r?o.trimSlash(r):null}get token(){let e=o.env("DZ_TOKEN");if(e)return e;let t=[p.join(this.repoRoot,".dezycro","companion-token.local.json"),p.join(ke.homedir(),".dezycro","token.json")];for(let n of t){let r=o.str(o.readJson(n)?.token);if(r)return r}return null}activeFeature(){let e=o.readJson(p.join(this.repoRoot,".dezycro","active-feature.json")),t=o.str(e?.featureId);if(!t)return null;let n=o.str(e?.workspaceId)??this.workspaceId;return n?{workspaceId:n,featureId:t,active:e?.active===!0}:null}connection(){let e=this.apiUrl;if(!e)return{error:"no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)"};let t=this.workspaceId;if(!t)return{error:"no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)"};let n=this.token;return n?{apiUrl:e,workspaceId:t,token:n}:{error:"no token (run `dezycro setup` or set DZ_TOKEN)"}}static findRepoRoot(e){let t=p.resolve(e||process.cwd());for(let n=0;n<64;n+=1){if(M.existsSync(p.join(t,".dezycro")))return t;let r=p.dirname(t);if(r===t)return null;t=r}return null}static readJson(e){try{return JSON.parse(M.readFileSync(e,"utf8"))}catch{return null}}static isObj(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}static str(e){return typeof e=="string"&&e.length>0?e:null}static env(e){let t=process.env[e];return t&&t.length>0?t:null}static trimSlash(e){return e.replace(/\/$/,"")}};var O=class o{run(e){if(e.includes("--upload-dry-run")){this.uploadDryRun();return}let t=v.defaultFile(),n=v.fromFile(t);if(e.includes("--json")){process.stdout.write(`${JSON.stringify(n,null,2)}
|
|
31
31
|
`);return}process.stdout.write(o.format(n,t))}async uploadDryRun(){let e=c=>{process.stdout.write(`${c}
|
|
32
|
-
`)},t=
|
|
32
|
+
`)},t=d.at(process.cwd()),n=t.connection(),r=v.defaultFile(),i=ve.join(k.dir(),"gateway.upload-cursor.json"),s="error"in n?{apiUrl:"(unset)",workspaceId:t.workspaceId??"(unset)",token:"(unset)"}:n,a=await new C({...s,metricsFile:r,cursorFile:i}).upload({dryRun:!0});e("Dezycro telemetry \u2014 dry run (nothing sent)"),e(` metrics file ${r}`),e(` target ${s.apiUrl}/api/v1/workspaces/${s.workspaceId}/usage-telemetry`),"error"in n&&e(` connection NOT configured \u2014 ${n.error}`),e(` pending upload ${a.turns} turn + ${a.compactions} compaction event(s) since the cursor`)}static format(e,t){if(e.turns===0&&e.compactions===0)return`No metrics yet at ${t}
|
|
33
33
|
(They're written as you run agents through \`dezycro run\`. DZ_METRICS=0 disables.)
|
|
34
34
|
`;let n=c=>c.toLocaleString("en-US"),r=c=>n(Math.round(c/4)),i=c=>e.turns>0?n(Math.round(c/e.turns)):"0",s=[];s.push("Dezycro gateway metrics"),s.push(` ${t}`),e.firstTs&&e.lastTs&&s.push(` ${e.firstTs} \u2192 ${e.lastTs}`),s.push(""),s.push(`Turns: ${n(e.turns)} errors: ${n(e.errorTurns)} model time: ${(e.totalDurationMs/1e3).toFixed(0)}s`);let a=[`fresh input ~${i(e.inputTokens)} tok/turn`,`output ~${i(e.outputTokens)} tok/turn`];if(e.cacheReadTokens+e.inputTokens>0){let c=100*e.cacheReadTokens/(e.cacheReadTokens+e.inputTokens);a.push(`cache hit ${c.toFixed(1)}%`)}return s.push(a.join(" \xB7 ")),s.push(""),s.push("Where the tokens are (per request \u2014 content-free)"),s.push(` tool definitions ${n(e.maxToolDefChars)} chars (~${r(e.maxToolDefChars)} tok) \u2190 fixed per-request cost; schema-bloat lever`),s.push(` system prompt ${n(e.maxSystemChars)} chars (~${r(e.maxSystemChars)} tok)`),s.push(` fresh tool results ${n(e.tailResultChars)} chars (~${r(e.tailResultChars)} tok) total \xB7 biggest single ${n(e.maxFreshToolChars)} chars (~${r(e.maxFreshToolChars)} tok)`),s.push(` fresh user text ${n(e.tailTextChars)} chars (~${r(e.tailTextChars)} tok) total`),s.push(""),s.push("Cache"),s.push(` read ${n(e.cacheReadTokens)} tok \xB7 create ${n(e.cacheCreationTokens)} tok`),e.maxCacheCreationTokens>0&&s.push(` largest prefix write ${n(e.maxCacheCreationTokens)} tok (a spike here = cache-bust \u2014 the costly case)`),s.push(""),s.push("Request-side compaction opportunity (not yet compacted)"),s.push(` turns with >1k-tok fresh tool output ${n(e.bigToolTurns)}`),s.push(" (fresh tool results are the safe headroom; reversible elision is the lever \u2014 needs sizing first)"),s.push(""),s.push("Source-side compaction (shell output \u2014 already applied)"),s.push(` est. tokens saved ${n(e.estTokensSaved)} over ${n(e.compactions)} compaction(s)${o.breakdown(e.commands)}`),s.push(` lines elided ${n(e.linesElided)}`),s.push(""),s.push(`Totals: input ${n(e.inputTokens)} \xB7 output ${n(e.outputTokens)} \xB7 cache read ${n(e.cacheReadTokens)} \xB7 cache create ${n(e.cacheCreationTokens)}`),s.push(`Gate blocks: ${n(e.gateBlocks)}`),s.push(`Skills fired:${o.breakdown(e.skillsTriggered)||" none"}`),s.push(`Models:${o.breakdown(e.models)||" (none)"}`),s.push(""),`${s.join(`
|
|
35
35
|
`)}
|
|
36
|
-
`}static breakdown(e){let t=Object.entries(e).sort((n,r)=>r[1]-n[1]);return t.length===0?"":` ${t.map(([n,r])=>`${n}=${r}`).join(", ")}`}};var
|
|
36
|
+
`}static breakdown(e){let t=Object.entries(e).sort((n,r)=>r[1]-n[1]);return t.length===0?"":` ${t.map(([n,r])=>`${n}=${r}`).join(", ")}`}};var q=class{now(){return Date.now()}nowIso(){return new Date().toISOString()}sleep(e){return new Promise(t=>setTimeout(t,e))}},Y=class{constructor(e,t,n,r,i){this.apiUrl=e;this.workspaceId=t;this.featureId=n;this.token=r;this.eventTypes=i}apiUrl;workspaceId;featureId;token;eventTypes;async wait(e,t){let n=new URL(`${this.apiUrl}/api/v1/workspaces/${this.workspaceId}/features/${this.featureId}/inbox/wait`);this.eventTypes&&n.searchParams.set("eventTypes",this.eventTypes),n.searchParams.set("since",e),n.searchParams.set("timeoutSec",String(t));let r=new AbortController,i=setTimeout(()=>r.abort(),(t+10)*1e3);try{let s=await fetch(n.toString(),{method:"GET",headers:{Authorization:`Bearer ${this.token}`,Accept:"application/json"},signal:r.signal});if(!s.ok)throw new Error(`inbox/wait returned ${s.status}`);let a=await s.json();return{nextCursor:typeof a.nextCursor=="string"?a.nextCursor:e,events:Array.isArray(a.events)?a.events:[]}}finally{clearTimeout(i)}}},U=class{constructor(e,t=30,n=1e3){this.totalTimeoutSec=e;this.perPollSec=t;this.errorBackoffMs=n}totalTimeoutSec;perPollSec;errorBackoffMs;static DEFAULT_TOTAL_TIMEOUT_SEC=600},K=class{constructor(e,t,n){this.gateway=e;this.clock=t;this.policy=n}gateway;clock;policy;async awaitEvent(){let e=this.clock.now()+this.policy.totalTimeoutSec*1e3,t=this.clock.nowIso(),n=!1,r="";for(;this.clock.now()<e;){let i=Math.ceil((e-this.clock.now())/1e3);if(i<=0)break;let s=Math.min(this.policy.perPollSec,i);try{let a=await this.gateway.wait(t,s);n=!0;let[c]=a.events;if(c)return{outcome:"EVENT",event:c};t=a.nextCursor||t}catch(a){r=a instanceof Error?a.message:String(a),this.clock.now()<e&&await this.clock.sleep(this.policy.errorBackoffMs)}}return n?{outcome:"TIMEOUT"}:{outcome:"ERROR",error:r||"event wait failed"}}},X=class{constructor(e,t,n){this.result=e;this.eventTypes=t;this.timeoutSec=n}result;eventTypes;timeoutSec;render(){switch(this.result.outcome){case"EVENT":return JSON.stringify(this.result.event);case"TIMEOUT":return`TIMEOUT: no ${this.eventTypes??"any"} event within ${this.timeoutSec}s.`;case"ERROR":return`ERROR: ${this.result.error??"unknown"}`}}exitCode(){switch(this.result.outcome){case"EVENT":return 0;case"TIMEOUT":return 5;case"ERROR":return 2}}},Q=class o{constructor(e,t,n,r){this.eventTypes=e;this.feature=t;this.timeoutSec=n;this.json=r}eventTypes;feature;timeoutSec;json;static fromArgv(e){let t=null,n=null,r=U.DEFAULT_TOTAL_TIMEOUT_SEC,i=!1;for(let s=0;s<e.length;s+=1){let a=e[s];if(a==="--event-types")t=e[s+1]??null,s+=1;else if(a==="--feature")n=e[s+1]??null,s+=1;else if(a==="--timeout"){let c=Number(e[s+1]);Number.isFinite(c)&&c>0&&(r=c),s+=1}else a==="--json"&&(i=!0)}return new o(t,n,r,i)}},B=class{constructor(e=new q){this.clock=e}clock;async run(e){let t=Q.fromArgv(e),n=d.at(process.cwd()),r=n.connection();if("error"in r){this.fail(`not configured \u2014 ${r.error}`);return}let i=t.feature??n.activeFeature()?.featureId??null;if(!i){this.fail("no feature (pass --feature <id>, or run /dezycro:work to bind one)");return}let s=new Y(r.apiUrl,r.workspaceId,i,r.token,t.eventTypes),a=await new K(s,this.clock,new U(t.timeoutSec)).awaitEvent(),c=new X(a,t.eventTypes,t.timeoutSec);process.stdout.write(`${t.json?JSON.stringify(a):c.render()}
|
|
37
37
|
`),process.exit(c.exitCode())}fail(e){process.stderr.write(`dezycro await-event: ${e}
|
|
38
|
-
`),process.exit(2)}};var
|
|
38
|
+
`),process.exit(2)}};var m=l(require("fs")),Te=l(require("path"));var _=class{file;constructor(e){this.file=Te.join(e,".dezycro","active-feature.json")}read(){try{let e=JSON.parse(m.readFileSync(this.file,"utf8")),t=typeof e.featureId=="string"?e.featureId:null;return t?{featureId:t,featureName:typeof e.featureName=="string"?e.featureName:void 0,workspaceId:typeof e.workspaceId=="string"?e.workspaceId:void 0,selectedAt:typeof e.selectedAt=="string"?e.selectedAt:void 0,active:e.active===!0}:null}catch{return null}}setActive(e){let t=this.read();if(!t||(t.active??!1)===e)return!1;try{return m.writeFileSync(this.file,`${JSON.stringify({...t,active:e},null,2)}
|
|
39
|
+
`),!0}catch{return!1}}clear(){try{if(m.existsSync(this.file))return m.unlinkSync(this.file),!0}catch{}return!1}};var N=class{run(e){let t=d.at(process.cwd()).repoRoot,n=new _(t);if(e.includes("--clear")){let s=n.read()!==null;n.clear(),process.stdout.write(s?`dezycro: active feature cleared.
|
|
40
|
+
`:`dezycro: no active feature to clear.
|
|
41
|
+
`);return}let r=n.read();if(!r){process.stdout.write(`dezycro: no active feature bound (run /dezycro:work to bind one).
|
|
42
|
+
`);return}let i=r.featureName?`${r.featureName} (${r.featureId})`:r.featureId;process.stdout.write(`dezycro feature: ${i}
|
|
43
|
+
active this session: ${r.active?"yes":"no"}
|
|
44
|
+
`)}};var we=(a=>(a.Run="run",a.Setup="setup",a.ResolveAuth="resolve-auth",a.Completion="completion",a.Metrics="metrics",a.AwaitEvent="await-event",a.Feature="feature",a))(we||{}),Ue=new Set(["help","-h","--help"]),be=`dezycro \u2014 Dezycro for AI coding agents
|
|
39
45
|
|
|
40
46
|
Usage: dezycro <command> [args]
|
|
41
47
|
|
|
@@ -48,7 +54,8 @@ Commands:
|
|
|
48
54
|
metrics --upload-dry-run Preview the content-free telemetry the daemon would upload (sends nothing)
|
|
49
55
|
await-event --event-types <csv> [--feature <id>] [--timeout <sec>] [--json]
|
|
50
56
|
Block until the command bus pushes a matching event; prints it and exits (0 event / 5 timeout / 2 error)
|
|
57
|
+
feature [--clear] Show the bound feature (and whether it's active this session); --clear wipes it (done with this feature)
|
|
51
58
|
|
|
52
59
|
Gateway control: dezycro run --status dezycro run --stop
|
|
53
|
-
Custom port: dezycro run --port <n> <agent> (overrides DZ_PORT, default 13579)`,
|
|
54
|
-
`),console.error(
|
|
60
|
+
Custom port: dezycro run --port <n> <agent> (overrides DZ_PORT, default 13579)`,ee=class{handlers=new Map([["run",e=>{new I().run(e)}],["setup",()=>new A().run()],["resolve-auth",e=>new D().run(e[0])],["completion",e=>new P().run(e)],["metrics",e=>new O().run(e)],["await-event",e=>{new B().run(e)}],["feature",e=>new N().run(e)]]);run(e){let[t,...n]=e,r=this.handlers.get(t);if(r){r(n);return}if(t===void 0||Ue.has(t)){console.log(be);return}console.error(`dezycro: unknown command '${t}'
|
|
61
|
+
`),console.error(be),process.exit(2)}};new ee().run(process.argv.slice(2));0&&(module.exports={Command});
|
package/dist/dezycro-config.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface PlatformConnection {
|
|
|
24
24
|
export interface ActiveFeature {
|
|
25
25
|
workspaceId: string;
|
|
26
26
|
featureId: string;
|
|
27
|
+
/**
|
|
28
|
+
* Session-scoped: true only while a session is actively working this feature.
|
|
29
|
+
* The nudge gate keys on this; the resume pointer (featureId) persists regardless.
|
|
30
|
+
* Missing/false → treat as inactive (so stale pointers stop nudging).
|
|
31
|
+
*/
|
|
32
|
+
active: boolean;
|
|
27
33
|
}
|
|
28
34
|
export declare class DezycroConfig {
|
|
29
35
|
readonly repoRoot: string;
|
package/dist/dezycro-config.js
CHANGED
|
@@ -109,7 +109,7 @@ class DezycroConfig {
|
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
111
|
const workspaceId = DezycroConfig.str(raw?.workspaceId) ?? this.workspaceId;
|
|
112
|
-
return workspaceId ? { workspaceId, featureId } : null;
|
|
112
|
+
return workspaceId ? { workspaceId, featureId, active: raw?.active === true } : null;
|
|
113
113
|
}
|
|
114
114
|
/** Everything needed for a platform call, or an error naming the first gap. */
|
|
115
115
|
connection() {
|
package/dist/gateway/proxy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var ve=Object.create;var G=Object.defineProperty;var Ce=Object.getOwnPropertyDescriptor;var xe=Object.getOwnPropertyNames;var we=Object.getPrototypeOf,Re=Object.prototype.hasOwnProperty;var Ee=(n,e)=>{for(var t in e)G(n,t,{get:e[t],enumerable:!0})},oe=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of xe(e))!Re.call(n,o)&&o!==t&&G(n,o,{get:()=>e[o],enumerable:!(r=Ce(e,o))||r.enumerable});return n};var c=(n,e,t)=>(t=n!=null?ve(we(n)):{},oe(e||!n||!n.__esModule?G(t,"default",{value:n,enumerable:!0}):t,n)),Ae=n=>oe(G({},"__esModule",{value:!0}),n);var Pe={};Ee(Pe,{Gateway:()=>_,createGatewayServer:()=>Oe});module.exports=Ae(Pe);var ke=c(require("node:http")),Te=c(require("node:fs")),be=c(require("node:path")),Se=require("node:stream");var J=c(require("fs")),se=c(require("os")),g=c(require("path")),A=class n{constructor(e){this.repoRoot=e;this.config=n.readJson(g.join(e,".dezycro","config.json"))}repoRoot;config;static at(e=process.cwd()){return new n(n.findRepoRoot(e)??g.resolve(e))}get workspaceId(){return n.env("DZ_WORKSPACE_ID")??n.str(this.config?.workspaceId)}get tenantId(){return n.env("DZ_TENANT_ID")??n.str(this.config?.tenantId)}get projectId(){return n.str(this.config?.projectId)}get apiUrl(){let e=n.env("DZ_API_URL");if(e)return n.trimSlash(e);let t=Array.isArray(this.config?.environments)?this.config?.environments:[],r=t.find(s=>n.isObj(s)&&s.default===!0)??t[0],o=n.isObj(r)?n.str(r.apiUrl):null;return o?n.trimSlash(o):null}get token(){let e=n.env("DZ_TOKEN");if(e)return e;let t=[g.join(this.repoRoot,".dezycro","companion-token.local.json"),g.join(se.homedir(),".dezycro","token.json")];for(let r of t){let o=n.str(n.readJson(r)?.token);if(o)return o}return null}activeFeature(){let e=n.readJson(g.join(this.repoRoot,".dezycro","active-feature.json")),t=n.str(e?.featureId);if(!t)return null;let r=n.str(e?.workspaceId)??this.workspaceId;return r?{workspaceId:r,featureId:t}:null}connection(){let e=this.apiUrl;if(!e)return{error:"no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)"};let t=this.workspaceId;if(!t)return{error:"no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)"};let r=this.token;return r?{apiUrl:e,workspaceId:t,token:r}:{error:"no token (run `dezycro setup` or set DZ_TOKEN)"}}static findRepoRoot(e){let t=g.resolve(e||process.cwd());for(let r=0;r<64;r+=1){if(J.existsSync(g.join(t,".dezycro")))return t;let o=g.dirname(t);if(o===t)return null;t=o}return null}static readJson(e){try{return JSON.parse(J.readFileSync(e,"utf8"))}catch{return null}}static isObj(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}static str(e){return typeof e=="string"&&e.length>0?e:null}static env(e){let t=process.env[e];return t&&t.length>0?t:null}static trimSlash(e){return e.replace(/\/$/,"")}};var d={MessageStart:"message_start",ContentBlockStart:"content_block_start",ContentBlockDelta:"content_block_delta",ContentBlockStop:"content_block_stop",MessageDelta:"message_delta",MessageStop:"message_stop"},$={User:"user",Assistant:"assistant",System:"system"},y={Text:"text",ToolUse:"tool_use"},H={Text:"text_delta",InputJson:"input_json_delta"},x={ToolUse:"tool_use",EndTurn:"end_turn"};var ie={UpdateTask:"update_task"},ae={Status:"status"},q={Done:"DONE",Pushed:"PUSHED"};var b={SpecPublished:"specPublished",VerifiedRecently:"verifiedRecently"};var w={Anthropic:"anthropic",OpenAI:"openai"};var X={Health:"/__dz/health",Stop:"/__dz/stop"},R={EventStream:"text/event-stream",Json:"application/json"};var le="[Dezycro] If you changed any controller/API files, publish the spec and verify before marking work done.",k=class n{constructor(e,t,r=()=>!0){this.skills=e;this.policyLine=t;this.policyGate=r}skills;policyLine;policyGate;apply(e,t){let r=n.lastUserText(e),o=this.triggeredSkills(r),s=this.build(r);return s&&n.appendToLastUserMessage(e,t,s),{text:s,triggers:o}}triggeredSkills(e){let t=[];for(let r of Object.keys(this.skills??{}))e.includes(r)&&t.push(r);return t}static composition(e){return{toolDefChars:n.toolDefChars(e),systemChars:n.systemChars(e),...n.tailComposition(e)}}static emptyComposition(){return{toolDefChars:0,systemChars:0,tailResultChars:0,tailTextChars:0,freshToolChars:0}}static maxToolResultChars(e){return n.tailComposition(e).freshToolChars}static toolDefChars(e){return Array.isArray(e.tools)&&e.tools.length?JSON.stringify(e.tools).length:0}static systemChars(e){let t=e.system;if(typeof t=="string")return t.length;if(!Array.isArray(t))return 0;let r=0;for(let o of t)o&&typeof o.text=="string"&&(r+=o.text.length);return r}static tailComposition(e){let t=n.lastUserContent(e);if(typeof t=="string")return{tailResultChars:0,tailTextChars:t.length,freshToolChars:0};if(!Array.isArray(t))return{tailResultChars:0,tailTextChars:0,freshToolChars:0};let r=0,o=0,s=0;for(let i of t)if(i)if(i.type==="tool_result"){let a=n.textLength(i.content);r+=a,s=Math.max(s,a)}else typeof i.text=="string"&&(o+=i.text.length);return{tailResultChars:r,tailTextChars:o,freshToolChars:s}}static lastUserContent(e){let t=Array.isArray(e.messages)?e.messages:null;if(!t)return null;let r=t.length-1;for(;r>=0&&t[r].role!==$.User;)r-=1;if(r<0)return null;let o=t[r].content;return typeof o=="string"||Array.isArray(o)?o:null}static textLength(e){if(typeof e=="string")return e.length;if(!Array.isArray(e))return 0;let t=0;for(let r of e)r&&typeof r.text=="string"&&(t+=r.text.length);return t}build(e){let t=[];this.policyLine&&this.policyGate()&&t.push(this.policyLine);for(let[r,o]of Object.entries(this.skills??{}))e.includes(r)&&t.push(o);return t.join(`
|
|
1
|
+
"use strict";var ve=Object.create;var G=Object.defineProperty;var Ce=Object.getOwnPropertyDescriptor;var xe=Object.getOwnPropertyNames;var we=Object.getPrototypeOf,Re=Object.prototype.hasOwnProperty;var Ee=(n,e)=>{for(var t in e)G(n,t,{get:e[t],enumerable:!0})},oe=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of xe(e))!Re.call(n,o)&&o!==t&&G(n,o,{get:()=>e[o],enumerable:!(r=Ce(e,o))||r.enumerable});return n};var c=(n,e,t)=>(t=n!=null?ve(we(n)):{},oe(e||!n||!n.__esModule?G(t,"default",{value:n,enumerable:!0}):t,n)),Ae=n=>oe(G({},"__esModule",{value:!0}),n);var Pe={};Ee(Pe,{Gateway:()=>_,createGatewayServer:()=>Oe});module.exports=Ae(Pe);var ke=c(require("node:http")),Te=c(require("node:fs")),be=c(require("node:path")),Se=require("node:stream");var J=c(require("fs")),se=c(require("os")),g=c(require("path")),A=class n{constructor(e){this.repoRoot=e;this.config=n.readJson(g.join(e,".dezycro","config.json"))}repoRoot;config;static at(e=process.cwd()){return new n(n.findRepoRoot(e)??g.resolve(e))}get workspaceId(){return n.env("DZ_WORKSPACE_ID")??n.str(this.config?.workspaceId)}get tenantId(){return n.env("DZ_TENANT_ID")??n.str(this.config?.tenantId)}get projectId(){return n.str(this.config?.projectId)}get apiUrl(){let e=n.env("DZ_API_URL");if(e)return n.trimSlash(e);let t=Array.isArray(this.config?.environments)?this.config?.environments:[],r=t.find(s=>n.isObj(s)&&s.default===!0)??t[0],o=n.isObj(r)?n.str(r.apiUrl):null;return o?n.trimSlash(o):null}get token(){let e=n.env("DZ_TOKEN");if(e)return e;let t=[g.join(this.repoRoot,".dezycro","companion-token.local.json"),g.join(se.homedir(),".dezycro","token.json")];for(let r of t){let o=n.str(n.readJson(r)?.token);if(o)return o}return null}activeFeature(){let e=n.readJson(g.join(this.repoRoot,".dezycro","active-feature.json")),t=n.str(e?.featureId);if(!t)return null;let r=n.str(e?.workspaceId)??this.workspaceId;return r?{workspaceId:r,featureId:t,active:e?.active===!0}:null}connection(){let e=this.apiUrl;if(!e)return{error:"no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)"};let t=this.workspaceId;if(!t)return{error:"no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)"};let r=this.token;return r?{apiUrl:e,workspaceId:t,token:r}:{error:"no token (run `dezycro setup` or set DZ_TOKEN)"}}static findRepoRoot(e){let t=g.resolve(e||process.cwd());for(let r=0;r<64;r+=1){if(J.existsSync(g.join(t,".dezycro")))return t;let o=g.dirname(t);if(o===t)return null;t=o}return null}static readJson(e){try{return JSON.parse(J.readFileSync(e,"utf8"))}catch{return null}}static isObj(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}static str(e){return typeof e=="string"&&e.length>0?e:null}static env(e){let t=process.env[e];return t&&t.length>0?t:null}static trimSlash(e){return e.replace(/\/$/,"")}};var d={MessageStart:"message_start",ContentBlockStart:"content_block_start",ContentBlockDelta:"content_block_delta",ContentBlockStop:"content_block_stop",MessageDelta:"message_delta",MessageStop:"message_stop"},$={User:"user",Assistant:"assistant",System:"system"},y={Text:"text",ToolUse:"tool_use"},H={Text:"text_delta",InputJson:"input_json_delta"},x={ToolUse:"tool_use",EndTurn:"end_turn"};var ie={UpdateTask:"update_task"},ae={Status:"status"},q={Done:"DONE",Pushed:"PUSHED"};var b={SpecPublished:"specPublished",VerifiedRecently:"verifiedRecently"};var w={Anthropic:"anthropic",OpenAI:"openai"};var X={Health:"/__dz/health",Stop:"/__dz/stop"},R={EventStream:"text/event-stream",Json:"application/json"};var le="[Dezycro] If you changed any controller/API files, publish the spec and verify before marking work done.",k=class n{constructor(e,t,r=()=>!0){this.skills=e;this.policyLine=t;this.policyGate=r}skills;policyLine;policyGate;apply(e,t){let r=n.lastUserText(e),o=this.triggeredSkills(r),s=this.build(r);return s&&n.appendToLastUserMessage(e,t,s),{text:s,triggers:o}}triggeredSkills(e){let t=[];for(let r of Object.keys(this.skills??{}))e.includes(r)&&t.push(r);return t}static composition(e){return{toolDefChars:n.toolDefChars(e),systemChars:n.systemChars(e),...n.tailComposition(e)}}static emptyComposition(){return{toolDefChars:0,systemChars:0,tailResultChars:0,tailTextChars:0,freshToolChars:0}}static maxToolResultChars(e){return n.tailComposition(e).freshToolChars}static toolDefChars(e){return Array.isArray(e.tools)&&e.tools.length?JSON.stringify(e.tools).length:0}static systemChars(e){let t=e.system;if(typeof t=="string")return t.length;if(!Array.isArray(t))return 0;let r=0;for(let o of t)o&&typeof o.text=="string"&&(r+=o.text.length);return r}static tailComposition(e){let t=n.lastUserContent(e);if(typeof t=="string")return{tailResultChars:0,tailTextChars:t.length,freshToolChars:0};if(!Array.isArray(t))return{tailResultChars:0,tailTextChars:0,freshToolChars:0};let r=0,o=0,s=0;for(let i of t)if(i)if(i.type==="tool_result"){let a=n.textLength(i.content);r+=a,s=Math.max(s,a)}else typeof i.text=="string"&&(o+=i.text.length);return{tailResultChars:r,tailTextChars:o,freshToolChars:s}}static lastUserContent(e){let t=Array.isArray(e.messages)?e.messages:null;if(!t)return null;let r=t.length-1;for(;r>=0&&t[r].role!==$.User;)r-=1;if(r<0)return null;let o=t[r].content;return typeof o=="string"||Array.isArray(o)?o:null}static textLength(e){if(typeof e=="string")return e.length;if(!Array.isArray(e))return 0;let t=0;for(let r of e)r&&typeof r.text=="string"&&(t+=r.text.length);return t}build(e){let t=[];this.policyLine&&this.policyGate()&&t.push(this.policyLine);for(let[r,o]of Object.entries(this.skills??{}))e.includes(r)&&t.push(o);return t.join(`
|
|
2
2
|
`)}static lastUserText(e){let t=Array.isArray(e.messages)?e.messages:null;if(!t)return"";for(let r=t.length-1;r>=0;r-=1){let o=t[r];if(o.role===$.User){if(typeof o.content=="string")return o.content;if(Array.isArray(o.content))return o.content.map(s=>typeof s=="string"?s:"text"in s&&typeof s.text=="string"?s.text:"").join(" ")}}return""}static appendToLastUserMessage(e,t,r){if(!r)return e;let o=Array.isArray(e.messages)?e.messages:null;if(!o||!o.length)return t===w.OpenAI&&typeof e.input=="string"&&(e.input+=`
|
|
3
3
|
|
|
4
4
|
${n.tagged(r)}`),e;let s=o.length-1;for(;s>=0&&o[s].role!==$.User;)s-=1;if(s<0)return e;let i=o[s],a=n.tagged(r);return typeof i.content=="string"?i.content+=`
|
|
@@ -31,5 +31,5 @@ ${t}`}};var N=class{constructor(e){this.sources=e}sources;async load(){return(aw
|
|
|
31
31
|
`)}get model(){return this.modelId}get usage(){return{...this.totals}}observe(e){let t=m.parse(e);if(!t)return;this.modelId??=t.model;let r=t.usage;r&&(r.inputTokens!=null&&(this.totals.inputTokens=r.inputTokens),r.outputTokens!=null&&(this.totals.outputTokens=r.outputTokens),r.cacheReadTokens!=null&&(this.totals.cacheReadTokens=r.cacheReadTokens),r.cacheCreationTokens!=null&&(this.totals.cacheCreationTokens=r.cacheCreationTokens))}};var he=c(require("node:fs")),ge=c(require("node:path"));var j=class n{static BIG_TOOL_CHARS=4e3;static defaultFile(){return ge.join(h.dir(),"gateway.jsonl")}static fromFile(e=n.defaultFile()){let t;try{t=he.readFileSync(e,"utf8")}catch{return n.empty()}return n.aggregate(t.split(`
|
|
32
32
|
`))}static aggregate(e){let t=n.empty();for(let r of e){let o=n.parse(r);o&&(o.type==="turn"?n.addTurn(t,o):o.type==="compaction"&&n.addCompaction(t,o),n.trackTime(t,o.ts))}return t}static addTurn(e,t){e.turns+=1,e.inputTokens+=n.num(t.inputTokens),e.outputTokens+=n.num(t.outputTokens),e.cacheReadTokens+=n.num(t.cacheReadTokens),e.cacheCreationTokens+=n.num(t.cacheCreationTokens),e.totalDurationMs+=n.num(t.durationMs),e.gateBlocks+=n.num(t.blockedToolCalls);let r=n.num(t.freshToolChars);if(e.maxFreshToolChars=Math.max(e.maxFreshToolChars,r),r>n.BIG_TOOL_CHARS&&(e.bigToolTurns+=1),e.maxToolDefChars=Math.max(e.maxToolDefChars,n.num(t.toolDefChars)),e.maxSystemChars=Math.max(e.maxSystemChars,n.num(t.systemChars)),e.tailResultChars+=n.num(t.tailResultChars),e.tailTextChars+=n.num(t.tailTextChars),e.maxCacheCreationTokens=Math.max(e.maxCacheCreationTokens,n.num(t.cacheCreationTokens)),typeof t.status=="number"&&t.status!==200&&(e.errorTurns+=1),n.bump(e.models,typeof t.model=="string"?t.model:"(unknown)"),Array.isArray(t.skillsTriggered))for(let o of t.skillsTriggered)n.bump(e.skillsTriggered,String(o))}static addCompaction(e,t){e.compactions+=1,e.charsSaved+=n.num(t.charsSaved),e.estTokensSaved+=n.num(t.estTokensSaved),e.linesElided+=n.num(t.elidedLines),n.bump(e.commands,typeof t.command=="string"?t.command:"(unknown)")}static trackTime(e,t){typeof t=="string"&&((!e.firstTs||t<e.firstTs)&&(e.firstTs=t),(!e.lastTs||t>e.lastTs)&&(e.lastTs=t))}static parse(e){if(!e.trim())return null;try{return JSON.parse(e)}catch{return null}}static bump(e,t){e[t]=(e[t]??0)+1}static num(e){return typeof e=="number"&&Number.isFinite(e)?e:0}static empty(){return{turns:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0,totalDurationMs:0,errorTurns:0,gateBlocks:0,maxFreshToolChars:0,bigToolTurns:0,maxToolDefChars:0,maxSystemChars:0,tailResultChars:0,tailTextChars:0,maxCacheCreationTokens:0,models:{},skillsTriggered:{},compactions:0,charsSaved:0,estTokensSaved:0,linesElided:0,commands:{},firstTs:null,lastTs:null}}};var f=c(require("node:fs")),ye=c(require("node:path")),F=class n{apiUrl;workspaceId;token;metricsFile;cursorFile;batchSize;timeoutMs;fetchImpl;constructor(e){this.apiUrl=e.apiUrl,this.workspaceId=e.workspaceId,this.token=e.token,this.metricsFile=e.metricsFile,this.cursorFile=e.cursorFile,this.batchSize=e.batchSize??1e3,this.timeoutMs=e.timeoutMs??15e3,this.fetchImpl=e.fetchImpl??fetch}async upload({dryRun:e=!1}={}){let{lines:t,newOffset:r}=this.readNewLines(),o=n.toBatch(t),s=o.turns.length+o.compactions.length,i={turns:o.turns.length,compactions:o.compactions.length,newOffset:r,sent:!1,dryRun:e};return e?i:s===0?(r!==this.readCursor()&&this.writeCursor(r),i):(await this.postAll(o),this.writeCursor(r),{...i,sent:!0})}static toBatch(e){let t=[],r=[];for(let o of e){let s;try{s=JSON.parse(o)}catch{continue}typeof s.ts=="string"&&(s.type==="turn"?t.push(n.mapTurn(s)):s.type==="compaction"&&r.push(n.mapCompaction(s)))}return{turns:t,compactions:r}}static mapTurn(e){return{timestamp:e.ts,provider:e.provider??null,model:e.model??null,status:e.status??null,durationMs:e.durationMs??null,inputTokens:e.inputTokens??null,outputTokens:e.outputTokens??null,cacheReadTokens:e.cacheReadTokens??null,cacheCreationTokens:e.cacheCreationTokens??null,toolDefChars:e.toolDefChars??null,systemChars:e.systemChars??null,freshToolChars:e.freshToolChars??null,tailResultChars:e.tailResultChars??null,tailTextChars:e.tailTextChars??null,blockedToolCalls:e.blockedToolCalls??null,skillsFired:Array.isArray(e.skillsTriggered)?e.skillsTriggered:[]}}static mapCompaction(e){return{timestamp:e.ts,command:e.command??null,originalChars:e.originalChars??null,outputChars:e.outputChars??null,charsSaved:e.charsSaved??null,estTokensSaved:e.estTokensSaved??null,originalLines:e.originalLines??null,outputLines:e.outputLines??null,elidedLines:e.elidedLines??null}}readNewLines(){let e=this.readCursor(),t;try{t=f.statSync(this.metricsFile).size}catch{return{lines:[],newOffset:e}}if(e>t&&(e=0),e===t)return{lines:[],newOffset:e};let r=f.openSync(this.metricsFile,"r");try{let o=t-e,s=Buffer.alloc(o);f.readSync(r,s,0,o,e);let i=s.toString("utf8"),a=i.lastIndexOf(`
|
|
33
33
|
`);if(a<0)return{lines:[],newOffset:e};let l=i.slice(0,a+1),u=e+Buffer.byteLength(l,"utf8");return{lines:l.split(`
|
|
34
|
-
`).filter(z=>z.trim().length>0),newOffset:u}}finally{f.closeSync(r)}}readCursor(){try{let e=JSON.parse(f.readFileSync(this.cursorFile,"utf8"));return typeof e.offset=="number"&&e.offset>=0?e.offset:0}catch{return 0}}writeCursor(e){try{f.mkdirSync(ye.dirname(this.cursorFile),{recursive:!0}),f.writeFileSync(this.cursorFile,JSON.stringify({offset:e,uploadedAt:new Date().toISOString()}))}catch{}}async postAll(e){for(let t=0;t<e.turns.length;t+=this.batchSize)await this.postBatch({turns:e.turns.slice(t,t+this.batchSize),compactions:[]});for(let t=0;t<e.compactions.length;t+=this.batchSize)await this.postBatch({turns:[],compactions:e.compactions.slice(t,t+this.batchSize)})}async postBatch(e){let t=`${this.apiUrl}/api/v1/workspaces/${this.workspaceId}/usage-telemetry`,r=new AbortController,o=setTimeout(()=>r.abort(),this.timeoutMs);try{let s=await this.fetchImpl(t,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"content-type":"application/json",accept:"application/json"},body:JSON.stringify(e),signal:r.signal});if(!s.ok)throw new Error(`usage-telemetry upload returned ${s.status}`)}finally{clearTimeout(o)}}};var _=class n{constructor(e,t){this.config=e;this.deps=t;this.injector=new k(t.skills,t.policyLine,t.policyGate)}config;deps;static UPSTREAMS=[{match:e=>e.startsWith("/v1/messages"),base:"https://api.anthropic.com",provider:w.Anthropic},{match:e=>e.startsWith("/v1/chat/completions")||e.startsWith("/v1/responses")||e.startsWith("/v1/completions"),base:"https://api.openai.com",provider:w.OpenAI}];static HOP_BY_HOP_REQUEST=["host","content-length","connection","accept-encoding","transfer-encoding"];static HOP_BY_HOP_RESPONSE=["content-encoding","content-length","transfer-encoding","connection"];injector;logger=new O("gateway");metrics=new h("gateway");telemetryTimer=null;telemetryUploader=null;static async fromEnv(){let e={port:Number(process.env.PORT||13579),inject:process.env.INJECT==="1",gate:process.env.GATE==="1",capture:process.env.CAPTURE==="1",stateFile:process.env.DZ_STATE_FILE||null},t={skills:await n.loadSkills(),policy:n.loadJson(process.env.DZ_POLICY_FILE)||Q,policyLine:process.env.INJECT_POLICY_LINE==="0"?null:le,policyGate:()=>A.at(process.cwd()).activeFeature()
|
|
34
|
+
`).filter(z=>z.trim().length>0),newOffset:u}}finally{f.closeSync(r)}}readCursor(){try{let e=JSON.parse(f.readFileSync(this.cursorFile,"utf8"));return typeof e.offset=="number"&&e.offset>=0?e.offset:0}catch{return 0}}writeCursor(e){try{f.mkdirSync(ye.dirname(this.cursorFile),{recursive:!0}),f.writeFileSync(this.cursorFile,JSON.stringify({offset:e,uploadedAt:new Date().toISOString()}))}catch{}}async postAll(e){for(let t=0;t<e.turns.length;t+=this.batchSize)await this.postBatch({turns:e.turns.slice(t,t+this.batchSize),compactions:[]});for(let t=0;t<e.compactions.length;t+=this.batchSize)await this.postBatch({turns:[],compactions:e.compactions.slice(t,t+this.batchSize)})}async postBatch(e){let t=`${this.apiUrl}/api/v1/workspaces/${this.workspaceId}/usage-telemetry`,r=new AbortController,o=setTimeout(()=>r.abort(),this.timeoutMs);try{let s=await this.fetchImpl(t,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"content-type":"application/json",accept:"application/json"},body:JSON.stringify(e),signal:r.signal});if(!s.ok)throw new Error(`usage-telemetry upload returned ${s.status}`)}finally{clearTimeout(o)}}};var _=class n{constructor(e,t){this.config=e;this.deps=t;this.injector=new k(t.skills,t.policyLine,t.policyGate)}config;deps;static UPSTREAMS=[{match:e=>e.startsWith("/v1/messages"),base:"https://api.anthropic.com",provider:w.Anthropic},{match:e=>e.startsWith("/v1/chat/completions")||e.startsWith("/v1/responses")||e.startsWith("/v1/completions"),base:"https://api.openai.com",provider:w.OpenAI}];static HOP_BY_HOP_REQUEST=["host","content-length","connection","accept-encoding","transfer-encoding"];static HOP_BY_HOP_RESPONSE=["content-encoding","content-length","transfer-encoding","connection"];injector;logger=new O("gateway");metrics=new h("gateway");telemetryTimer=null;telemetryUploader=null;static async fromEnv(){let e={port:Number(process.env.PORT||13579),inject:process.env.INJECT==="1",gate:process.env.GATE==="1",capture:process.env.CAPTURE==="1",stateFile:process.env.DZ_STATE_FILE||null},t={skills:await n.loadSkills(),policy:n.loadJson(process.env.DZ_POLICY_FILE)||Q,policyLine:process.env.INJECT_POLICY_LINE==="0"?null:le,policyGate:()=>A.at(process.cwd()).activeFeature()?.active===!0,loadState:()=>n.loadJson(e.stateFile)};return new n(e,t)}static loadSkills(){return new N(n.skillSources()).load()}static skillSources(){return[new P]}createServer(){return ke.createServer((e,t)=>this.handle(e,t))}listen(){let e=this.createServer();return e.on("error",t=>{t.code==="EADDRINUSE"&&(this.logger.warn(`port ${this.config.port} already in use \u2014 a gateway is probably running.`),process.exit(1)),this.logger.error(`server error: ${t.message}`),process.exit(1)}),e.listen(this.config.port,"127.0.0.1",()=>{this.logger.info(`listening on http://127.0.0.1:${this.config.port} (inject=${this.config.inject} gate=${this.config.gate}) skills=${Object.keys(this.deps.skills).length}`)}),this.startTelemetry(),e}startTelemetry(){if(!h.enabled()||process.env.DZ_TELEMETRY==="0")return;let e=A.at(process.cwd()).connection();if("error"in e){this.logger.debug(`telemetry: off (${e.error})`);return}this.telemetryUploader=new F({...e,metricsFile:j.defaultFile(),cursorFile:be.join(h.dir(),"gateway.upload-cursor.json")});let t=Math.max(3e4,Number(process.env.DZ_TELEMETRY_INTERVAL_MS)||3e5),r=()=>{this.flushTelemetry()};this.telemetryTimer=setInterval(r,t),this.telemetryTimer.unref?.(),setTimeout(r,15e3).unref?.(),this.logger.info(`telemetry: background upload every ${Math.round(t/1e3)}s \u2192 ws ${e.workspaceId}`)}async flushTelemetry(){if(this.telemetryUploader)try{let e=await this.telemetryUploader.upload();e.sent&&this.logger.info(`telemetry: uploaded ${e.turns} turn + ${e.compactions} compaction event(s)`)}catch(e){this.logger.warn(`telemetry upload failed: ${e.message}`)}}handle(e,t){if(this.handleControlPlane(e,t))return;let r=n.pickUpstream(e.url||""),o=[];e.on("data",s=>o.push(s)),e.on("end",()=>this.forward(e,t,r,o))}handleControlPlane(e,t){return e.url===X.Health?(t.writeHead(200,{"content-type":R.Json}),t.end(JSON.stringify({ok:!0,dz:!0,pid:process.pid,port:this.config.port,inject:this.config.inject,gate:this.config.gate})),!0):e.url===X.Stop?(t.writeHead(200,{"content-type":R.Json}),t.end(JSON.stringify({ok:!0,stopping:!0})),this.telemetryTimer&&clearInterval(this.telemetryTimer),this.flushTelemetry().finally(()=>setTimeout(()=>process.exit(0),50)),setTimeout(()=>process.exit(0),3e3).unref?.(),!0):!1}async forward(e,t,r,o){let s=Date.now(),i=Buffer.concat(o),a=[],l=k.emptyComposition();if(this.config.inject&&r&&e.method==="POST"&&i.length){let p=this.injectBody(i.toString("utf8"),r.provider);i=Buffer.from(p.bodyText,"utf8"),a=p.triggers,l=p.composition}let u=process.env.FORCE_UPSTREAM||(r?r.base:"https://api.anthropic.com");try{let p=await fetch(u+e.url,{method:e.method,headers:n.buildForwardHeaders(e.headers),body:["GET","HEAD"].includes(e.method||"")?void 0:i,redirect:"manual"}),z=(p.headers.get("content-type")||"").toLowerCase();if(r?.provider===w.Anthropic&&p.ok&&p.body&&(this.config.gate||h.enabled())){let re=this.config.gate?new D(this.deps.policy,this.deps.loadState()):null,ne={startedAt:s,provider:r.provider,status:p.status,triggers:a,...l};if(z.includes(R.EventStream))return this.streamResponse(p,t,re,ne);if(z.includes(R.Json))return this.jsonResponse(p,t,re,ne)}t.writeHead(p.status,n.filterResponseHeaders(p.headers)),p.body?n.webToNode(p.body).pipe(t):t.end()}catch(p){this.logger.error(`upstream error: ${p.message}`),t.writeHead(502,{"content-type":R.Json}),t.end(JSON.stringify({error:"gateway upstream failed",detail:String(p)}))}}injectBody(e,t){try{let r=JSON.parse(e),{text:o,triggers:s}=this.injector.apply(r,t),i=h.enabled()?k.composition(r):k.emptyComposition();return o?(this.logger.debug(`-> ${t} injected(${o.split(`
|
|
35
35
|
`).length} line(s))`),{bodyText:JSON.stringify(r),triggers:s,composition:i}):{bodyText:e,triggers:s,composition:i}}catch{return{bodyText:e,triggers:[],composition:k.emptyComposition()}}}streamResponse(e,t,r,o){t.writeHead(e.status,n.filterResponseHeaders(e.headers));let s=r?new L(r):null,i=h.enabled()?new M:null,a=n.webToNode(e.body);a.setEncoding("utf8"),a.on("data",l=>{if(i&&i.feed(l),!s){t.write(l);return}try{let u=s.push(l);u&&t.write(u)}catch{t.write(l)}}),a.on("end",()=>{if(s)try{let l=s.flush();l&&t.write(l)}catch{}t.end(),this.recordTurn(o,i,s)}),a.on("error",()=>{try{t.end()}catch{}this.recordTurn(o,i,s)})}async jsonResponse(e,t,r,o){let s=await e.text(),i=s,a=null,l={model:null,usage:{}};try{let u=JSON.parse(s);l={model:u.model??null,usage:n.usageOf(u)},r&&(a=new I(r),i=JSON.stringify(a.rewrite(u)))}catch{i=s}a&&i!==s&&this.logger.info("\u26D4 gate rewrote response (blocked tool_use)"),t.writeHead(e.status,n.filterResponseHeaders(e.headers)),t.end(i),this.recordTurn(o,l,a)}recordTurn(e,t,r){let o=t?.usage??{};this.metrics.record({type:"turn",provider:e.provider,model:t?.model??null,durationMs:Date.now()-e.startedAt,status:e.status,inputTokens:o.inputTokens,outputTokens:o.outputTokens,cacheReadTokens:o.cacheReadTokens,cacheCreationTokens:o.cacheCreationTokens,skillsTriggered:e.triggers.length?e.triggers:void 0,blockedToolCalls:r?r.blockedCount:void 0,gated:r?r.blockedCount>0:void 0,freshToolChars:e.freshToolChars||void 0,toolDefChars:e.toolDefChars||void 0,systemChars:e.systemChars||void 0,tailResultChars:e.tailResultChars||void 0,tailTextChars:e.tailTextChars||void 0})}static usageOf(e){let t=e.usage;return t?{inputTokens:t.input_tokens,outputTokens:t.output_tokens,cacheReadTokens:t.cache_read_input_tokens,cacheCreationTokens:t.cache_creation_input_tokens}:{}}static pickUpstream(e){return n.UPSTREAMS.find(t=>t.match(e))??null}static buildForwardHeaders(e){let t={};for(let[r,o]of Object.entries(e))n.HOP_BY_HOP_REQUEST.includes(r.toLowerCase())||(typeof o=="string"?t[r]=o:Array.isArray(o)&&(t[r]=o.join(", ")));return t}static filterResponseHeaders(e){let t={};return e.forEach((r,o)=>{n.HOP_BY_HOP_RESPONSE.includes(o.toLowerCase())||(t[o]=r)}),t}static webToNode(e){return Se.Readable.fromWeb(e)}static loadJson(e){try{return e?JSON.parse(Te.readFileSync(e,"utf8")):null}catch{return null}}};function Oe(n,e){return new _(n,e).createServer()}require.main===module&&_.fromEnv().then(n=>n.listen());0&&(module.exports={Gateway,createGatewayServer});
|
package/dist/hooks/lib/router.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`;
|
|
3
|
-
`)}function
|
|
4
|
-
`)}renderEvent(t){if(t.eventType==="INVALIDATION"){let r=this.stringField(t.payload,"artifactSummary")??"(no summary)",s=this.stringField(t.payload,"reason")??"(no reason)",i=this.stringField(t.payload,"previousStatus")??"?",a=`${t.sourceType.toLowerCase()} ${t.sourceId??"?"}`;return`- INVALIDATION (${t.urgency}): ${a} ("${this.truncate(r,120)}") \u2014 reason: "${this.truncate(s,200)}" \u2014 was ${i}.`}let n=this.summarizePayload(t.payload),o=`${t.sourceType.toLowerCase()}${t.sourceId?` ${t.sourceId}`:""}`;return`- [Dezycro] ${t.eventType} (${t.urgency}) from ${o}${n?`: ${n}`:""}.`}summarizePayload(t){let n=[];for(let[o,r]of Object.entries(t??{}))r!=null&&(Array.isArray(r)?n.push(`${o}=${r.join("|")}`):typeof r!="object"&&n.push(`${o}=${String(r)}`));return this.truncate(n.join(", "),240)}reactionGuidance(t){switch(t){case
|
|
1
|
+
"use strict";var ue=Object.create;var F=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var pe=Object.getPrototypeOf,fe=Object.prototype.hasOwnProperty;var ge=(e,t)=>{for(var n in t)F(e,n,{get:t[n],enumerable:!0})},ct=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of de(t))!fe.call(e,r)&&r!==n&&F(e,r,{get:()=>t[r],enumerable:!(o=le(t,r))||o.enumerable});return e};var S=(e,t,n)=>(n=e!=null?ue(pe(e)):{},ct(t||!e||!e.__esModule?F(n,"default",{value:e,enumerable:!0}):n,e)),me=e=>ct(F({},"__esModule",{value:!0}),e);var Pn={};ge(Pn,{QUALITY_GATE_REASON_PREFIX:()=>Zt,TRIVIAL_TURN_CHAR_DEFAULT_EXPORT:()=>In,_extractEditedPaths:()=>hn,_handlePostEdit:()=>yn,_handleSessionStart:()=>vn,_handleStop:()=>kn,_handleUpdateTaskGate:()=>Sn,_handleUserPromptSubmit:()=>bn,_handleWorkbookMutation:()=>Tn,_readActiveFeatureId:()=>wn,_transcriptCharsSinceLastStop:()=>Cn,dispatch:()=>rn});module.exports=me(Pn);var U=S(require("fs")),Qt=S(require("path"));var B=S(require("fs")),_=S(require("path")),ut=Object.freeze({conservative:{decision:.85,issue:.8,assumption:.9,taskStatus:.9},balanced:{decision:.75,issue:.7,assumption:.8,taskStatus:.85},aggressive:{decision:.6,issue:.55,assumption:.7,taskStatus:.75}}),D=Object.freeze({companion:{autoVerify:{preTaskDone:!0,prePush:!0,postEditPulse:{enabled:!0,threshold:5},autoPublishSpecOnControllerChange:!0,controllerPaths:["**/controllers/**/*.{kt,java,py,go,ts,tsx,js,rs,rb,cs}","**/api/**/*.{kt,java,py,go,ts,tsx,js,rs,rb,cs}"],verifyTimeoutSeconds:300,publishSpecTimeoutSeconds:300},autoWorkbookUpdates:{enabled:!0,preset:"conservative",maxAutoLogsPerTurn:2,maxAutoLogsPerSession:10,undoWindow:"next-turn",thresholds:{decision:null,issue:null,assumption:null,taskStatus:null},trivialTurnCharCutoff:600,hashRingBufferSize:32},coverageNudges:{enabled:!0,staleBaselineDays:14,fetchMode:"async"},authoring:{defaultDepth:"adaptive",existingWorkScan:!0,qualityGate:{enabled:!0},minViableContextGate:{enabled:!0}},safeMode:!1}});function Y(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function lt(e,t){if(!Y(t))return t===void 0?e:t;let n=Array.isArray(e)?[...e]:{...e};for(let o of Object.keys(t)){let r=n[o],s=t[o];Y(r)&&Y(s)?n[o]=lt(r,s):n[o]=s}return n}function dt(e){let t=_.join(e,".dezycro","config.json"),n;try{n=B.readFileSync(t,"utf8")}catch(r){if(r.code==="ENOENT")return JSON.parse(JSON.stringify(D));throw r}let o;try{o=JSON.parse(n)}catch{return JSON.parse(JSON.stringify(D))}return lt(JSON.parse(JSON.stringify(D)),o)}function pt(e){let t=e?.companion?.autoWorkbookUpdates||{},n=t.preset||"conservative",o=ut[n]||ut.conservative,r=t.thresholds||{};return{decision:typeof r.decision=="number"?r.decision:o.decision,issue:typeof r.issue=="number"?r.issue:o.issue,assumption:typeof r.assumption=="number"?r.assumption:o.assumption,taskStatus:typeof r.taskStatus=="number"?r.taskStatus:o.taskStatus}}function C(e){let t=_.resolve(e||process.cwd());for(let n=0;n<64;n+=1){if(B.existsSync(_.join(t,".dezycro")))return t;let o=_.dirname(t);if(o===t)return null;t=o}return null}var m=S(require("fs")),N=S(require("path")),ye=500,Se=10,Q=32,ft=64,gt=Object.freeze({autoDecisionLogs:0,autoIssueLogs:0,autoAssumptionLogs:0,autoTaskStatusLogs:0,undoCount:0,verifyAutoRuns:0,verifyAutoFailures:0,qualityGateFailures:0,contextGateFailures:0,lockContention:0,forceApprovals:0}),ke=Object.freeze({schemaVersion:1,currentSessionId:null,authoringMode:!1,authoringDocId:null,authoringDocType:null,authoringTranscriptPath:null,authoringStartedTs:null,pulseCount:0,lastEditTs:null,needsSpecPublish:!1,controllerEditsSinceLastPublish:[],coverageSnapshot:null,coverageSnapshotTs:null,coverageFetchPending:!1,lastVerifyCompletionTs:null,recentDecisionHashes:[],recentIssueHashes:[],recentAssumptionHashes:[],lastAutoWorkbookMutation:null,sessionAutoLogCount:0,turnAutoLogCount:0,currentTurnId:0,suppressWorkbookUpdatesUntil:null,pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:null,commandBus:{cursor:null,lastPollTs:null,lastAttemptTs:null,consecutiveFailures:0,recentEventIds:[]},metrics:gt});function mt(e){return N.join(e,".dezycro","companion-state.json")}function ht(e){return N.join(e,".dezycro",".companion-state.lock")}function be(e){return N.join(e,".dezycro",".companion-state.json.tmp")}function Te(e){m.mkdirSync(N.join(e,".dezycro"),{recursive:!0})}function Z(){return JSON.parse(JSON.stringify(ke))}function k(e){let t=mt(e);try{let n=m.readFileSync(t,"utf8"),o=JSON.parse(n);return{...Z(),...o,metrics:{...gt,...o.metrics||{}}}}catch(n){let o=n;return o&&o.code==="ENOENT",Z()}}function ve(e){let t=ht(e),n=Date.now();for(;Date.now()-n<ye;){try{let r=m.openSync(t,"wx");return m.writeSync(r,String(process.pid)),m.closeSync(r),!0}catch(r){if(r.code!=="EEXIST")throw r}let o=Date.now()+Se;for(;Date.now()<o;);}return!1}function we(e){try{m.unlinkSync(ht(e))}catch{}}function Ce(e,t){return t?{...e,...t}:e}function tt(e,t,n){if(!t||e.includes(t))return e;let o=e.concat([t]);return o.length>n?o.slice(o.length-n):o}function d(e,t){if(Te(e),!ve(e))return null;try{let n=k(e),o=t||{},r={...n};for(let a of Object.keys(o)){let c=o[a];if(a==="metrics")r.metrics=Ce(n.metrics,c);else if(a==="pushDecisionHash")r.recentDecisionHashes=tt(n.recentDecisionHashes,c,Q);else if(a==="pushIssueHash")r.recentIssueHashes=tt(n.recentIssueHashes,c,Q);else if(a==="pushAssumptionHash")r.recentAssumptionHashes=tt(n.recentAssumptionHashes,c,Q);else if(a==="pushControllerEdit"){let u=c,p=n.controllerEditsSinceLastPublish.filter(h=>h.path!==u.path).concat([u]);r.controllerEditsSinceLastPublish=p.length>ft?p.slice(p.length-ft):p}else r[a]=c}let s=be(e),i=m.openSync(s,"w");return m.writeSync(i,JSON.stringify(r,null,2)),m.fsyncSync(i),m.closeSync(i),m.renameSync(s,mt(e)),r}finally{we(e)}}function E(e,t,n=1){let o=k(e).metrics[t]||0;return d(e,{metrics:{[t]:o+n}})}var j=S(require("fs")),nt=S(require("path")),Ie="companion.log";function yt(e,t){try{let n=nt.join(e,".dezycro");if(!j.existsSync(n))return;let o=JSON.stringify({ts:new Date().toISOString(),...t})+`
|
|
2
|
+
`;j.appendFileSync(nt.join(n,Ie),o,{encoding:"utf8"})}catch{}}function g(e,t,n){yt(e,{level:"info",event:t,...n||{}})}function $(e,t,n){yt(e,{level:"warn",event:t,...n||{}})}var kt=S(require("fs")),bt=S(require("path"));function Tt(e){let t=e||{},n=t.repoRoot,o=typeof t.maxAgeMinutes=="number"?t.maxAgeMinutes:60;if(!n)return!1;let r=bt.join(n,".dezycro","state.json"),s;try{let l=kt.readFileSync(r,"utf8");s=JSON.parse(l)}catch{return!1}if(!s||typeof s!="object")return!1;let i=s.lastVerifyTs,a=s.lastVerifyRunId;if(!i||!a)return!1;let c=Date.parse(i);if(!Number.isFinite(c))return!1;let u=Date.now()-c;return!(u<0||u>o*60*1e3)}function vt(e){let t=e||{},n=t.paths||[],o=t.status||"DONE",r=n.length>0?n.join(","):"<paths>";return{reason:"Cannot mark task "+o+" \u2014 no recent passing verifier run found for the linked paths. Run `/dezycro:verify --path "+r+"` first to attach passing verifier evidence, then retry."}}function wt(e){if(!e)return{needed:!1,controllerPaths:[],lastEditTs:null};let t=k(e),n=Array.isArray(t.controllerEditsSinceLastPublish)?t.controllerEditsSinceLastPublish:[],o=n.map(i=>i&&i.path).filter(Boolean),r=n.length>0&&n[n.length-1].editedTs||null;return{needed:!!t.needsSpecPublish&&o.length>0,controllerPaths:o,lastEditTs:r}}function Ae(e){return String(e||"").replace(/\\/g,"/")}function It(e){let t=e.match(/\{([^{}]+)\}/);if(!t||t.index===void 0)return[e];let n=t[0],r=t[1].split(","),s=[];for(let i of r){let a=e.slice(0,t.index)+i+e.slice(t.index+n.length);s.push(...It(a))}return s}function _e(e){let t="^";for(let n=0;n<e.length;n+=1){let o=e[n];o==="*"?e[n+1]==="*"?(n+=1,e[n+1]==="/"?(n+=1,t+="(?:.*/)?"):t+=".*"):t+="[^/]*":o==="?"?t+="[^/]":".+^$()|[]\\".indexOf(o)!==-1?t+="\\"+o:t+=o}return t+="$",new RegExp(t)}var Ct=new Map;function Re(e){let t=Ct.get(e);if(t)return t;let n=It(e).map(_e);return Ct.set(e,n),n}function Ee(e,t){if(!e||!Array.isArray(t)||t.length===0)return!1;let n=Ae(e);for(let o of t){if(typeof o!="string"||o.length===0)continue;let r=Re(o);for(let s of r)if(s.test(n))return!0}return!1}function Pt(e,t){return Array.isArray(e)?e.filter(n=>Ee(n,t)):[]}var b=S(require("fs")),V=S(require("path"));var xt=Object.freeze({IDLE:"IDLE",DETECT_INTENT:"DETECT_INTENT",SCAN_EXISTING_WORK:"SCAN_EXISTING_WORK",CLARIFY:"CLARIFY",PROPOSE_PLACEMENT:"PROPOSE_PLACEMENT",CREATE_FEATURE:"CREATE_FEATURE",TRD_DISCOVERY:"TRD_DISCOVERY",MIN_CONTEXT_GATE:"MIN_CONTEXT_GATE",DRAFT:"DRAFT",ITERATE:"ITERATE",QUALITY_GATE:"QUALITY_GATE",FINALIZE:"FINALIZE"}),At=Object.freeze({PRD:"PRD",TRD:"TRD"}),An=Object.freeze(["skip","draft it","just write it","next"]),_n=Object.freeze([xt.MIN_CONTEXT_GATE,xt.QUALITY_GATE]),De="authoring-transcript.json",Ne="authoring-current-draft.json";function _t(e){return V.join(e,".dezycro",De)}function Rt(e){return V.join(e,".dezycro",Ne)}function Le(e){b.mkdirSync(V.join(e,".dezycro"),{recursive:!0})}function Me(e,t){let n=`${e}.tmp`,o=b.openSync(n,"w");try{b.writeSync(o,JSON.stringify(t,null,2)),b.fsyncSync(o)}finally{b.closeSync(o)}b.renameSync(n,e)}function Et(e){try{let t=b.readFileSync(e,"utf8");return JSON.parse(t)}catch{return null}}function Ot(e,t){if(!t||!t.docId||!t.docType)throw new Error("authoring.enter: docId and docType required");if(t.docType!==At.PRD&&t.docType!==At.TRD)throw new Error(`authoring.enter: invalid docType ${t.docType}`);Le(e);let n=_t(e),o=new Date().toISOString(),r=Et(n),s=r&&r.docId===t.docId?r:{docId:t.docId,docType:t.docType,featureId:t.featureId||null,seed:t.seed||null,startedTs:o,discoverySummary:null,qaTurns:[],draftHistory:[],qualityGateHistory:[]};return Me(n,s),d(e,{authoringMode:!0,authoringDocId:t.docId,authoringDocType:t.docType,authoringTranscriptPath:n,authoringStartedTs:o}),s}function Dt(e){d(e,{authoringMode:!1,authoringDocId:null,authoringDocType:null,authoringTranscriptPath:null,authoringStartedTs:null});for(let t of[_t(e),Rt(e)])try{b.unlinkSync(t)}catch{}}function Nt(e){let t=k(e);return{active:!!(t&&t.authoringMode),docId:t?t.authoringDocId:null,docType:t?t.authoringDocType:null,transcriptPath:t?t.authoringTranscriptPath:null,startedTs:t?t.authoringStartedTs:null}}function Lt(e){return Et(Rt(e))}var Rn=Object.freeze(["architecture","data model","data-model","datamodel","api contract","api-contract","apicontract","migration","auth","queue","verifier-behavior","verifier behavior"]),Ue=Object.freeze([{id:"architecture",pattern:/^##\s+architecture\b/im},{id:"data_model",pattern:/^##\s+data\s+model\b/im},{id:"api_contracts",pattern:/^##\s+api\s+contracts?\b/im},{id:"failure_modes",pattern:/^##\s+failure\s+modes?/im},{id:"migration",pattern:/^##\s+migration\b/im},{id:"open_questions",pattern:/^##\s+open\s+questions?\b/im}]),Fe=/\b(TBD|TODO|FIXME|fill in later|to be determined)\b/i,Mt=1e3,Ht=/^##\s+Pillar\s+\d+\s*:\s*(.+?)\s*$/gim,En=Object.freeze(["data_model","api_contracts","state_or_persistence","failure_modes","migration_or_rollout"]);function Be(e){if(!e||typeof e!="string")return[];let t=[];Ht.lastIndex=0;let n;for(;(n=Ht.exec(e))!==null;){let o=n[1].trim();o&&t.push(o)}return t}function Ut(e){let{trdContent:t,prdContent:n}=e,o=[],r=(t||"").toString(),s=r.trim().length;s<Mt&&o.push({section:"document",issue:`TRD is too short (${s} chars; minimum ${Mt}).`,required_fix:"Expand the draft with concrete technical detail."});for(let c of Ue)c.pattern.test(r)||o.push({section:c.id,issue:"Required H2 section is missing.",required_fix:`Add a "## ${c.id.replace(/_/g," ")}" section grounded in the PRD + discovery.`});let i=r.match(Fe);i&&o.push({section:"placeholders",issue:`Placeholder token "${i[0]}" present \u2014 TRDs must not ship with unresolved placeholders.`,required_fix:"Resolve or move the open item into the ## Open Questions section."});let a=Be(n);if(a.length>0){let c=[],u=r.toLowerCase();for(let l of a)u.includes(l.toLowerCase())||c.push(l);c.length>0&&o.push({section:"pillar_coverage",issue:`TRD does not mention PRD pillar(s): ${c.join(", ")}.`,required_fix:"Add a per-pillar technical-design section, or reference each pillar by name in the relevant TRD section."})}return{passed:o.length===0,blockingIssues:o,deterministic:!0}}var Ft=S(require("crypto"));function Bt(e,t,n){let o=String(e||"").toLowerCase().trim(),r=String(t||"").toLowerCase().trim().replace(/\s+/g," "),s=String(n||"").toLowerCase().trim();return Ft.createHash("sha1").update(`${o}::${r}::${s}`).digest("hex").slice(0,16)}var G=Object.freeze({decision:"Logging decision: {{summary}} \u2014 reason: {{reason}}. Say 'undo that' if wrong.",issue:"Logging issue ({{severity}}): {{summary}}. Say 'undo that' if wrong.",assumption:"Logging assumption: {{summary}} \u2014 rationale: {{rationale}}. Say 'undo that' if wrong.",task_status:"Marking task {{task_id}} as {{proposed_status}} \u2014 rationale: {{rationale}}. Say 'undo that' if wrong."}),Ve="Auto-logging paused \u2014 {{count}} entries logged this session. Type /dezycro:sync to log manually.";function jt(e){let t=e.thresholds||{},n=Math.max(0,Number(e.perTurnSlotsLeft)||0),o=Math.max(0,Number(e.perSessionSlotsLeft)||0);if(n<=0||o<=0)return Ve.replace("{{count}}",o<=0?"session-cap":"turn-cap");let r=Math.min(n,o),s=(e.recentHashes||[]).filter(Boolean),i=["[Dezycro Companion]",`Feature: ${Ge(e.activeFeatureName,e.activeFeatureId)}. Slots: ${r} (${n}/turn, ${o}/session).`,"","Scan since your last message for committed signals to log:",` - Decision (conf \u2265 ${t.decision??.85}) \u2014 committed technical/product choice`,` - Issue (conf \u2265 ${t.issue??.8}) \u2014 current blocker/bug`,` - Assumption (conf \u2265 ${t.assumption??.9}) \u2014 unverified belief affecting impl`,` - Task-status (conf \u2265 ${t.taskStatus??.9}) \u2014 task is now DONE/PUSHED`,"","Skip: hypotheticals, brainstorming, implementation-obvious details, ephemeral content, intent statements while authoring a PRD/TRD."];return s.length>0&&i.push(`Already-logged hashes (skip dupes): ${s.join(", ")}`),i.push("","For each surviving candidate (max "+r+"): print the announce line verbatim and call the matching MCP tool.",' decision: "'+G.decision+'" \u2192 mcp__dezycro(-dev)?__add_decision',' issue: "'+G.issue+'" \u2192 mcp__dezycro(-dev)?__add_issue',' assumption: "'+G.assumption+'" \u2192 mcp__dezycro(-dev)?__add_assumption',' task_status: "'+G.task_status+'" \u2192 mcp__dezycro(-dev)?__update_task',"","If nothing meets the bar, simply continue with whatever the user asked next \u2014 do NOT output any acknowledgment line. The hook will silence itself for trivial turns."),i.join(`
|
|
3
|
+
`)}function Ge(e,t){if(!t)return"(none \u2014 skip if no feature is active)";let n=String(t).slice(0,8);return e?`${e} (${n}\u2026)`:n+"\u2026"}function $t(e){return typeof e!="string"?null:/add_decision$/.test(e)?"decision":/add_issue$/.test(e)?"issue":/add_assumption$/.test(e)?"assumption":/update_task$/.test(e)?"task-status":null}var Vt=Object.freeze(["undo that","no, wrong","revert that","undo the last log","wrong, undo"]);function Gt(e){if(typeof e!="string")return!1;let t=e.toLowerCase().trim();if(!t)return!1;for(let n=0;n<Vt.length;n+=1)if(t.includes(Vt[n]))return!0;return!1}function Jt(e){if(!e||!e.tool||!e.entityId)return null;let t=e.entityId;switch(e.tool){case"add_decision":return{tool:"update_decision",args:{id:t,status:"INVALIDATED",reason:"undo by user"},humanLine:`Reverting the last auto-logged decision (${t.slice(0,8)}\u2026) \u2014 marking INVALIDATED.`};case"add_issue":return{tool:"update_issue",args:{id:t,status:"CANCELLED",reason:"undo by user"},humanLine:`Reverting the last auto-logged issue (${t.slice(0,8)}\u2026) \u2014 marking CANCELLED.`};case"add_assumption":return{tool:"update_issue",args:{id:t,status:"INVALIDATED",kind:"ASSUMPTION",reason:"undo by user"},humanLine:`Reverting the last auto-logged assumption (${t.slice(0,8)}\u2026) \u2014 marking INVALIDATED.`};case"update_task":return e.priorStatus?{tool:"update_task",args:{id:t,status:e.priorStatus},humanLine:`Reverting the last auto-task-status change \u2014 restoring ${t.slice(0,8)}\u2026 to ${e.priorStatus}.`}:null;default:return null}}function zt(e,t){return!e||typeof e.createdInTurnId!="number"?!1:t===e.createdInTurnId+1}function ot(e,t){return`${e} ${t}${e===1?"":"s"}`}function rt(e){return!e||typeof e!="object"?null:{uncoveredPaths:Number(e.uncoveredPaths??0)||0,endpointsNotInSpec:Number(e.endpointsNotInSpec??0)||0,staleBaselines:Number(e.staleBaselines??0)||0}}function J(e){let t=rt(e);if(!t)return null;let n=[];return t.uncoveredPaths>0&&n.push(`${ot(t.uncoveredPaths,"path")} uncovered`),t.endpointsNotInSpec>0&&n.push(`${ot(t.endpointsNotInSpec,"endpoint")} not in spec`),t.staleBaselines>0&&n.push(`${ot(t.staleBaselines,"baseline")} stale`),n.length===0?null:`Coverage: ${n.join(", ")}`}function Wt(e,t){let n=rt(e),o=rt(t);if(!o)return null;if(!n)return J(t);let s=[["paths uncovered",o.uncoveredPaths-n.uncoveredPaths],["endpoints not in spec",o.endpointsNotInSpec-n.endpointsNotInSpec],["baselines stale",o.staleBaselines-n.staleBaselines]].filter(([,a])=>a!==0);return s.length===0?null:`Coverage delta since last verify: ${s.map(([a,c])=>`${c>0?"+":""}${c} ${a}`).join(", ")}`}var L={HaltAndReplan:"HALT_AND_REPLAN",AcknowledgeAndContinue:"ACKNOWLEDGE_AND_CONTINUE",SilentLog:"SILENT_LOG"};var W=S(require("fs")),qt=S(require("os")),I=S(require("path")),z=class e{constructor(t){this.repoRoot=t;this.config=e.readJson(I.join(t,".dezycro","config.json"))}repoRoot;config;static at(t=process.cwd()){return new e(e.findRepoRoot(t)??I.resolve(t))}get workspaceId(){return e.env("DZ_WORKSPACE_ID")??e.str(this.config?.workspaceId)}get tenantId(){return e.env("DZ_TENANT_ID")??e.str(this.config?.tenantId)}get projectId(){return e.str(this.config?.projectId)}get apiUrl(){let t=e.env("DZ_API_URL");if(t)return e.trimSlash(t);let n=Array.isArray(this.config?.environments)?this.config?.environments:[],o=n.find(s=>e.isObj(s)&&s.default===!0)??n[0],r=e.isObj(o)?e.str(o.apiUrl):null;return r?e.trimSlash(r):null}get token(){let t=e.env("DZ_TOKEN");if(t)return t;let n=[I.join(this.repoRoot,".dezycro","companion-token.local.json"),I.join(qt.homedir(),".dezycro","token.json")];for(let o of n){let r=e.str(e.readJson(o)?.token);if(r)return r}return null}activeFeature(){let t=e.readJson(I.join(this.repoRoot,".dezycro","active-feature.json")),n=e.str(t?.featureId);if(!n)return null;let o=e.str(t?.workspaceId)??this.workspaceId;return o?{workspaceId:o,featureId:n,active:t?.active===!0}:null}connection(){let t=this.apiUrl;if(!t)return{error:"no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)"};let n=this.workspaceId;if(!n)return{error:"no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)"};let o=this.token;return o?{apiUrl:t,workspaceId:n,token:o}:{error:"no token (run `dezycro setup` or set DZ_TOKEN)"}}static findRepoRoot(t){let n=I.resolve(t||process.cwd());for(let o=0;o<64;o+=1){if(W.existsSync(I.join(n,".dezycro")))return n;let r=I.dirname(n);if(r===n)return null;n=r}return null}static readJson(t){try{return JSON.parse(W.readFileSync(t,"utf8"))}catch{return null}}static isObj(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}static str(t){return typeof t=="string"&&t.length>0?t:null}static env(t){let n=process.env[t];return n&&n.length>0?n:null}static trimSlash(t){return t.replace(/\/$/,"")}};var qe=["sessionStart","userPromptSubmit","stop","postToolUse"],Ke=60,Xe=L.HaltAndReplan,Kt=200,Ye=100,Qe=3,Ze=1800,st=class e{async pollIfDue(t,n,o,r){let s=this.resolveConfig(o),i=s.reactionMode,a={events:[],statePatch:null,reactionMode:i};if(!s.enabled||!s.hooks.has(n)&&n!=="sessionStart")return a;let c=r.commandBus,u=c.lastAttemptTs??c.lastPollTs;if(n!=="sessionStart"&&u){let f=Date.parse(u);if(!Number.isNaN(f)){let w=e.backoffSeconds(s.minPollIntervalSeconds,c.consecutiveFailures??0,s.circuitBreakerThreshold,s.maxBackoffSeconds);if((Date.now()-f)/1e3<w)return a}}let l=new z(t),p=l.activeFeature();if(!p||!p.active)return a;let h=l.apiUrl;if(!h)return a;let v=l.token;if(!v)return g(t,"command_bus_skip_no_token",{hookEvent:n}),a;let y=n==="sessionStart"?null:c.cursor,P=new Date().toISOString();try{let f=await this.fetchInbox(h,p,v,y),w=new Set(c.recentEventIds??[]),T=f.events.filter(O=>!w.has(O.eventId)),R=this.trimRecent([...c.recentEventIds??[],...T.map(O=>O.eventId)]),X={commandBus:{cursor:f.nextCursor||c.cursor,lastPollTs:f.serverTs,lastAttemptTs:P,consecutiveFailures:0,recentEventIds:R}};return T.length>0&&g(t,"command_bus_events_received",{hookEvent:n,count:T.length,types:T.map(O=>O.eventType)}),{events:T,statePatch:X,reactionMode:i}}catch(f){let w=(c.consecutiveFailures??0)+1,T={commandBus:{...c,lastAttemptTs:P,consecutiveFailures:w}},R=e.backoffSeconds(s.minPollIntervalSeconds,w,s.circuitBreakerThreshold,s.maxBackoffSeconds);return g(t,"command_bus_poll_failed",{hookEvent:n,error:f instanceof Error?f.message:String(f),consecutiveFailures:w,nextPollAfterSeconds:R,backoff:w>=s.circuitBreakerThreshold}),{events:[],statePatch:T,reactionMode:i}}}static backoffSeconds(t,n,o,r){if(n<o)return t;let s=n-o+1;return Math.min(t*Math.pow(2,s),r)}resolveConfig(t){let n=t?.enabled!==!1,o=new Set(t?.pollHooks??qe);o.add("sessionStart");let r=Math.max(0,t?.minPollIntervalSeconds??Ke),s=t?.reactionMode??Xe,i=Math.max(1,t?.circuitBreakerThreshold??Qe),a=Math.max(r,t?.maxBackoffSeconds??Ze);return{enabled:n,hooks:o,minPollIntervalSeconds:r,reactionMode:s,circuitBreakerThreshold:i,maxBackoffSeconds:a}}async fetchInbox(t,n,o,r){let s=new URL(`${t}/api/v1/workspaces/${n.workspaceId}/features/${n.featureId}/inbox`);r&&s.searchParams.set("since",r),s.searchParams.set("limit",String(Ye));let i=new AbortController,a=setTimeout(()=>i.abort(),5e3);try{let c=await fetch(s.toString(),{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:i.signal});if(!c.ok)throw new Error(`inbox poll returned ${c.status}`);let u=await c.json();if(!u||typeof u.serverTs!="string"||typeof u.nextCursor!="string")throw new Error("inbox poll response missing serverTs/nextCursor");return{serverTs:u.serverTs,nextCursor:u.nextCursor,events:Array.isArray(u.events)?u.events:[]}}finally{clearTimeout(a)}}trimRecent(t){return t.length<=Kt?t:t.slice(t.length-Kt)}},it=class{renderForAgent(t,n){if(t.length===0)return"";let o=[`\u26A0\uFE0F Dezycro Command Bus: ${t.length} new event(s) on this feature.`,""];for(let r of t)o.push(this.renderEvent(r));return t.some(r=>r.urgency==="BLOCKING")&&o.push("",this.reactionGuidance(n)),o.join(`
|
|
4
|
+
`)}renderEvent(t){if(t.eventType==="INVALIDATION"){let r=this.stringField(t.payload,"artifactSummary")??"(no summary)",s=this.stringField(t.payload,"reason")??"(no reason)",i=this.stringField(t.payload,"previousStatus")??"?",a=`${t.sourceType.toLowerCase()} ${t.sourceId??"?"}`;return`- INVALIDATION (${t.urgency}): ${a} ("${this.truncate(r,120)}") \u2014 reason: "${this.truncate(s,200)}" \u2014 was ${i}.`}let n=this.summarizePayload(t.payload),o=`${t.sourceType.toLowerCase()}${t.sourceId?` ${t.sourceId}`:""}`;return`- [Dezycro] ${t.eventType} (${t.urgency}) from ${o}${n?`: ${n}`:""}.`}summarizePayload(t){let n=[];for(let[o,r]of Object.entries(t??{}))r!=null&&(Array.isArray(r)?n.push(`${o}=${r.join("|")}`):typeof r!="object"&&n.push(`${o}=${String(r)}`));return this.truncate(n.join(", "),240)}reactionGuidance(t){switch(t){case L.HaltAndReplan:return"Reaction mode: HALT_AND_REPLAN \u2014 pause your current line of work, acknowledge in chat, and re-plan around these invalidations before touching code again.";case L.AcknowledgeAndContinue:return"Reaction mode: ACKNOWLEDGE_AND_CONTINUE \u2014 surface this in your reply but continue current work; do not silently reuse invalidated artifacts.";case L.SilentLog:return"Reaction mode: SILENT_LOG \u2014 recorded for the session transcript; no chat interruption.";default:return""}}stringField(t,n){let o=t?.[n];return typeof o=="string"?o:null}truncate(t,n){return t.length<=n?t:t.slice(0,n-1)+"\u2026"}},M=new st,q=new it;var x=S(require("fs")),Xt=S(require("path"));function Yt(e){switch(e){case"resume":return!0;case"compact":return null;case"startup":case"clear":return!1;default:return!1}}var H=class{file;constructor(t){this.file=Xt.join(t,".dezycro","active-feature.json")}read(){try{let t=JSON.parse(x.readFileSync(this.file,"utf8")),n=typeof t.featureId=="string"?t.featureId:null;return n?{featureId:n,featureName:typeof t.featureName=="string"?t.featureName:void 0,workspaceId:typeof t.workspaceId=="string"?t.workspaceId:void 0,selectedAt:typeof t.selectedAt=="string"?t.selectedAt:void 0,active:t.active===!0}:null}catch{return null}}setActive(t){let n=this.read();if(!n||(n.active??!1)===t)return!1;try{return x.writeFileSync(this.file,`${JSON.stringify({...n,active:t},null,2)}
|
|
5
|
+
`),!0}catch{return!1}}clear(){try{if(x.existsSync(this.file))return x.unlinkSync(this.file),!0}catch{}return!1}};var en=new Set(["DONE","PUSHED"]),nn=["/dezycro:sync","log what we did","log this","save progress","save what we did","wrap up","we're done","we are done","end of session","sweep the workbook"];function on(e){if(typeof e!="string")return null;let t=e.toLowerCase().trim();if(!t)return null;for(let n of nn)if(t.includes(n))return n;return null}var Zt="TRD quality gate (deterministic layer) blocked APPROVED transition: ";async function rn(e){let{event:t,route:n,payload:o,flags:r}=e;return t==="state"?cn(r||{}):t==="authoring-state"?un(r||{}):t==="pre-tool-use"&&n==="update_task"?te(o||{}):t==="pre-tool-use"&&n==="change_document_status"?an(o||{}):t==="post-tool-use"&&n==="edit"?ee(o||{}):t==="post-tool-use"&&n==="workbook_mutation"?ce(o||{}):t==="session-start"?oe(o||{}):t==="session-end"?dn(o||{}):t==="user-prompt-submit"?re(o||{}):t==="stop"?se(o||{}):0}function te(e){let t=C(process.cwd());if(!t)return process.stdout.write("{}"),0;let n=e&&e.tool_input||{},o=String(n.status||"").toUpperCase();if(!en.has(o))return process.stdout.write("{}"),0;let r=n.pathRefs,s=Array.isArray(r)?r.filter(Boolean):[];if(s.length===0)return process.stdout.write("{}"),0;let i=A(t);if((i&&i.companion&&i.companion.autoVerify||{}).preTaskDone===!1)return process.stdout.write("{}"),0;if(Tt({repoRoot:t,paths:s})){if(g(t,"pre_done_gate_pass",{status:o,pathRefs:s}),o==="PUSHED"){let p=sn(t,i);if(p){g(t,"pre_push_coverage_nudge",{line:p});let h={systemMessage:p};return process.stdout.write(JSON.stringify(h)),0}}return process.stdout.write("{}"),0}E(t,"verifyAutoRuns"),E(t,"verifyAutoFailures"),$(t,"pre_done_gate_block",{status:o,pathRefs:s});let u=vt({paths:s,status:o}).reason,l={decision:"block",reason:u,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:u}};return process.stdout.write(JSON.stringify(l)),0}function sn(e,t){if((t&&t.companion&&t.companion.coverageNudges||{}).enabled===!1)return null;let o=k(e).coverageSnapshot,r=J(o);return r?`[Companion] ${r} \u2014 verify passed, but consider checking these before push.`:null}function an(e){let n=e&&e.tool_input||{},o=n.status||n.newStatus||null,r=n.docId||n.documentId||n.id||null;if(o!=="APPROVED")return process.stdout.write("{}"),0;let s=C(process.cwd());if(!s)return process.stdout.write("{}"),0;let i=Lt(s);if(!i||r&&i.docId&&i.docId!==r)return $(s,"quality_gate_degraded_permit",{reason:i?"docId_mismatch":"sidecar_missing",docId:r,sidecarDocId:i?i.docId:null}),process.stdout.write("{}"),0;if(i.docType!=="TRD")return process.stdout.write("{}"),0;let a=Ut({trdContent:i.content,prdContent:i.prdContent||""});if(a.passed)return g(s,"quality_gate_deterministic_pass",{docId:i.docId}),process.stdout.write("{}"),0;E(s,"qualityGateFailures");let c=a.blockingIssues.map(p=>`[${p.section}] ${p.issue}`).slice(0,5).join(" | "),u=`${Zt}${c||"unspecified failure"}. Fix and try again.`;$(s,"quality_gate_blocked",{docId:i.docId,issues:a.blockingIssues});let l={decision:"block",reason:u,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:u}};return process.stdout.write(JSON.stringify(l)),0}async function ee(e){let t=C(process.cwd());if(!t)return 0;{let y=A(t),P=k(t),f=await M.pollIfDue(t,"postToolUse",y?.companion?.commandBus,P);f.statePatch&&d(t,f.statePatch);let w=q.renderForAgent(f.events,f.reactionMode);if(w){let T={hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:w}};return process.stdout.write(JSON.stringify(T)),0}}let n=e&&e.tool_input||{},o=ne(n);if(o.length===0)return 0;let r=A(t),s=r&&r.companion&&r.companion.autoVerify||{},i=Array.isArray(s.controllerPaths)?s.controllerPaths:[],a=s.postEditPulse&&s.postEditPulse.enabled!==!1,c=s.postEditPulse&&Number(s.postEditPulse.threshold)||5,u=Pt(o,i),l=new Date().toISOString(),h=(k(t).pulseCount||0)+1,v=a&&h>=c&&u.length>0;if(u.length===0)d(t,{pulseCount:h,lastEditTs:l});else for(let y=0;y<u.length;y+=1){let P=y===u.length-1,f={pushControllerEdit:{path:u[y],editedTs:l}};P&&(f.needsSpecPublish=!0,f.lastEditTs=l,f.pulseCount=v?0:h),d(t,f)}if(g(t,"post_edit",{edited:o.length,matchedControllers:u.length,pulseCount:v?0:h,nudged:v}),v){let y="[Companion] "+h+" edits since last verify and controller paths were touched ("+u.join(", ")+"). Consider running `/dezycro:verify` to catch regressions early.";process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:y}}))}return 0}function ne(e){if(!e||typeof e!="object")return[];let t=e.file_path;if(typeof t=="string")return[t];let n=e.path;if(typeof n=="string")return[n];let o=e.edits;if(Array.isArray(o)){let r=new Set;for(let s of o)s&&typeof s.file_path=="string"&&r.add(s.file_path);return Array.from(r)}return[]}function cn(e){let t=C(process.cwd())||process.cwd();if(e.get){let n=k(t);return process.stdout.write(JSON.stringify(n,null,2)),0}if(typeof e.patch=="string"){let n;try{n=JSON.parse(e.patch)}catch(r){let s=r.message;return process.stdout.write(JSON.stringify({ok:!1,error:"invalid JSON for --patch: "+s})),1}return n===null||typeof n!="object"||Array.isArray(n)?(process.stdout.write(JSON.stringify({ok:!1,error:"--patch must be a JSON object"})),1):d(t,n)===null?(process.stdout.write(JSON.stringify({ok:!1,error:"lock contention \u2014 state not patched"})),1):(process.stdout.write(JSON.stringify({ok:!0})),0)}return process.stdout.write(JSON.stringify({ok:!1,error:"state requires one of --get or --patch=<json>"})),1}function un(e){let t=C(process.cwd())||process.cwd(),n=e.set||(e.get?"get":null);if(n==="enter"){let o=e["doc-id"],r=e["doc-type"];if(!o||!r)return process.stdout.write(JSON.stringify({ok:!1,error:"--doc-id and --doc-type are required for --set=enter"})),1;try{let s=Ot(t,{docId:o,docType:r,featureId:e["feature-id"]||null,seed:e.seed||null});return process.stdout.write(JSON.stringify({ok:!0,transcript:s})),0}catch(s){return process.stdout.write(JSON.stringify({ok:!1,error:s.message})),1}}if(n==="exit")try{return Dt(t),process.stdout.write(JSON.stringify({ok:!0})),0}catch(o){return process.stdout.write(JSON.stringify({ok:!1,error:o.message})),1}if(n==="get"){let o=Nt(t);return process.stdout.write(JSON.stringify({ok:!0,...o})),0}return process.stdout.write(JSON.stringify({ok:!1,error:"authoring-state requires one of --set=enter, --set=exit, --get"})),1}var ln=600;async function oe(e){let t=C(process.cwd());if(!t)return 0;d(t,{sessionAutoLogCount:0,turnAutoLogCount:0,currentTurnId:0,lastAutoWorkbookMutation:null,coverageFetchPending:!1});let n=Yt(e.source);n!==null&&new H(t).setActive(n),g(t,"session_start",{source:e.source??null,active:n});let o=A(t),r=k(t),s=await M.pollIfDue(t,"sessionStart",o?.companion?.commandBus,r);return s.statePatch&&d(t,s.statePatch),0}async function dn(e){let t=C(process.cwd());if(!t)return 0;let n=new H(t).setActive(!1);return g(t,"session_end",{reason:e.reason??null,deactivated:n}),0}async function re(e){let t=C(process.cwd());if(!t)return 0;let n=k(t),o=(n.currentTurnId||0)+1;d(t,{currentTurnId:o,turnAutoLogCount:0});{let i=A(t),a=await M.pollIfDue(t,"userPromptSubmit",i?.companion?.commandBus,n);a.statePatch&&d(t,a.statePatch);let c=q.renderForAgent(a.events,a.reactionMode);if(c){let u={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:c}};return process.stdout.write(JSON.stringify(u)),0}}let r=String(e?.prompt??e?.user_message??e?.message??"").trim(),s=on(r);if(s&&(d(t,{pendingWorkbookSweep:!0,pendingWorkbookSweepReason:"wrap_phrase:"+s}),g(t,"wrap_phrase_detected",{phrase:s})),r&&Gt(r)){let i=n.lastAutoWorkbookMutation;if(i&&zt(i,o)){let a=Jt(i);if(a){E(t,"undoCount"),d(t,{lastAutoWorkbookMutation:null}),g(t,"undo_directive",{tool:a.tool,entityId:i.entityId});let u={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:"[Dezycro Companion \u2014 undo] "+a.humanLine+`
|
|
5
6
|
|
|
6
7
|
Call `+a.tool+` with arguments:
|
|
7
8
|
`+JSON.stringify(a.args,null,2)+`
|
|
8
9
|
|
|
9
|
-
Then confirm the reversal in one short sentence.`}};return process.stdout.write(JSON.stringify(
|
|
10
|
+
Then confirm the reversal in one short sentence.`}};return process.stdout.write(JSON.stringify(u)),0}}}{let i=pn(t);if(i){g(t,"publish_spec_nudge",{paths:i.count});let a={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:i.text}};return process.stdout.write(JSON.stringify(a)),0}}return fn(t,n),process.stdout.write("{}"),0}function pn(e){let t=A(e);if((t&&t.companion&&t.companion.autoVerify||{}).autoPublishSpecOnControllerChange===!1)return null;let o=wt(e);if(!o.needed||o.controllerPaths.length===0)return null;let r=8,s=o.controllerPaths,i=s.slice(0,r).map(c=>" - "+c);return s.length>r&&i.push(" - \u2026and "+(s.length-r)+" more"),{text:"[Dezycro Companion] "+s.length+` controller/API file(s) changed since the last published spec:
|
|
10
11
|
`+i.join(`
|
|
11
|
-
`)+"\n\nNew or changed endpoints are NOT yet reflected in the published OpenAPI spec, so verification cannot see them. Before continuing, run `/dezycro:publish-spec` to upload the updated spec (this regenerates the JEP), then `/dezycro:verify` to check the feature against it. This reminder repeats each turn until the spec is published and clears automatically after `/dezycro:publish-spec`.",count:s.length}}function
|
|
12
|
+
`)+"\n\nNew or changed endpoints are NOT yet reflected in the published OpenAPI spec, so verification cannot see them. Before continuing, run `/dezycro:publish-spec` to upload the updated spec (this regenerates the JEP), then `/dezycro:verify` to check the feature against it. This reminder repeats each turn until the spec is published and clears automatically after `/dezycro:publish-spec`.",count:s.length}}function fn(e,t){if(A(e)?.companion?.coverageNudges?.enabled===!1)return;let o=t.coverageSnapshot;if(!o||o.surfacedTs)return;let r=J(o);if(!r)return;d(e,{coverageSnapshot:{...o,surfacedTs:new Date().toISOString()}}),g(e,"coverage_one_liner",{line:r});let s={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:"[Companion] "+r}};process.stdout.write(JSON.stringify(s))}async function se(e){let t=C(process.cwd());if(!t)return 0;let n=A(t);{let w=k(t),T=await M.pollIfDue(t,"stop",n?.companion?.commandBus,w);T.statePatch&&d(t,T.statePatch);let R=q.renderForAgent(T.events,T.reactionMode);if(R){let X={systemMessage:R};return process.stdout.write(JSON.stringify(X)),0}}let o=n?.companion?.autoWorkbookUpdates||{};if(n?.companion?.safeMode===!0||o.enabled===!1)return at();let r=k(t),s=!!(r.lastVerifyCompletionTs&&r.lastVerifyCompletionTs!==r.lastSeenVerifyCompletionTs);if(!!!(r.pendingWorkbookSweep||s))return g(t,"stop_skip",{reason:"no_trigger"}),s&&d(t,{lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs}),K(t,r);if(r.authoringMode)return g(t,"stop_skip",{reason:"authoring_mode"}),d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs}),K(t,r);let a=Number(o.maxAutoLogsPerTurn??2),c=Number(o.maxAutoLogsPerSession??10),u=Math.max(0,a-(r.turnAutoLogCount||0)),l=Math.max(0,c-(r.sessionAutoLogCount||0));if(u===0||l===0)return g(t,"stop_skip",{reason:"caps_exhausted",perTurnSlotsLeft:u,perSessionSlotsLeft:l}),d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs}),K(t,r);if(r.suppressWorkbookUpdatesUntil&&Date.now()<new Date(r.suppressWorkbookUpdatesUntil).getTime())return g(t,"stop_skip",{reason:"suppressed"}),d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs}),K(t,r);d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs});let p=pt(n),h=[...r.recentDecisionHashes||[],...r.recentIssueHashes||[],...r.recentAssumptionHashes||[]],v=ae(t),y=jt({thresholds:p,perTurnSlotsLeft:u,perSessionSlotsLeft:l,recentHashes:h,activeFeatureId:v.featureId,activeFeatureName:v.featureName});g(t,"stop_sweep_directive",{perTurnSlotsLeft:u,perSessionSlotsLeft:l,recentHashCount:h.length});let P=ie(t,r);P&&(y+=`
|
|
12
13
|
|
|
13
|
-
`+P);let
|
|
14
|
-
`).filter(Boolean),r=0,s=!1;for(let i=o.length-1;i>=0;i-=1){let a;try{a=JSON.parse(o[i])}catch{continue}let
|
|
14
|
+
`+P);let f={decision:"block",reason:y};return process.stdout.write(JSON.stringify(f)),0}function K(e,t){let n=ie(e,t);if(!n)return at();g(e,"stop_coverage_delta",{delta:n});let o={systemMessage:n};return process.stdout.write(JSON.stringify(o)),0}function ie(e,t){let n=t.lastVerifyCompletionTs,o=t.coverageSnapshot;if(!n||!o)return null;let r=o.surfacedTs?new Date(o.surfacedTs).getTime():0;if(new Date(n).getTime()<=r)return null;let i=Wt(o.previous||null,o);return i?(d(e,{coverageSnapshot:{...o,surfacedTs:new Date().toISOString()}}),"[Companion] "+i):null}function at(){return process.stdout.write("{}"),0}function gn(e){try{let t=e?.transcript_path;if(!t||typeof t!="string"||!U.existsSync(t))return null;let o=U.readFileSync(t,"utf8").split(`
|
|
15
|
+
`).filter(Boolean),r=0,s=!1;for(let i=o.length-1;i>=0;i-=1){let a;try{a=JSON.parse(o[i])}catch{continue}let c=a?.message?.role??a?.role;if(c==="assistant"){let u=JSON.stringify(a.message?.content??a.content??"");r+=u.length}else if(c==="user"){if(s)break;s=!0;let u=JSON.stringify(a.message?.content??a.content??"");r+=u.length}if(r>1e6)break}return r}catch{return null}}function mn(e){return ae(e).featureId}function ae(e){try{let t=U.readFileSync(Qt.join(e,".dezycro","active-feature.json"),"utf8"),n=JSON.parse(t);return{featureId:n?.featureId||null,featureName:n?.featureName||null}}catch{return{featureId:null,featureName:null}}}function A(e){try{return dt(e)}catch{return JSON.parse(JSON.stringify(D))}}function ce(e){let t=C(process.cwd());if(!t)return 0;let n=e?.tool_name||"",o=$t(n);if(!o)return 0;let r=e?.tool_input||{},s=e?.tool_response||e?.response||{},i=String(r.summary||r.title||r.description||r.statement||""),a=r.taskId||r.linkedTaskId||null,c=Bt(o,i,a),u=s.data,l=s.id||(u?u.id:void 0)||r.taskId||null,p=k(t),h=(p.turnAutoLogCount||0)+1,v=(p.sessionAutoLogCount||0)+1,y={turnAutoLogCount:h,sessionAutoLogCount:v,lastAutoWorkbookMutation:{tool:n.split("__").pop()||"",entityId:l,createdInTurnId:p.currentTurnId||0,createdTs:new Date().toISOString(),priorStatus:r.priorStatus||null}};return o==="decision"&&(y.pushDecisionHash=c),o==="issue"&&(y.pushIssueHash=c),o==="assumption"&&(y.pushAssumptionHash=c),d(t,y),E(t,o==="decision"?"autoDecisionLogs":o==="issue"?"autoIssueLogs":o==="assumption"?"autoAssumptionLogs":"autoTaskStatusLogs"),g(t,"workbook_mutation",{kind:o,entityId:l,turnAutoLogCount:h,sessionAutoLogCount:v}),process.stdout.write("{}"),0}var hn=ne,yn=ee,Sn=te,kn=se,bn=re,Tn=ce,vn=oe,wn=mn,Cn=gn,In=ln;0&&(module.exports={QUALITY_GATE_REASON_PREFIX,TRIVIAL_TURN_CHAR_DEFAULT_EXPORT,_extractEditedPaths,_handlePostEdit,_handleSessionStart,_handleStop,_handleUpdateTaskGate,_handleUserPromptSubmit,_handleWorkbookMutation,_readActiveFeatureId,_transcriptCharsSinceLastStop,dispatch});
|
package/hooks/companion-runner
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* companion-runner <event> [--route=<route>] [--flag[=value] ...]
|
|
7
7
|
*
|
|
8
8
|
* where <event> is one of:
|
|
9
|
-
* session-start | user-prompt-submit | pre-tool-use | post-tool-use
|
|
10
|
-
* | state | authoring-state
|
|
9
|
+
* session-start | session-end | user-prompt-submit | pre-tool-use | post-tool-use
|
|
10
|
+
* | stop | state | authoring-state
|
|
11
11
|
*
|
|
12
12
|
* Reads the hook payload from stdin (JSON, when applicable), dispatches to
|
|
13
13
|
* lib/router.js, and exits with the appropriate status code / stdout payload
|
package/hooks/hooks.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dezycro-ai/agent-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Official Dezycro plugin for AI coding agents (Claude Code today, more to come) — reverse-engineer your codebase into features, PRDs, TRDs, and live verification.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dezycro": "./dist/cli/dezycro.js"
|
package/skills/work/SKILL.md
CHANGED
|
@@ -133,7 +133,8 @@ Write `.dezycro/active-feature.json` using the Write tool:
|
|
|
133
133
|
{
|
|
134
134
|
"featureId": "<resolved featureId>",
|
|
135
135
|
"featureName": "<resolved featureName>",
|
|
136
|
-
"selectedAt": "<current ISO 8601 timestamp>"
|
|
136
|
+
"selectedAt": "<current ISO 8601 timestamp>",
|
|
137
|
+
"active": true
|
|
137
138
|
}
|
|
138
139
|
```
|
|
139
140
|
|
|
@@ -141,6 +142,17 @@ Create the `.dezycro/` directory first if it doesn't exist: `mkdir -p .dezycro`
|
|
|
141
142
|
|
|
142
143
|
**This file is a pointer, not state.** It tells `/dezycro:status` and `/dezycro:verify` which feature is active. Any actual work — tasks, issues, decisions — lives in the workbook.
|
|
143
144
|
|
|
145
|
+
**`active` is session-scoped.** It's the signal the companion uses to decide whether to nudge — so always write `active: true` here (you're binding because work is starting). The companion manages it after that:
|
|
146
|
+
- A fresh session (`claude` startup) or `/clear` starts **inactive** — the agent must `/dezycro:work` to (re)bind before any nudge fires. So binding here is what re-activates after a handoff into a new session.
|
|
147
|
+
- Resuming a session (`/resume`, `--continue`) **restores** `active` automatically — no re-bind needed.
|
|
148
|
+
- Ending a session sets `active: false` but keeps the pointer, so `/dezycro:work` with no argument resumes this same feature.
|
|
149
|
+
|
|
150
|
+
When the feature is **done** (shipped via `promote_to_regression`, or the developer moves on / says they're done), clear the pointer so it no longer counts as active:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
dezycro feature --clear
|
|
154
|
+
```
|
|
155
|
+
|
|
144
156
|
## Step 7: Display Summary
|
|
145
157
|
|
|
146
158
|
Present the feature context to the user in this format:
|