@rockysoft/opencode-hybrid-workflow 0.6.0 → 0.7.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/README.md CHANGED
@@ -176,5 +176,5 @@ Remove the old plugin entries from `opencode.json` and `cli.json` before adding
176
176
  </details>
177
177
 
178
178
  [Advanced configuration](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.4.0/docs/configuration.md) · [MIT license](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.4.0/LICENSE) · [Third-party notices](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.4.0/THIRD_PARTY_NOTICES.md)
179
-
180
- For deterministic Vitest projects, see [faster verification](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.6.0/docs/verification.md). Full checks remain the default.
179
+
180
+ For deterministic Vitest projects, see [faster verification](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.7.0/docs/verification.md). Full checks remain the default; eligible projects automatically choose full checks when measured optimization overhead is higher.
package/docs/CHANGELOG.md CHANGED
@@ -1,12 +1,21 @@
1
- # Release notes
2
-
3
- ## 0.6.0
4
-
5
- - Reuse verified full test results when declared deterministic inputs are unchanged.
6
- - Run affected Vitest tests for known static source changes, with full checks as the fallback.
7
- - Clearly distinguish fresh, reused and targeted results.
8
-
9
- Full checks remain the default. [Set up faster verification](./verification.md) for eligible projects; reviewer approval rules are unchanged.
1
+ # Release notes
2
+
3
+ ## 0.7.0
4
+
5
+ - Choose full or optimized verification automatically from measured execution and input-checking costs.
6
+ - Avoid dependency hashing when ordinary full checks are faster; reconsider when the suite grows.
7
+ - Learn when a targeted test selection costs more than a full run.
8
+ - Keep timing-history failures from interrupting verification.
9
+
10
+ Existing deterministic verification contracts work without new settings. Projects without a contract continue to run full checks.
11
+
12
+ ## 0.6.0
13
+
14
+ - Reuse verified full test results when declared deterministic inputs are unchanged.
15
+ - Run affected Vitest tests for known static source changes, with full checks as the fallback.
16
+ - Clearly distinguish fresh, reused and targeted results.
17
+
18
+ Full checks remain the default. [Set up faster verification](./verification.md) for eligible projects; reviewer approval rules are unchanged.
10
19
 
11
20
  ## 0.5.0
12
21
 
@@ -1,52 +1,60 @@
1
- # Faster verification
2
-
3
- Full checks work as before. For deterministic **Vitest 4** projects, Hybrid can reuse an unchanged full result or run tests related to a known source change. It always labels which happened. Reviewer approval remains separate.
4
-
5
- Ask your planning agent to establish the input contract from your actual tests and dependencies. Keep full checks if those assumptions cannot be established. This first adapter supports exactly `"test": "vitest run"`, without `pretest` or `posttest`; other scripts still execute normally.
6
-
7
- Example in the project's `package.json`:
8
-
9
- ```json
10
- {
11
- "scripts": { "test": "vitest run" },
12
- "hybrid": {
13
- "verification": {
14
- "version": 1,
15
- "scripts": {
16
- "test": {
17
- "runner": "vitest",
18
- "deterministic": true,
19
- "externalInputs": false,
20
- "unrelatedDocuments": ["README.md"],
21
- "outputs": ["node_modules/.vite"],
22
- "relatedSources": ["src/money.ts"]
23
- }
24
- }
25
- }
26
- }
27
- }
28
- ```
29
-
30
- These are **explicit project assertions**, not conclusions inferred by the plugin:
31
-
32
- - `deterministic`: the same inputs produce the same test outcome. Time, randomness and external service state must not affect it.
33
- - `externalInputs: false`: tests read only bound project files, installed dependencies and the child process environment. Git history/status, user-home files, OS services, global tools launched by tests and other undeclared external state are not covered. Such checks must run fresh.
34
- - `unrelatedDocuments`: literal Markdown/text files proven not to affect any test. No globs. All other project files, including Git-ignored fixtures and `.env`, are fingerprinted.
35
- - `outputs`: disposable output directories that tests never use as inputs. Supported: `coverage`, `node_modules/.vite`, `node_modules/.vite-temp`. Never exclude test fixtures or source. Output exclusion is an assertion; a test that reads its prior cache is not eligible.
36
- - `relatedSources`: existing implementation modules with a known static import relationship to tests, including transitive consumers. Do not declare modules with computed imports, runtime file loading, global side effects across test files, custom loader dependencies or dependencies outside Vitest's graph. A conservative scan rejects common dynamic-loading cases; it is **not a general dependency proof**. Omit this list when unsure; unchanged-input reuse still works.
37
-
38
- Every optimization starts with a successful, complete, nonempty full Vitest run. The receipt binds the project, contract, complete nonexcluded file inventory, installed dependency bytes, file permissions, lockfiles, configuration, Node/platform, pnpm implementation and child environment. Secrets are hashed, never copied into receipts. Raw test output can still contain what your tests print, just as with ordinary verification.
39
-
40
- An exact match may reuse the previous result and reports its original execution time. Changes limited to declared static sources may run `vitest related --run`; that result is **selected-tests-only**, not a full-suite pass. An empty selection falls back to full verification. Unknown/new/deleted files, changed tests/fixtures/configuration, toolchain or environment changes use a full run. External links or unsupported launchers disable optimization. A failed fresh full attempt invalidates the older success. Reuse is local to the project; nothing is uploaded to a cache service.
41
-
42
- For a fresh full check, the agent calls:
43
-
44
- ```json
45
- { "action": "verify", "script": "test", "verificationMode": "full" }
46
- ```
47
-
48
- Use fresh full checks for final acceptance or whenever the dependency assumptions are uncertain. Existing typecheck, lint and build scripts still run in full. No verification result grants reviewer approval, and no automatic model or environment changes are made.
49
-
50
- Hashing dependencies has a cost; small suites may gain little or nothing from targeting. Validate elapsed time on your project. Browser scenario/evidence reuse is a separate feature and is not included here.
51
-
52
- Reference: [Vitest related CLI and its static import limitations](https://vitest.dev/guide/cli.html#vitest-related).
1
+ # Faster verification
2
+
3
+ Full checks work as before. For deterministic **Vitest 4** projects, Hybrid can reuse an unchanged full result or run tests related to a known source change. It always labels which happened. Reviewer approval remains separate.
4
+
5
+ Ask your planning agent to establish the input contract from your actual tests and dependencies. Keep full checks if those assumptions cannot be established. This first adapter supports exactly `"test": "vitest run"`, without `pretest` or `posttest`; other scripts still execute normally.
6
+
7
+ Example in the project's `package.json`:
8
+
9
+ ```json
10
+ {
11
+ "scripts": { "test": "vitest run" },
12
+ "hybrid": {
13
+ "verification": {
14
+ "version": 1,
15
+ "scripts": {
16
+ "test": {
17
+ "runner": "vitest",
18
+ "deterministic": true,
19
+ "externalInputs": false,
20
+ "unrelatedDocuments": ["README.md"],
21
+ "outputs": ["node_modules/.vite"],
22
+ "relatedSources": ["src/money.ts"]
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ These are **explicit project assertions**, not conclusions inferred by the plugin:
31
+
32
+ - `deterministic`: the same inputs produce the same test outcome. Time, randomness and external service state must not affect it.
33
+ - `externalInputs: false`: tests read only bound project files, installed dependencies and the child process environment. Git history/status, user-home files, OS services, global tools launched by tests and other undeclared external state are not covered. Such checks must run fresh.
34
+ - `unrelatedDocuments`: literal Markdown/text files proven not to affect any test. No globs. All other project files, including Git-ignored fixtures and `.env`, are fingerprinted.
35
+ - `outputs`: disposable output directories that tests never use as inputs. Supported: `coverage`, `node_modules/.vite`, `node_modules/.vite-temp`. Never exclude test fixtures or source. Output exclusion is an assertion; a test that reads its prior cache is not eligible.
36
+ - `relatedSources`: existing implementation modules with a known static import relationship to tests, including transitive consumers. Do not declare modules with computed imports, runtime file loading, global side effects across test files, custom loader dependencies or dependencies outside Vitest's graph. A conservative scan rejects common dynamic-loading cases; it is **not a general dependency proof**. Omit this list when unsure; unchanged-input reuse still works.
37
+
38
+ Every optimization starts with a successful, complete, nonempty full Vitest run. The receipt binds the project, contract, complete nonexcluded file inventory, installed dependency bytes, file permissions, lockfiles, configuration, Node/platform, pnpm implementation and child environment. Secrets are hashed, never copied into receipts. Raw test output can still contain what your tests print, just as with ordinary verification.
39
+
40
+ An exact match may reuse the previous result and reports its original execution time. Changes limited to declared static sources may run `vitest related --run`; that result is **selected-tests-only**, not a full-suite pass. An empty selection falls back to full verification. Unknown/new/deleted files, changed tests/fixtures/configuration, toolchain or environment changes use a full run. External links or unsupported launchers disable optimization. A failed fresh full attempt invalidates the older success. Reuse is local to the project; nothing is uploaded to a cache service.
41
+
42
+ For a fresh full check, the agent calls:
43
+
44
+ ```json
45
+ { "action": "verify", "script": "test", "verificationMode": "full" }
46
+ ```
47
+
48
+ Use fresh full checks for final acceptance or whenever the dependency assumptions are uncertain. Existing typecheck, lint and build scripts still run in full. No verification result grants reviewer approval, and no automatic model or environment changes are made.
49
+
50
+ ## Automatic cost choice
51
+
52
+ Once a project has the contract above, no speed settings are needed. The first run measures full execution and the additional input-binding work. Later runs choose ordinary full checks without that hashing when it costs at least as much as running the suite. Supported Vitest runs still validate a complete, nonempty passing report.
53
+
54
+ Ordinary runs keep measuring execution time. If the suite grows enough to make optimization worthwhile, Hybrid recalibrates and can reuse unchanged results again. A new targeted selection is measured on its first use; if selection plus binding is slower than full execution, later matching corrections use full checks instead.
55
+
56
+ Results include the reason, execution time and input-binding time. Timing history is a local performance hint, never evidence of correctness or reviewer approval. A missing or invalid history causes calibration; a failed history write does not discard the check result. A forced full check skips optimization hashing and does not create a reusable receipt.
57
+
58
+ The initial calibration and a first targeted trial can be slower. Decisions use previous observations, so machine load and changing workloads can still affect the next run; this is not a promise that every call will be faster. Browser scenario/evidence reuse is a separate feature and is not included here.
59
+
60
+ Reference: [Vitest related CLI and its static import limitations](https://vitest.dev/guide/cli.html#vitest-related).
@@ -0,0 +1,55 @@
1
+ import {readFileSync} from 'node:fs';
2
+ import {hash} from '../../core/snapshot.mjs';
3
+ import {writeJSONAtomic} from '../../core/state-store.mjs';
4
+
5
+ // Performance hints never establish correctness. Choosing ordinary verification
6
+ // needs no content fingerprint; choosing reuse/selection still needs every guard.
7
+ export function costKey(root,script,manifest,contract,executable,environment) {
8
+ return hash(JSON.stringify([root,script,manifest,contract,executable,process.version,process.platform,process.arch,Object.entries(environment).sort(([a],[b])=>a.localeCompare(b))]));
9
+ }
10
+ const milliseconds=value=>Number.isFinite(value)&&value>=0;
11
+ export function loadCosts(file,key) {
12
+ try {
13
+ const {checksum,...value}=JSON.parse(readFileSync(file,'utf8'));
14
+ if(value.schema!==1||value.key!==key||!milliseconds(value.fullMs)||!milliseconds(value.bindingMs)||!value.targets||!value.targetHints||[value.targets,value.targetHints].some(map=>typeof map!=='object'||Array.isArray(map)||Object.entries(map).some(([k,v])=>!(/^[a-f0-9]{64}$/.test(k))||!milliseconds(v)))||checksum!==hash(JSON.stringify(value)))return null;
15
+ return value;
16
+ }catch{return null;}
17
+ }
18
+ export function sourceHints(tree,contract){return Object.fromEntries(contract.relatedSources.map(name=>[name,tree.files[name]?.hash??null]));}
19
+ export function targetHint(tree,contract,sources){return hash(JSON.stringify([sources,Object.entries(tree.files).filter(([name])=>!contract.relatedSources.includes(name)&&!contract.unrelatedDocuments.includes(name)).map(([name,file])=>[name,file.hash])]));}
20
+ export function ordinaryReason(mode,costs,tree,contract) {
21
+ if(mode==='full')return 'Fresh full verification requested; skipping optimization overhead.';
22
+ if(costs&&costs.fullMs<=costs.bindingMs)return 'Measured full execution is no slower than input binding; running ordinary full checks without fingerprinting.';
23
+ if(costs?.baselineSources&&contract){
24
+ const sources=contract.relatedSources.filter(name=>(tree.files[name]?.hash??null)!==costs.baselineSources[name]);
25
+ const elapsed=costs.targetHints[targetHint(tree,contract,sources)];
26
+ if(sources.length&&elapsed!==undefined&&costs.bindingMs+elapsed>=costs.fullMs)return 'Previous measurements for these changed sources favor ordinary full checks; skipping fingerprinting.';
27
+ }
28
+ return null;
29
+ }
30
+ export function selectionKey(inputs,contract,sources) {
31
+ // Preserve measurements across source corrections, but discard them when test
32
+ // selection inputs (tests/config/fixtures/dependencies/toolchain) change.
33
+ return hash(JSON.stringify([inputs.bindingHash,sources,Object.entries(inputs.files).filter(([name])=>!contract.relatedSources.includes(name)),inputs.directories,inputs.links]));
34
+ }
35
+ export function cheaperPlan(plan,inputs,contract,costs) {
36
+ if(!costs)return {scope:'full',reason:'Measuring full execution and input-binding costs before choosing optimization.'};
37
+ if(plan.scope!=='targeted')return plan;
38
+ const targetKey=selectionKey(inputs,contract,plan.sources);
39
+ const targetMs=costs.targets[targetKey];
40
+ if(targetMs!==undefined&&costs.bindingMs+targetMs>=costs.fullMs)return {scope:'full',reason:'Measured targeted execution plus input binding is not cheaper than a full run.',targetKey};
41
+ return {...plan,targetKey,reason:plan.reason+(targetMs===undefined?' Measuring this selection for future cost decisions.':' Previous measurements predict a saving.')};
42
+ }
43
+ export function observeCosts(file,key,previous,record,tree,contract) {
44
+ if(!record.success||record.inputChangedDuringRun||record.modifiedFiles)return;
45
+ const measured=record.timings;
46
+ const fullMs=record.scope==='full'&&record.execution==='executed'?measured.executionMs:previous?.fullMs;
47
+ const bindingMs=record.inputFingerprint?measured.inputBindingMs:previous?.bindingMs;
48
+ if(!milliseconds(fullMs)||!milliseconds(bindingMs))return;
49
+ const targets={...previous?.targets};
50
+ const targetHints={...previous?.targetHints};
51
+ if(record.scope==='targeted'&&record.costTargetKey){targets[record.costTargetKey]=measured.executionMs;targetHints[record.costTargetHint]=measured.executionMs;}
52
+ const baselineSources=record.scope==='full'&&record.execution==='executed'?sourceHints(tree,contract):previous?.baselineSources;
53
+ const value={schema:1,key,fullMs,bindingMs,targets,targetHints,baselineSources,observedAt:record.at};
54
+ writeJSONAtomic(file,{...value,checksum:hash(JSON.stringify(value))});
55
+ }
@@ -8,26 +8,43 @@ import {runtimePath} from '../../core/state-store.mjs';
8
8
  import {contractFor,captureInputs} from './inputs.mjs';
9
9
  import {planVerification} from './planner.mjs';
10
10
  import {loadReceipt,saveReceipt,invalidateReceipt,testResult} from './receipt.mjs';
11
+ import {costKey,loadCosts,ordinaryReason,cheaperPlan,observeCosts,targetHint} from './costs.mjs';
11
12
 
12
- export async function verifyScript(controller,state,script,signal,mode='auto',execute=runProcess) {
13
+ export async function verifyScript(controller,state,script,signal,mode='auto',execute=runProcess,{now=()=>performance.now()}={}) {
13
14
  if(signal?.aborted)throw Error('Cancelled before verification.');
14
15
  if(!['auto','full'].includes(mode))throw Error('verificationMode must be auto or full.');
15
16
  if(!['typecheck','lint','test','build'].includes(script))throw Error('Verification supports only pnpm run typecheck/lint/test/build, with no arbitrary arguments.');
16
17
  const root=controller.directory,config=controller.config;
17
18
  const manifest=JSON.parse(readFileSync(path.join(root,'package.json'),'utf8'));
18
19
  if(typeof manifest.scripts?.[script]!=='string')throw Error(`No ${script} script in package.json.`);
19
- const started=performance.now(),before=snapshot(root).fingerprint;
20
+ const started=now(),beforeTree=snapshot(root),before=beforeTree.fingerprint;
20
21
  const id=randomUUID(),record={id,script,command:manifest.scripts[script],before,at:new Date().toISOString(),success:false,scope:'full',execution:'executed'};
21
22
  const span=controller.trace?.span('verification',{script});
22
23
  const receiptFile=runtimePath(controller.folder,`verification-${script}.json`);
23
24
  const {contract,reason}=contractFor(manifest,script);
25
+ // Report validation is independent of the expensive reusable-input binding.
26
+ let structured=false;
27
+ if(contract)try{structured=/^4\./.test(JSON.parse(readFileSync(path.join(root,'node_modules/vitest/package.json'),'utf8')).version);}catch{}
28
+ let costsFile,costWarning;
29
+ try{costsFile=runtimePath(controller.folder,`verification-${script}-costs.json`);}catch{costWarning='Timing history unavailable; verification continues without saved cost hints.';}
30
+ const environment=workerEnvironment(),key=contract?costKey(root,script,manifest,contract,config.pnpmExecutable,environment):null;
31
+ const costs=key&&costsFile?loadCosts(costsFile,key):null;
32
+ const ordinary=ordinaryReason(mode,costs,beforeTree,contract);
33
+ record.timings={executionMs:0,inputBindingMs:0};
34
+ record.costDecision=ordinary?'ordinary':contract?'calibrating-or-optimized':'unsupported';
35
+ record.costEstimate=costs?{fullExecutionMs:costs.fullMs,inputBindingMs:costs.bindingMs}:null;
36
+ const capture=()=>{const at=now();try{return captureInputs(root,contract,config.pnpmExecutable,workerEnvironment());}finally{record.timings.inputBindingMs+=now()-at;}};
24
37
  let inputs,plan={scope:'full',reason},receipt;
25
- if(contract)try {
26
- inputs=captureInputs(root,contract,config.pnpmExecutable,workerEnvironment());
38
+ if(ordinary)plan={scope:'full',reason:ordinary};
39
+ else if(contract)try {
40
+ inputs=capture();
27
41
  receipt=loadReceipt(receiptFile);
28
42
  plan=planVerification({root,mode,contract,inputs,receipt});
43
+ plan=cheaperPlan(plan,inputs,contract,costs);
29
44
  }catch{plan={scope:'full',reason:'Inputs or toolchain could not be fully bound; running the full script.'};inputs=null;}
30
45
  record.reason=plan.reason;record.scope=plan.scope;
46
+ record.costTargetKey=plan.targetKey??null;
47
+ record.costTargetHint=plan.scope==='targeted'?targetHint(beforeTree,contract,plan.sources):null;
31
48
  if(plan.reuse) {
32
49
  record.success=true;record.execution='reused';record.sourceReceipt=receipt.id;record.executedAt=receipt.at;record.testCount=receipt.testCount;
33
50
  record.output='Reused a prior full-run result under the declared deterministic input contract; no tests ran now.';
@@ -38,25 +55,26 @@ export async function verifyScript(controller,state,script,signal,mode='auto',ex
38
55
  const folder=runtimePath(controller.folder,'verification',id);mkdirSync(folder,{recursive:true,mode:0o700});
39
56
  const reportFile=runtimePath(controller.folder,'verification',id,'vitest.json');
40
57
  let args=plan.scope==='targeted'?['exec','vitest','related','--run',...plan.sources]:['run',script];
41
- if(inputs)args.push('--reporter=json',`--outputFile=${reportFile}`);
58
+ if(structured)args.push('--reporter=json',`--outputFile=${reportFile}`);
42
59
  record.arguments=args;
60
+ const executionStart=now();
43
61
  try {
44
62
  record.output=await execute(config.pnpmExecutable,args,root,'',config.verificationTimeoutMs??0,signal,{streamOnly:true,captureStderr:true});
45
63
  record.success=true;record.exitCode=0;
46
- if(inputs) {
64
+ if(structured) {
47
65
  try {const result=testResult(reportFile,root);record.tests=result.tests;record.testCount=result.count;}
48
66
  catch {record.success=false;record.error='Vitest did not report a complete nonempty passing selection. Run a full check and inspect its report.';}
49
67
  }
50
68
  } catch(error) {
51
69
  if(error.unreapedPid){span?.end({status:'failed'});throw error;}
52
70
  record.error=error.message;record.output=error.output??'';record.exitCode=error.exitCode??null;
53
- }
71
+ } finally {record.timings.executionMs=now()-executionStart;}
54
72
  if(plan.scope==='targeted'&&!record.success){
55
73
  let empty=/No test files found/i.test(record.output);
56
74
  try{empty ||= JSON.parse(readFileSync(reportFile,'utf8')).numTotalTests===0;}catch{}
57
75
  if(empty){
58
76
  span?.end({status:'failed',scope:'targeted'});
59
- const full=await verifyScript(controller,state,script,signal,'full',execute);
77
+ const full=await verifyScript(controller,state,script,signal,'full',execute,{now});
60
78
  full.selectionFallback='Related selection was empty; executed full script instead.';
61
79
  state.checks[state.checks.length-1]=full;controller.save(state);return full;
62
80
  }
@@ -64,15 +82,17 @@ export async function verifyScript(controller,state,script,signal,mode='auto',ex
64
82
  }
65
83
  record.after=snapshot(root).fingerprint;record.modifiedFiles=before!==record.after;
66
84
  let afterInputs;
67
- if(inputs)try{afterInputs=captureInputs(root,contract,config.pnpmExecutable,workerEnvironment());}catch{}
85
+ if(inputs)try{afterInputs=capture();}catch{}
68
86
  const stable=inputs&&afterInputs?.key===inputs.key;
69
87
  record.reusable=!!(record.success&&stable&&record.scope==='full');
70
88
  if(inputs&&!stable){record.reusable=false;record.inputChangedDuringRun=true;if(record.execution==='reused'){record.success=false;record.error='Inputs changed during reuse validation; run verification again.';}}
71
- record.durationMs=Math.round(performance.now()-started);
89
+ record.durationMs=Math.round(now()-started);
72
90
  if(signal?.aborted){record.success=false;record.reusable=false;record.error='Verification cancelled.';}
73
91
  record.inputFingerprint=inputs?.key??null;
74
92
  record.coverage=record.scope==='targeted'?'selected-tests-only':'full-script';
75
93
  if(record.reusable&&record.execution==='executed')saveReceipt(receiptFile,{schema:1,id,scope:'full',success:true,at:record.at,artifact:before,inputs:afterInputs,tests:record.tests,testCount:record.testCount});
94
+ if(key&&costsFile)try{observeCosts(costsFile,key,costs,record,beforeTree,contract);}catch{costWarning='Timing history could not be saved; the verification result is unaffected.';}
95
+ if(costWarning)record.costWarning=costWarning;
76
96
  span?.end({status:record.success?'completed':signal?.aborted?'cancelled':'failed',execution:record.execution,scope:record.scope});
77
97
  state.checks=[...(state.checks??[]),record].slice(-4);controller.save(state);
78
98
  return record;
package/package.json CHANGED
@@ -1,76 +1,76 @@
1
- {
2
- "name": "@rockysoft/opencode-hybrid-workflow",
3
- "version": "0.6.0",
4
- "description": "Portable configurable-reviewer planning, design, implementation checkpoints and usage sidebar for OpenCode 2",
5
- "type": "module",
6
- "license": "MIT",
7
- "author": "RockySoft",
8
- "keywords": [
9
- "opencode",
10
- "opencode-plugin",
11
- "agents",
12
- "code-review",
13
- "workflow"
14
- ],
15
- "publishConfig": {
16
- "access": "public",
17
- "tag": "latest"
18
- },
19
- "exports": {
20
- ".": "./index.js",
21
- "./tui": "./tui.js"
22
- },
23
- "bin": {
24
- "opencode-hybrid": "./cli.mjs"
25
- },
26
- "files": [
27
- "index.js",
28
- "tui.js",
29
- "cli.mjs",
30
- "lib/",
31
- "core/",
32
- "sidebar/",
33
- "assets/",
34
- "README.md",
35
- "THIRD_PARTY_NOTICES.md",
36
- "LICENSE",
37
- "docs/"
38
- ],
39
- "engines": {
40
- "node": ">=22"
41
- },
42
- "scripts": {
43
- "build": "node scripts/build.mjs",
44
- "prepack": "npm run build",
45
- "test": "node --test test/*.test.mjs",
46
- "doctor": "node cli.mjs doctor"
47
- },
48
- "devDependencies": {
49
- "@babel/core": "7.29.7",
50
- "@babel/preset-typescript": "7.27.1",
51
- "babel-preset-solid": "1.9.12"
52
- },
53
- "peerDependencies": {
54
- "@opencode/plugin": ">=2.1.2 <3",
55
- "@opentui/core": ">=0.6.6",
56
- "@opentui/solid": ">=0.6.6",
57
- "solid-js": ">=1.9.0"
58
- },
59
- "peerDependenciesMeta": {
60
- "@opencode/plugin": {
61
- "optional": true
62
- },
63
- "@opentui/core": {
64
- "optional": true
65
- },
66
- "@opentui/solid": {
67
- "optional": true
68
- },
69
- "solid-js": {
70
- "optional": true
71
- }
72
- },
73
- "dependencies": {
74
- "diff": "9.0.0"
75
- }
76
- }
1
+ {
2
+ "name": "@rockysoft/opencode-hybrid-workflow",
3
+ "version": "0.7.0",
4
+ "description": "Portable configurable-reviewer planning, design, implementation checkpoints and usage sidebar for OpenCode 2",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "RockySoft",
8
+ "keywords": [
9
+ "opencode",
10
+ "opencode-plugin",
11
+ "agents",
12
+ "code-review",
13
+ "workflow"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public",
17
+ "tag": "latest"
18
+ },
19
+ "exports": {
20
+ ".": "./index.js",
21
+ "./tui": "./tui.js"
22
+ },
23
+ "bin": {
24
+ "opencode-hybrid": "./cli.mjs"
25
+ },
26
+ "files": [
27
+ "index.js",
28
+ "tui.js",
29
+ "cli.mjs",
30
+ "lib/",
31
+ "core/",
32
+ "sidebar/",
33
+ "assets/",
34
+ "README.md",
35
+ "THIRD_PARTY_NOTICES.md",
36
+ "LICENSE",
37
+ "docs/"
38
+ ],
39
+ "engines": {
40
+ "node": ">=22"
41
+ },
42
+ "scripts": {
43
+ "build": "node scripts/build.mjs",
44
+ "prepack": "npm run build",
45
+ "test": "node --test test/*.test.mjs",
46
+ "doctor": "node cli.mjs doctor"
47
+ },
48
+ "devDependencies": {
49
+ "@babel/core": "7.29.7",
50
+ "@babel/preset-typescript": "7.27.1",
51
+ "babel-preset-solid": "1.9.12"
52
+ },
53
+ "peerDependencies": {
54
+ "@opencode/plugin": ">=2.1.2 <3",
55
+ "@opentui/core": ">=0.6.6",
56
+ "@opentui/solid": ">=0.6.6",
57
+ "solid-js": ">=1.9.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "@opencode/plugin": {
61
+ "optional": true
62
+ },
63
+ "@opentui/core": {
64
+ "optional": true
65
+ },
66
+ "@opentui/solid": {
67
+ "optional": true
68
+ },
69
+ "solid-js": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "dependencies": {
74
+ "diff": "9.0.0"
75
+ }
76
+ }