@givolt/i3-composer-agent 2.3.3 → 2.3.4

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.
Files changed (42) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/README.md +2 -2
  6. package/bin/i3-composer-agent.cjs +18 -15
  7. package/bin/wizard.cjs +12 -52
  8. package/docs/codebuddy-setup.md +21 -7
  9. package/docs/codex-setup.md +1 -1
  10. package/docs/other-hosts.md +1 -1
  11. package/docs/workbuddy-setup.md +18 -2
  12. package/hooks/manage.cjs +22 -14
  13. package/package.json +5 -1
  14. package/plugin.json +1 -1
  15. package/scripts/ui-prototype-buddy-lifecycle-test.js +89 -0
  16. package/scripts/ui-prototype-codebuddy-hook-test.js +1 -1
  17. package/scripts/ui-prototype-dependencies-test.js +13 -0
  18. package/scripts/ui-prototype-execution-gates-test.js +24 -0
  19. package/scripts/ui-prototype-execution-policy-test.js +12 -1
  20. package/scripts/ui-prototype-recovery-test.js +72 -0
  21. package/skills/i3-ui-prototype/SKILL.md +5 -3
  22. package/skills/i3-ui-prototype/assets/hosts/codebuddy/profile.json +10 -6
  23. package/skills/i3-ui-prototype/references/entry-contract.md +1 -1
  24. package/skills/i3-ui-prototype/references/execution-evidence.md +25 -8
  25. package/skills/i3-ui-prototype/references/interaction-acceptance.md +1 -1
  26. package/skills/i3-ui-prototype/references/project-layout.md +1 -1
  27. package/skills/i3-ui-prototype/scripts/buddy-host.cjs +40 -0
  28. package/skills/i3-ui-prototype/scripts/codebuddy-context.cjs +14 -14
  29. package/skills/i3-ui-prototype/scripts/codebuddy-hook.cjs +74 -35
  30. package/skills/i3-ui-prototype/scripts/codebuddy-registration.cjs +7 -5
  31. package/skills/i3-ui-prototype/scripts/execution-evidence.cjs +1 -1
  32. package/skills/i3-ui-prototype/scripts/execution-gates.cjs +23 -2
  33. package/skills/i3-ui-prototype/scripts/execution-policy.cjs +28 -7
  34. package/skills/i3-ui-prototype/scripts/execution-recovery.cjs +52 -0
  35. package/skills/i3-ui-prototype/scripts/execution-runtime.cjs +1 -1
  36. package/skills/i3-ui-prototype/scripts/hosts/codebuddy.cjs +9 -5
  37. package/skills/i3-ui-prototype/scripts/native-command.cjs +18 -0
  38. package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +2 -4
  39. package/skills/i3-ui-prototype/scripts/native-pilot.cjs +5 -1
  40. package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +28 -6
  41. package/skills/i3-ui-prototype/scripts/prototype-hooks.cjs +13 -13
  42. package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +1 -1
@@ -4,7 +4,7 @@
4
4
  const fs=require('node:fs'),path=require('node:path'),os=require('node:os'),assert=require('node:assert/strict');
5
5
  const R=require('./codebuddy-registration.cjs'),C=require('./codebuddy-context.cjs'),E=require('./execution-evidence.cjs');
6
6
  function context(options) {
7
- assert.equal(options.host||'codebuddy','codebuddy','A+B supports CodeBuddy only');
7
+ const host=options.host||'codebuddy';assert.ok(['codebuddy','workbuddy'].includes(host),'A+B supports CodeBuddy and WorkBuddy');
8
8
  const project=fs.realpathSync(path.resolve(options.project||'.')),scope=options.scope;
9
9
  assert.ok(['project','global'].includes(scope),'Specify project or global installation scope');
10
10
  const base=scope==='project'?path.join(project,'.codebuddy'):C.globalBase(options);
@@ -12,7 +12,7 @@ function context(options) {
12
12
  const record=recordText?JSON.parse(recordText):null;
13
13
  const unified=path.join(base,'i3-composer-agent/hooks/i3-prototype.cjs');
14
14
  const scriptPath=options.entryPath||(fs.existsSync(unified)?unified:path.join(base,'skills/i3-ui-prototype/scripts/hosts/codebuddy.cjs'));
15
- return {project,scope,base,record,scriptPath};
15
+ return {project,scope,base,record,scriptPath,host};
16
16
  }
17
17
  function prepare(options,runtime={}) {
18
18
  const current=runtime.current||context(options),readText=runtime.readText||R.fileIO.read,action=options.action||'keep';
@@ -21,12 +21,8 @@ function prepare(options,runtime={}) {
21
21
  if(action==='disable'){const settings=R.config(readText(path.join(current.base,'settings.json')));assert.ok(!Object.values(settings.hooks||{}).flat().some(g=>g.hooks.some(h=>h.command?.includes(R.OWNER))),'Unknown owned-looking Hook has no ownership record; preserve it for review');}
22
22
  return {summary:{status:'not-enabled',scope:current.scope,changed:false},unchanged:true};
23
23
  }
24
- const product=options.product||current.record?.product;assert.ok(['ide','cli'].includes(product),'Use --codebuddy-product ide or cli when enabling Hooks');
25
- if(action!=='disable') {
26
- const manifest=readText(path.join(current.base,'.i3-composer-agent.json'));
27
- if(manifest)assert.ok(!JSON.parse(manifest).agents?.includes('workbuddy'),'Shared CodeBuddy/WorkBuddy registration cannot safely identify the product; preserve settings and use an exclusive CodeBuddy scope');
28
- assert.ok(!options.agents?.includes('workbuddy')||current.scope!=='project','Do not enable CodeBuddy audit in a shared WorkBuddy project target');
29
- }
24
+ assert.ok(options.product===undefined||['auto','ide','cli'].includes(options.product),'Invalid Buddy product profile');
25
+ const product='auto'; // Legacy product arguments are hints, never a second new registration.
30
26
  const explicit=options.transcriptRoot,roots=explicit==='auto'?[]:explicit?[path.resolve(explicit)]:current.record?.transcriptRoots||[];
31
27
  const mode=explicit?(explicit==='auto'?'auto':'manual'):current.record?.transcriptMode||(roots.length?'manual':'auto');
32
28
  if(action!=='disable') {
@@ -34,7 +30,7 @@ function prepare(options,runtime={}) {
34
30
  }
35
31
  const settingsText=readText(path.join(current.base,'settings.json')),settings=R.config(settingsText);
36
32
  if(action!=='disable')assert.ok(!settings.disableAllHooks&&!settings.allowManagedHooksOnly,'Host policy prevents automatic auditing; preserve the existing policy');
37
- return R.plan({action,...current,product,nodePath:process.execPath,scriptDigest:runtime.scriptDigest||C.bundle(options.entrySource||(path.basename(current.scriptPath)==='i3-prototype.cjs'?current.scriptPath:path.join(__dirname,'hosts/codebuddy.cjs'))),currentText:settingsText,
33
+ return R.plan({action,...current,product,hosts:options.hosts,agents:options.agents,nodePath:process.execPath,scriptDigest:runtime.scriptDigest||C.bundle(options.entrySource||(path.basename(current.scriptPath)==='i3-prototype.cjs'?current.scriptPath:path.join(__dirname,'hosts/codebuddy.cjs'))),currentText:settingsText,
38
34
  transcriptMode:mode,transcriptRoots:roots,productVersion:options.productVersion||current.record?.productVersion||'unreported'});
39
35
  }
40
36
  function configure(options) {
@@ -51,8 +47,9 @@ function check(options,runtime={}) {
51
47
  return {...result,status:result.filesPresent?'files-present':'not-enabled'};
52
48
  }
53
49
  try {
54
- const record=(runtime.registration||C.registration)(current.base),effective=(runtime.select||C.select)({project:current.project,product:record.product,home:options.home,env:options.env});
55
- Object.assign(result,{product:record.product,productVersion:record.productVersion,registrationDigest:record.registrationDigest,effective:effective?.base===current.base,transcriptRoots:record.transcriptRoots,
50
+ const record=(runtime.registration||C.registration)(current.base),host=options.host||'codebuddy',products=record.product==='auto'?(host==='workbuddy'?['ide']:['ide','cli']):[record.product];
51
+ const effectiveByProduct=Object.fromEntries(products.map(product=>[product,(runtime.select||C.select)({project:current.project,product,host,home:options.home,env:options.env})?.base===current.base]));
52
+ Object.assign(result,{host,product:record.product,productVersion:record.productVersion,registrationDigest:record.registrationDigest,effective:Object.values(effectiveByProduct).every(Boolean),effectiveByProduct,transcriptRoots:record.transcriptRoots,
56
53
  transcript:{mode:record.transcriptMode||(record.transcriptRoots?.length?'manual':'auto'),status:'awaiting-native-event',invocationVerified:false}});
57
54
  let observation=null;
58
55
  const parent=path.join(current.base,'state/i3-prototype',C.projectKey(current.project));
@@ -62,18 +59,21 @@ function check(options,runtime={}) {
62
59
  const raw=R.fileIO.read(path.join(parent,name,'session.json'));if(!raw)continue;
63
60
  entries.push(...Object.values(JSON.parse(raw).bindings||{}));
64
61
  }
65
- for(const entry of entries)if(entry.project===current.project&&entry.registrationDigest===record.registrationDigest&&entry.settingsDigest===record.settingsDigest&&(!observation||entry.lastHook.at>observation.lastHook.at))observation=entry;
62
+ for(const entry of entries)if((entry.host||'codebuddy')===host&&entry.project===current.project&&entry.registrationDigest===record.registrationDigest&&entry.settingsDigest===record.settingsDigest&&(!observation||entry.lastHook.at>observation.lastHook.at))observation=entry;
66
63
  if(observation) {
67
64
  result.transcript.path=observation.source.path;
68
65
  C.prefix(observation.source.path,observation.source.bytes,observation.source.digest);
69
66
  C.source({session_id:observation.sessionId,transcript_path:observation.source.path},record,current.project);
70
67
  Object.assign(result.transcript,{status:'verified',invocationVerified:true});
71
68
  result.observation={sessionId:observation.sessionId,projectKey:observation.projectKey,runId:observation.runId,event:observation.lastHook.event,at:observation.lastHook.at};
69
+ const observedProduct=observation.product||(record.product!=='auto'?record.product:null);
70
+ assert.ok(['ide','cli'].includes(observedProduct),'Observed native product identity missing');
71
+ Object.assign(result,{product:observedProduct,productVersion:observation.productVersion||'unreported',effective:effectiveByProduct[observedProduct]===true,identitySource:observation.identitySource||'legacy-profile',recovery:observation.recovery||null});
72
72
  }
73
73
  const recent=observation&&Date.now()-Date.parse(observation.lastHook.at)>=0&&Date.now()-Date.parse(observation.lastHook.at)<30*60*1000;
74
74
  return {...result,status:result.effective&&recent?'active':record.product==='cli'?'pending-host-review':'registered',
75
75
  activationScope:'Only the displayed project/session/configuration and adapter bytes; not all projects or business acceptance',
76
- next:record.product==='cli'?'Review /hooks in CodeBuddy Code, then use a new session.':'Reload CodeBuddy and verify a real prototype write task.'};
76
+ next:'CLI: review /hooks; desktop: reload. Verify an actual Skill invocation and missing-Skill recovery in the chosen host.'};
77
77
  }catch(error){if(result.transcript)Object.assign(result.transcript,{status:'unverified',invocationVerified:false});return {...result,status:'failed',errors:[error.message]};}
78
78
  }
79
79
  function main(argv=process.argv.slice(2)) {
@@ -12,7 +12,7 @@ function globalSkillsDir(agent, home, env) {
12
12
  let base = home;
13
13
  if (config.base === 'config') base = env.XDG_CONFIG_HOME && path.isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : path.join(home, '.config');
14
14
  if (config.base === 'env') base = env[config.env]?.trim() || path.join(home, config.fallback);
15
- if (config.env === 'CODEBUDDY_CONFIG_DIR' && (!path.isAbsolute(base) || /[\x00\r\n]/.test(base))) throw new Error('CODEBUDDY_CONFIG_DIR must be an absolute safe directory');
15
+ if (['CODEBUDDY_CONFIG_DIR','WORKBUDDY_CONFIG_DIR'].includes(config.env) && (!path.isAbsolute(base) || /[\x00\r\n]/.test(base))) throw new Error(`${config.env} must be an absolute safe directory`);
16
16
  if (config.alternatives && !fs.existsSync(path.join(home, path.dirname(config.path)))) {
17
17
  const alternative = config.alternatives.find(relative => fs.existsSync(path.join(home, path.dirname(relative))));
18
18
  if (alternative) return path.join(home, alternative);