From fa1c40d010f84a1eef6619052d04856085be0ea7 Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:31:22 +0000 Subject: [PATCH 1/5] fix(eslint): add stricter lint rules and fix violations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add eslint rules: eqeqeq, no-self-compare, no-constructor-return, no-promise-executor-return, array-callback-return, no-constant-binary-expression, no-template-curly-in-string, and ban bare Error() calls. Fix all violations across apps/ and packages/. Notably, this caught real bugs: - order/cells: was comparing info with itself instead of descendant - sdk: was comparing tipEpoch with itself instead of start - core/logic: error message said "minimum" instead of "maximum" - order/entities: typo "respectfully" → "relative" --- apps/bot/src/index.ts | 18 ++++++++++-------- apps/faucet/src/main.ts | 4 +++- apps/sampler/src/index.ts | 6 +++--- apps/tester/src/index.ts | 22 ++++++++++++---------- eslint.config.mjs | 14 ++++++++++++++ packages/core/src/cells.ts | 2 +- packages/core/src/logic.ts | 6 +++--- packages/core/src/owned_owner.ts | 4 ++-- packages/dao/src/cells.ts | 2 +- packages/dao/src/dao.ts | 22 +++++++++++----------- packages/order/src/cells.ts | 18 +++++++++--------- packages/order/src/entities.ts | 28 ++++++++++++++-------------- packages/sdk/src/sdk.ts | 2 +- packages/utils/src/codec.ts | 2 +- packages/utils/src/heap.ts | 4 ++-- packages/utils/src/transaction.ts | 12 ++++++------ packages/utils/src/udt.ts | 2 +- 17 files changed, 94 insertions(+), 74 deletions(-) diff --git a/apps/bot/src/index.ts b/apps/bot/src/index.ts index e7b0ba0..f62be7a 100644 --- a/apps/bot/src/index.ts +++ b/apps/bot/src/index.ts @@ -66,16 +66,16 @@ import type { Cell, Header, Transaction } from "@ckb-lumos/base"; async function main(): Promise { const { CHAIN, RPC_URL, BOT_PRIVATE_KEY, BOT_SLEEP_INTERVAL } = process.env; if (!CHAIN) { - throw Error("Invalid env CHAIN: Empty"); + throw new Error("Invalid env CHAIN: Empty"); } if (!isChain(CHAIN)) { - throw Error("Invalid env CHAIN: " + CHAIN); + throw new Error("Invalid env CHAIN: " + CHAIN); } if (!BOT_PRIVATE_KEY) { - throw Error("Empty env BOT_PRIVATE_KEY"); + throw new Error("Empty env BOT_PRIVATE_KEY"); } if (!BOT_SLEEP_INTERVAL || Number(BOT_SLEEP_INTERVAL) < 1) { - throw Error("Invalid env BOT_SLEEP_INTERVAL"); + throw new Error("Invalid env BOT_SLEEP_INTERVAL"); } const chainConfig = await chainConfigFrom( @@ -89,7 +89,9 @@ async function main(): Promise { const sleepInterval = Number(BOT_SLEEP_INTERVAL) * 1000; for (;;) { - await new Promise((r) => setTimeout(r, 2 * Math.random() * sleepInterval)); + await new Promise((r) => { + setTimeout(r, 2 * Math.random() * sleepInterval); + }); // console.log(); /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -188,7 +190,7 @@ async function main(): Promise { ); const gain = - i == 0 && j == 0 + i === 0 && j === 0 ? 0n : !isPopulated(tx) ? negInf @@ -762,7 +764,7 @@ async function getTxsOutputs( )) { const txHash = tx.hash; if (!txHash) { - throw Error("Empty tx hash"); + throw new Error("Empty tx hash"); } result.set( txHash, @@ -877,7 +879,7 @@ function secp256k1Blake160( tx = prepareSigningEntries(tx, { config }); const message = tx.get("signingEntries").get(0)?.message; if (!message) { - throw Error("Empty message to sign"); + throw new Error("Empty message to sign"); } const sig = key.signRecoverable(message, privateKey); diff --git a/apps/faucet/src/main.ts b/apps/faucet/src/main.ts index 9767c54..b14cfc4 100644 --- a/apps/faucet/src/main.ts +++ b/apps/faucet/src/main.ts @@ -36,7 +36,9 @@ export async function main(): Promise { const capacityManager = CapacityManager.withEmptyData(); for (;;) { - await new Promise((r) => setTimeout(r, 120000)); + await new Promise((r) => { + setTimeout(r, 120000); + }); console.log(); const executionLog: { diff --git a/apps/sampler/src/index.ts b/apps/sampler/src/index.ts index 664cbfd..88db2f2 100644 --- a/apps/sampler/src/index.ts +++ b/apps/sampler/src/index.ts @@ -99,7 +99,7 @@ export async function main(): Promise { // Fetch header for the found block number and log it. const header = await client.getHeaderByNumber(blockNumber); if (!header) { - throw Error("Header not found"); + throw new Error("Header not found"); } logRow(header, note); @@ -156,8 +156,8 @@ function logRow(header: ccc.ClientBlockHeader, note: string): void { * @public */ export function samples(startMs: bigint, endMs: bigint, n: number): Date[] { - if (endMs < startMs) throw Error("endMs must be bigger than startMs"); - if (n < 1) throw Error("n must be a positive number"); + if (endMs < startMs) throw new Error("endMs must be bigger than startMs"); + if (n < 1) throw new Error("n must be a positive number"); // Convert bigints (ms) to Dates for year extraction. const start = new Date(Number(startMs)); diff --git a/apps/tester/src/index.ts b/apps/tester/src/index.ts index 27f7a6f..d923594 100644 --- a/apps/tester/src/index.ts +++ b/apps/tester/src/index.ts @@ -51,16 +51,16 @@ async function main(): Promise { const { CHAIN, RPC_URL, TESTER_PRIVATE_KEY, TESTER_SLEEP_INTERVAL } = process.env; if (!CHAIN) { - throw Error("Invalid env CHAIN: Empty"); + throw new Error("Invalid env CHAIN: Empty"); } if (!isChain(CHAIN)) { - throw Error("Invalid env CHAIN: " + CHAIN); + throw new Error("Invalid env CHAIN: " + CHAIN); } if (!TESTER_PRIVATE_KEY) { - throw Error("Empty env TESTER_PRIVATE_KEY"); + throw new Error("Empty env TESTER_PRIVATE_KEY"); } if (!TESTER_SLEEP_INTERVAL || Number(TESTER_SLEEP_INTERVAL) < 1) { - throw Error("Invalid env TESTER_SLEEP_INTERVAL"); + throw new Error("Invalid env TESTER_SLEEP_INTERVAL"); } const chainConfig = await chainConfigFrom( @@ -74,7 +74,9 @@ async function main(): Promise { const sleepInterval = Number(TESTER_SLEEP_INTERVAL) * 1000; for (;;) { - await new Promise((r) => setTimeout(r, 2 * Math.random() * sleepInterval)); + await new Promise((r) => { + setTimeout(r, 2 * Math.random() * sleepInterval); + }); console.log(); /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -195,15 +197,15 @@ async function main(): Promise { ); if (freeIckbUdt < 0n) { - throw Error("Negative iCKB after the tx"); + throw new Error("Negative iCKB after the tx"); } if (isCkb2Udt) { if (freeCkb < 1000n * CKB) { - throw Error("Not enough CKB, less than 1000 CKB after the tx"); + throw new Error("Not enough CKB, less than 1000 CKB after the tx"); } } else { if (freeCkb < 0n) { - throw Error("Not enough CKB to execute the transaction"); + throw new Error("Not enough CKB to execute the transaction"); } } @@ -387,7 +389,7 @@ async function getTxsOutputs( )) { const txHash = tx.hash; if (!txHash) { - throw Error("Empty tx hash"); + throw new Error("Empty tx hash"); } result.set( txHash, @@ -449,7 +451,7 @@ function secp256k1Blake160( tx = prepareSigningEntries(tx, { config }); const message = tx.get("signingEntries").get(0)?.message; if (!message) { - throw Error("Empty message to sign"); + throw new Error("Empty message to sign"); } const sig = key.signRecoverable(message, privateKey); diff --git a/eslint.config.mjs b/eslint.config.mjs index fb74496..698e3b4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,6 +12,20 @@ export default defineConfig( tseslint.configs.strictTypeChecked, { rules: { + "array-callback-return": "error", + eqeqeq: "error", + "no-self-compare": "error", + "no-constant-binary-expression": "error", + "no-template-curly-in-string": "error", + "no-constructor-return": "error", + "no-promise-executor-return": "error", + "no-restricted-syntax": [ + "error", + { + selector: "CallExpression[callee.name='Error']", + message: "Use `new Error(...)` instead of `Error(...)`.", + }, + ], "@typescript-eslint/explicit-function-return-type": "error", }, languageOptions: { diff --git a/packages/core/src/cells.ts b/packages/core/src/cells.ts index 0088442..4f8956d 100644 --- a/packages/core/src/cells.ts +++ b/packages/core/src/cells.ts @@ -76,7 +76,7 @@ export async function receiptCellFrom( ? options.cell : await options.client.getCell(options.outpoint); if (!cell) { - throw Error("Cell not found"); + throw new Error("Cell not found"); } const txHash = cell.outPoint.txHash; diff --git a/packages/core/src/logic.ts b/packages/core/src/logic.ts index 18565aa..eb8f647 100644 --- a/packages/core/src/logic.ts +++ b/packages/core/src/logic.ts @@ -76,11 +76,11 @@ export class LogicManager implements ScriptDeps { } if (depositAmount < ccc.fixedPointFrom(1082)) { - throw Error("iCKB deposit minimum is 1082 CKB"); + throw new Error("iCKB deposit minimum is 1082 CKB"); } if (depositAmount > ccc.fixedPointFrom(1000082)) { - throw Error("iCKB deposit minimum is 1082 CKB"); + throw new Error("iCKB deposit maximum is 1000082 CKB"); } tx.addCellDeps(this.cellDeps); @@ -104,7 +104,7 @@ export class LogicManager implements ScriptDeps { // Check that there are at most 64 output cells, see: // https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md#gotchas if (tx.outputs.length > 64) { - throw Error("More than 64 output cells in a NervosDAO transaction"); + throw new Error("More than 64 output cells in a NervosDAO transaction"); } } diff --git a/packages/core/src/owned_owner.ts b/packages/core/src/owned_owner.ts index b92daf9..037ccf3 100644 --- a/packages/core/src/owned_owner.ts +++ b/packages/core/src/owned_owner.ts @@ -102,7 +102,7 @@ export class OwnedOwnerManager implements ScriptDeps { // Check that there are at most 64 output cells, see: // https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md#gotchas if (tx.outputs.length > 64) { - throw Error("More than 64 output cells in a NervosDAO transaction"); + throw new Error("More than 64 output cells in a NervosDAO transaction"); } } @@ -144,7 +144,7 @@ export class OwnedOwnerManager implements ScriptDeps { // Check that there are at most 64 output cells, see: // https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md#gotchas if (tx.outputs.length > 64) { - throw Error("More than 64 output cells in a NervosDAO transaction"); + throw new Error("More than 64 output cells in a NervosDAO transaction"); } } diff --git a/packages/dao/src/cells.ts b/packages/dao/src/cells.ts index 457a889..c75c814 100644 --- a/packages/dao/src/cells.ts +++ b/packages/dao/src/cells.ts @@ -78,7 +78,7 @@ export async function daoCellFrom( ? options.cell : await options.client.getCell(options.outpoint); if (!cell) { - throw Error("Cell not found"); + throw new Error("Cell not found"); } const tip = options.tip; diff --git a/packages/dao/src/dao.ts b/packages/dao/src/dao.ts index 82c6f87..fc2e063 100644 --- a/packages/dao/src/dao.ts +++ b/packages/dao/src/dao.ts @@ -98,7 +98,7 @@ export class DaoManager implements ScriptDeps { // Check that there are at most 64 output cells, see: // https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md#gotchas if (tx.outputs.length > 64) { - throw Error("More than 64 output cells in a NervosDAO transaction"); + throw new Error("More than 64 output cells in a NervosDAO transaction"); } } @@ -135,22 +135,22 @@ export class DaoManager implements ScriptDeps { } if ( - tx.inputs.length != tx.outputs.length || - tx.outputs.length != tx.outputsData.length + tx.inputs.length !== tx.outputs.length || + tx.outputs.length !== tx.outputsData.length ) { - throw Error("Transaction have different inputs and outputs lengths"); + throw new Error("Transaction have different inputs and outputs lengths"); } for (const deposit of deposits) { const { cell, isDeposit, headers } = deposit; if (!isDeposit) { - throw Error("Not a deposit"); + throw new Error("Not a deposit"); } if ( sameSizeOnly && - cell.cellOutput.lock.args.length != lock.args.length + cell.cellOutput.lock.args.length !== lock.args.length ) { - throw Error( + throw new Error( "Withdrawal request lock args has different size from deposit", ); } @@ -172,7 +172,7 @@ export class DaoManager implements ScriptDeps { // Check that there are at most 64 output cells, see: // https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md#gotchas if (tx.outputs.length > 64) { - throw Error("More than 64 output cells in a NervosDAO transaction"); + throw new Error("More than 64 output cells in a NervosDAO transaction"); } } @@ -211,7 +211,7 @@ export class DaoManager implements ScriptDeps { maturity, } = withdrawalRequest; if (isDeposit) { - throw Error("Not a withdrawal request"); + throw new Error("Not a withdrawal request"); } tx.addHeaders(headers); const depositHeader = headers[0]; @@ -234,7 +234,7 @@ export class DaoManager implements ScriptDeps { const witness = tx.getWitnessArgsAt(inputIndex) ?? ccc.WitnessArgs.from({}); if (witness.inputType) { - throw Error("Witnesses of withdrawal request already in use"); + throw new Error("Witnesses of withdrawal request already in use"); } witness.inputType = ccc.hexFrom(ccc.numLeToBytes(headerIndex, 8)); tx.setWitnessArgsAt(inputIndex, witness); @@ -243,7 +243,7 @@ export class DaoManager implements ScriptDeps { // Check that there are at most 64 output cells, see: // https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md#gotchas if (tx.outputs.length > 64) { - throw Error("More than 64 output cells in a NervosDAO transaction"); + throw new Error("More than 64 output cells in a NervosDAO transaction"); } } diff --git a/packages/order/src/cells.ts b/packages/order/src/cells.ts index 8d3aa15..9b0dccc 100644 --- a/packages/order/src/cells.ts +++ b/packages/order/src/cells.ts @@ -187,28 +187,28 @@ export class OrderCell implements ValueComponents { } if (!this.cell.cellOutput.lock.eq(descendant.cell.cellOutput.lock)) { - throw Error("Order script different"); + throw new Error("Order script different"); } const udt = this.cell.cellOutput.type; if (!udt || !descendant.cell.cellOutput.type?.eq(udt)) { - throw Error("UDT type is different"); + throw new Error("UDT type is different"); } if (!descendant.getMaster().eq(this.getMaster())) { - throw Error("Master is different"); + throw new Error("Master is different"); } - if (!this.data.info.eq(this.data.info)) { - throw Error("Info is different"); + if (!this.data.info.eq(descendant.data.info)) { + throw new Error("Info is different"); } if (this.absTotal > descendant.absTotal) { - throw Error("Total value is lower than the original one"); + throw new Error("Total value is lower than the original one"); } if (this.absProgress > descendant.absProgress) { - throw Error("Progress is lower than the original one"); + throw new Error("Progress is lower than the original one"); } } @@ -279,11 +279,11 @@ export class MasterCell implements ValueComponents { */ validate(order: OrderCell): void { if (!this.cell.cellOutput.type?.eq(order.cell.cellOutput.lock)) { - throw Error("Order script different"); + throw new Error("Order script different"); } if (!order.getMaster().eq(this.cell.outPoint)) { - throw Error("Master is different"); + throw new Error("Master is different"); } } diff --git a/packages/order/src/entities.ts b/packages/order/src/entities.ts index 5111d14..973a17f 100644 --- a/packages/order/src/entities.ts +++ b/packages/order/src/entities.ts @@ -51,7 +51,7 @@ export class Ratio extends ccc.Entity.Base() { */ validate(): void { if (!this.isEmpty() && !this.isPopulated()) { - throw Error("Ratio invalid: not empty, not populated"); + throw new Error("Ratio invalid: not empty, not populated"); } } @@ -107,11 +107,11 @@ export class Ratio extends ccc.Entity.Base() { * and zero if they are equal. */ compare(other: Ratio): number { - if (this.udtScale == other.udtScale) { + if (this.udtScale === other.udtScale) { return Number(this.ckbScale - other.ckbScale); } - if (this.ckbScale == other.ckbScale) { + if (this.ckbScale === other.ckbScale) { return Number(other.udtScale - this.udtScale); } @@ -144,7 +144,7 @@ export class Ratio extends ccc.Entity.Base() { */ applyFee(isCkb2Udt: boolean, fee: ccc.Num, feeBase: ccc.Num): Ratio { if (fee >= feeBase) { - throw Error("Fee too big respectfully to feeBase"); + throw new Error("Fee too big relative to feeBase"); } if (fee === 0n) { @@ -210,7 +210,7 @@ export class Ratio extends ccc.Entity.Base() { mustCeil: boolean, ): ccc.FixedPoint { if (!this.isPopulated()) { - throw Error("Invalid midpoint ExchangeRatio"); + throw new Error("Invalid midpoint ExchangeRatio"); } if (amount === 0n) { @@ -334,14 +334,14 @@ export class Info extends ccc.Entity.Base() { */ validate(): void { if (this.ckbMinMatchLog < 0 || this.ckbMinMatchLog > 64) { - throw Error("ckbMinMatchLog invalid"); + throw new Error("ckbMinMatchLog invalid"); } if (this.ckbToUdt.isEmpty()) { if (this.udtToCkb.isPopulated()) { return; } else { - throw Error("ckbToUdt is Empty, but udtToCkb is not Populated"); + throw new Error("ckbToUdt is Empty, but udtToCkb is not Populated"); } } @@ -349,12 +349,12 @@ export class Info extends ccc.Entity.Base() { if (this.ckbToUdt.isPopulated()) { return; } else { - throw Error("udtToCkb is Empty, but ckbToUdt is not Populated"); + throw new Error("udtToCkb is Empty, but ckbToUdt is not Populated"); } } if (!this.ckbToUdt.isPopulated() || !this.udtToCkb.isPopulated()) { - throw Error("One ratio is invalid, so not Empty and not Populated"); + throw new Error("One ratio is invalid, so not Empty and not Populated"); } // Check that if we convert from ckb to udt and then back from udt to ckb, it doesn't lose value. @@ -362,7 +362,7 @@ export class Info extends ccc.Entity.Base() { this.ckbToUdt.ckbScale * this.udtToCkb.udtScale < this.ckbToUdt.udtScale * this.udtToCkb.ckbScale ) { - throw Error("udtToCkb and ckbToUdt allow order value to be extracted"); + throw new Error("udtToCkb and ckbToUdt allow order value to be extracted"); } } @@ -542,8 +542,8 @@ export class Relative extends ccc.Entity.Base() { * @throws {Error} If the padding is not of length 32 or contains non-zero values. */ validate(): void { - if (this.padding.length != 32 || this.padding.some((x) => x !== 0)) { - throw Error("Relative master invalid, non standard padding"); + if (this.padding.length !== 32 || this.padding.some((x) => x !== 0)) { + throw new Error("Relative master invalid, non standard padding"); } } @@ -616,7 +616,7 @@ function masterValidate(master: Master): void { value.validate(); } else { if (!/^0x[0-9a-f]{64}$/i.test(value.txHash) || value.index < 0) { - throw Error("OutPoint invalid"); + throw new Error("OutPoint invalid"); } } } @@ -707,7 +707,7 @@ export class OrderData extends ccc.Entity.Base() { */ validate(): void { if (this.udtValue < 0) { - throw Error("udtValue invalid, negative"); + throw new Error("udtValue invalid, negative"); } masterValidate(this.master); this.info.validate(); diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index e0c7642..b512889 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -440,7 +440,7 @@ export class IckbSdk { ckbMaturing.push({ ckbValue: BigInt(binAmount) * depositSize, maturity: - tipEpoch.compare(tipEpoch) < 0 + start.compare(tipEpoch) < 0 ? // If the bin has already started, assume worst-case timing. end.add(oneCycle).toUnix(tip) : // Otherwise, use the bin end as the maturity. diff --git a/packages/utils/src/codec.ts b/packages/utils/src/codec.ts index 1418fd4..80d608a 100644 --- a/packages/utils/src/codec.ts +++ b/packages/utils/src/codec.ts @@ -8,7 +8,7 @@ export const CheckedInt32LE = ccc.Codec.from({ encode: (numLike) => { const num = Number(numLike); if (num < -2147483648 || num > 2147483647) { - throw Error("NumLike out of int32 bounds"); + throw new Error("NumLike out of int32 bounds"); } const encoded = new Uint8Array(4); new DataView(encoded.buffer).setInt32(0, num, true); diff --git a/packages/utils/src/heap.ts b/packages/utils/src/heap.ts index 20995cc..f4f44b6 100644 --- a/packages/utils/src/heap.ts +++ b/packages/utils/src/heap.ts @@ -95,7 +95,7 @@ export class MinHeap { } const n = this.len() - 1; - if (n != i) { + if (n !== i) { this.swap(i, n); if (!this.down(i, n)) { this.up(i); @@ -126,7 +126,7 @@ export class MinHeap { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { const i = (j - 1) >> 1; // parent - if (i == j || !this.less(j, i)) { + if (i === j || !this.less(j, i)) { break; } this.swap(i, j); diff --git a/packages/utils/src/transaction.ts b/packages/utils/src/transaction.ts index 6fbe0cf..3f67db5 100644 --- a/packages/utils/src/transaction.ts +++ b/packages/utils/src/transaction.ts @@ -91,7 +91,7 @@ export class SmartTransaction extends ccc.Transaction { this.inputs.some((c) => c.cellOutput?.type?.eq(dao)) || this.outputs.some((c) => c.type?.eq(dao)); if (isDaoTx && this.outputs.length > 64) { - throw Error("More than 64 output cells in a NervosDAO transaction"); + throw new Error("More than 64 output cells in a NervosDAO transaction"); } return [inAdded, addedChange]; @@ -166,7 +166,7 @@ export class SmartTransaction extends ccc.Transaction { // Input is not well defined. if (!cellOutput || !outputData) { - throw Error("Unable to complete input"); + throw new Error("Unable to complete input"); } const cell = ccc.Cell.from({ outPoint, @@ -297,11 +297,11 @@ export class SmartTransaction extends ccc.Transaction { const h = this.headers.get(key); if (!h) { this.headers.set(key, header); - } else if (hash == h.hash) { + } else if (hash === h.hash) { // Keep old header. header = h; } else { - throw Error("Found two hashes for the same header"); + throw new Error("Found two hashes for the same header"); } } @@ -339,13 +339,13 @@ export class SmartTransaction extends ccc.Transaction { txHash: headerKey.type === "txHash" ? headerKey.value : undefined, }); if (headerDepsLength !== this.headerDeps.length) { - throw Error("Header was not present in HeaderDeps"); + throw new Error("Header was not present in HeaderDeps"); } } else { // Double check that header is present in headerDeps. const { hash } = header; if (!this.headerDeps.some((h) => h === hash)) { - throw Error("Header not found in HeaderDeps"); + throw new Error("Header not found in HeaderDeps"); } } diff --git a/packages/utils/src/udt.ts b/packages/utils/src/udt.ts index a4b3fc0..8dcc65b 100644 --- a/packages/utils/src/udt.ts +++ b/packages/utils/src/udt.ts @@ -153,7 +153,7 @@ export class UdtManager implements UdtHandler { // Input is not well defined if (!cellOutput || !outputData) { - throw Error("Unable to complete input"); + throw new Error("Unable to complete input"); } // Input is not an UDT From c2876befcd6010f515c4e9fa4ce20d32d6232beb Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:31:44 +0000 Subject: [PATCH 2/5] feat: replace pr/review scripts with AI Coworker configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add AGENTS.md as tool-agnostic agent config (CLAUDE.md is now a gitignored symlink to it). Add pnpm coworker/coworker:ask scripts, ccc:status, ccc:clean, ccc:reset commands. Remove scripts/pr.sh and scripts/review.sh — PR and review workflows are now handled inline. Also: bump typescript-eslint to 8.56, pnpm to 10.30.0, reorder check pipeline to run lint before build. --- .gitignore | 3 + AGENTS.md | 37 +++++++ package.json | 19 ++-- pnpm-lock.yaml | 262 ++++++++++++++++++++++++---------------------- scripts/pr.sh | 74 ------------- scripts/review.sh | 77 -------------- 6 files changed, 184 insertions(+), 288 deletions(-) create mode 100644 AGENTS.md delete mode 100755 scripts/pr.sh delete mode 100755 scripts/review.sh diff --git a/.gitignore b/.gitignore index 022d076..9179f04 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ # Reference repos contracts/ whitepaper/ + +# AI tool config symlink +CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d9224c9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,37 @@ +# AI Coworker Configuration + +This file is the tool-agnostic agent config: + +- Refer to yourself as "AI Coworker" in docs and comments, not by product or company name +- Never add AI tool attribution or branding to PR descriptions, commit messages, or code comments +- Do not install or use `gh` CLI +- **Routine Pre-PR Validation**: `pnpm check:full`, it wipes derived state and regenerates from scratch. If `ccc-dev/ccc/` has pending work, the wipe is skipped to prevent data loss — re-record or push CCC changes first for a clean validation +- **Open a PR**: Push the branch, then construct and present a GitHub compare URL + (`quick_pull=1`) to the user. Base branch is `master`. Prefill "title" (concise, under 70 chars) and "body" (markdown with ## Why and ## Changes sections) +- **Fetch PR review comments**: Use the GitHub REST API via curl. Fetch all three comment types (issue comments, reviews, and inline comments). Reviewers reply asynchronously — poll every minute until comments arrive +- **Copy to clipboard**: + ``` + head -c -1 <<'EOF' | wl-copy + content goes here + EOF + ``` + +# CCC Local Development (ccc-dev/) + +The `ccc-dev/` system uses a record/replay mechanism for deterministic builds of a local CCC fork: + +- `ccc-dev/pins/` is **committed** to git (base SHAs, merge refs, conflict resolutions), regenerated by `pnpm ccc:record` +- `ccc-dev/ccc/` is **not in git** — it is rebuilt from pins on `pnpm install` +- The developer may have **pending work** in `ccc-dev/ccc/`. Run `pnpm ccc:status` (exit 0 = safe to wipe, exit 1 = has custom work) before any operation that would destroy it. `pnpm ccc:record`, `pnpm ccc:clean`, and `pnpm ccc:reset` already guard against this automatically +- `.pnpmfile.cjs` silently rewrites all `@ckb-ccc/*` dependencies to `workspace:*` when `ccc-dev/ccc/` exists. Local CCC packages override published ones without any visible change in package.json files +- `pnpm install` has a side effect: if `ccc-dev/pins/REFS` exists but `ccc-dev/ccc/` does not, it automatically runs `ccc-dev/replay.sh` to rebuild CCC from pins. This is intentional +- `ccc-dev/patch.sh` rewrites CCC package exports to point at `.ts` source instead of `.d.ts`, then creates a deterministic git commit (fixed author/date) so record and replay produce the same `pins/HEAD` hash. This is why imports from `@ckb-ccc/*` resolve to TypeScript source files inside `node_modules` — it is not a bug +- `ccc-dev/tsc.mjs` is a custom `tsc` wrapper that filters out diagnostics originating from `ccc-dev/ccc/`. CCC source does not satisfy this repo's strict tsconfig (`verbatimModuleSyntax`, `noUncheckedIndexedAccess`, `noImplicitOverride`), so the wrapper suppresses those errors while still reporting errors in stack source + +# Reference Repos + +`contracts/` and `whitepaper/` (cloned via `pnpm reference`) are made **read-only** with `chmod -R a-w`. To refresh, delete the directory and re-run `pnpm reference` + +# Versioning + +All packages use version `1001.0.0` (Epoch Semantic Versioning), managed by changesets (`pnpm changeset`) diff --git a/package.json b/package.json index c5df51e..ff331bc 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,16 @@ "private": true, "scripts": { "ccc:record": "bash ccc-dev/record.sh releases/next releases/udt", + "ccc:status": "bash ccc-dev/status.sh", "ccc:push": "bash ccc-dev/push.sh", - "ccc:nuke": "rm -rf ccc-dev/ccc ccc-dev/pins", + "ccc:clean": "bash ccc-dev/status.sh && rm -rf ccc-dev/ccc", + "ccc:reset": "pnpm ccc:clean && rm -rf ccc-dev/pins", "build": "pnpm -r --filter !./apps/** --filter !./ccc-dev/ccc/** build", "build:all": "pnpm -r --filter !./ccc-dev/ccc/** build", - "check": "pnpm clean:deep && pnpm install && pnpm build:all && pnpm lint && pnpm test:ci", + "check": "pnpm clean:deep && pnpm install && pnpm lint && pnpm build:all && pnpm test:ci", "check:fresh": "rm pnpm-lock.yaml && pnpm run check", "check:ci": "CI=true pnpm run check", - "check:full": "pnpm check:fresh && pnpm check:ci", + "check:full": "pnpm ccc:clean; pnpm check:fresh && pnpm check:ci", "test": "vitest", "test:ci": "vitest run", "test:cov": "vitest run --coverage", @@ -21,9 +23,8 @@ "version": "pnpm changeset version", "publish": "pnpm publish -r", "docs": "typedoc", - "pr": "bash scripts/pr.sh", - "review": "bash scripts/review.sh", - "claude": "claude --dangerously-skip-permissions", + "coworker": "ln -sf AGENTS.md CLAUDE.md && claude --model opus --dangerously-skip-permissions", + "coworker:ask": "env -u CLAUDECODE claude --print --model sonnet --no-session-persistence", "reference": "pnpm reference:contracts; pnpm reference:whitepaper", "reference:contracts": "[ -d contracts ] || (git clone https://github.com/ickb/contracts.git && chmod -R a-w contracts)", "reference:whitepaper": "[ -d whitepaper ] || (git clone https://github.com/ickb/whitepaper.git && chmod -R a-w whitepaper)" @@ -42,8 +43,8 @@ "prettier-plugin-organize-imports": "^4.3.0", "typedoc": "0.28.7", "typescript": "^5.9.3", - "typescript-eslint": "^8.55.0", + "typescript-eslint": "^8.56.0", "vitest": "^3.2.4" }, - "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc" -} \ No newline at end of file + "packageManager": "pnpm@10.30.0+sha512.2b5753de015d480eeb88f5b5b61e0051f05b4301808a82ec8b840c9d2adf7748eb352c83f5c1593ca703ff1017295bc3fdd3119abb9686efc96b9fcb18200937" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a725b9a..621e21a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,7 +18,7 @@ importers: devDependencies: '@anthropic-ai/claude-code': specifier: latest - version: 2.1.42 + version: 2.1.45 '@changesets/changelog-github': specifier: ^0.5.2 version: 0.5.2 @@ -47,8 +47,8 @@ importers: specifier: ^5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.55.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.56.0 + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) @@ -204,7 +204,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.46.1 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^6.4.0 version: 6.4.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) @@ -306,13 +306,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/ckb-ccc: dependencies: @@ -343,13 +343,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/connector: dependencies: @@ -380,13 +380,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/connector-react: dependencies: @@ -423,13 +423,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/core: dependencies: @@ -490,13 +490,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) @@ -542,7 +542,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) @@ -576,13 +576,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/joy-id: dependencies: @@ -619,13 +619,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/lumos-patches: dependencies: @@ -674,13 +674,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/nip07: dependencies: @@ -711,13 +711,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/okx: dependencies: @@ -754,13 +754,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/rei: dependencies: @@ -791,13 +791,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/shell: dependencies: @@ -843,13 +843,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/spore: dependencies: @@ -889,13 +889,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) @@ -932,13 +932,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/type-id: dependencies: @@ -975,7 +975,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) @@ -1015,13 +1015,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/uni-sat: dependencies: @@ -1052,13 +1052,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/utxo-global: dependencies: @@ -1089,13 +1089,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) ccc-dev/ccc/packages/xverse: dependencies: @@ -1129,13 +1129,13 @@ importers: version: 4.3.0(prettier@3.8.1)(typescript@5.9.3) rimraf: specifier: ^6.0.1 - version: 6.1.2 + version: 6.1.3 typescript: specifier: ^5.9.2 version: 5.9.3 typescript-eslint: specifier: ^8.41.0 - version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) packages/core: dependencies: @@ -1200,8 +1200,8 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@anthropic-ai/claude-code@2.1.42': - resolution: {integrity: sha512-6zSAsdkmY3KSiO3EtPLHvXy9NbLYFC9Zk8s5dEBUwFR/vBpCvTlCv81JAIVgX53nuuqFv/ktBYEdAVDVbJJsqA==} + '@anthropic-ai/claude-code@2.1.45': + resolution: {integrity: sha512-L2F7ieThA0MFkzyvA7qhU8s+7pkWTXSQNj6XircdNP+r/L8uhX7JY39NtWpmMvcyxHse+9qUdylGN/rOQgBpXQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -2448,63 +2448,63 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript-eslint/eslint-plugin@8.55.0': - resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} + '@typescript-eslint/eslint-plugin@8.56.0': + resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.55.0 - eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/parser': ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.55.0': - resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} + '@typescript-eslint/parser@8.56.0': + resolution: {integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.55.0': - resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} + '@typescript-eslint/project-service@8.56.0': + resolution: {integrity: sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.55.0': - resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} + '@typescript-eslint/scope-manager@8.56.0': + resolution: {integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.55.0': - resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} + '@typescript-eslint/tsconfig-utils@8.56.0': + resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.55.0': - resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} + '@typescript-eslint/type-utils@8.56.0': + resolution: {integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.55.0': - resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} + '@typescript-eslint/types@8.56.0': + resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.55.0': - resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} + '@typescript-eslint/typescript-estree@8.56.0': + resolution: {integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.55.0': - resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} + '@typescript-eslint/utils@8.56.0': + resolution: {integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.55.0': - resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} + '@typescript-eslint/visitor-keys@8.56.0': + resolution: {integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-basic-ssl@1.2.0': @@ -2637,8 +2637,8 @@ packages: axios@1.13.5: resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} - b4a@1.7.4: - resolution: {integrity: sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==} + b4a@1.7.5: + resolution: {integrity: sha512-iEsKNwDh1wiWTps1/hdkNdmBgDlDVZP5U57ZVOlt+dNFqpc/lpPouCIxZw+DYBgc4P9NDfIZMPNR4CHNhzwLIA==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: @@ -2976,6 +2976,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.0: + resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3152,8 +3156,8 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.3: - resolution: {integrity: sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==} + glob@13.0.5: + resolution: {integrity: sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw==} engines: {node: 20 || >=22} glob@7.2.3: @@ -3534,8 +3538,8 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - minimatch@10.2.0: - resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==} + minimatch@10.2.1: + resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==} engines: {node: 20 || >=22} minimatch@3.1.2: @@ -3845,8 +3849,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@6.1.2: - resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==} + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} engines: {node: 20 || >=22} hasBin: true @@ -4097,11 +4101,11 @@ packages: peerDependencies: typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x - typescript-eslint@8.55.0: - resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} + typescript-eslint@8.56.0: + resolution: {integrity: sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' typescript@5.9.3: @@ -4343,7 +4347,7 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@anthropic-ai/claude-code@2.1.42': + '@anthropic-ai/claude-code@2.1.45': optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 '@img/sharp-darwin-x64': 0.33.5 @@ -5651,14 +5655,14 @@ snapshots: dependencies: '@types/node': 24.10.13 - '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/parser': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.0 + '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -5667,41 +5671,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/scope-manager': 8.56.0 + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.56.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) - '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3) + '@typescript-eslint/types': 8.56.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.55.0': + '@typescript-eslint/scope-manager@8.56.0': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/visitor-keys': 8.56.0 - '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) @@ -5709,14 +5713,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.55.0': {} + '@typescript-eslint/types@8.56.0': {} - '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.56.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/project-service': 8.56.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3) + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/visitor-keys': 8.56.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.4 @@ -5726,21 +5730,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.0 + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.55.0': + '@typescript-eslint/visitor-keys@8.56.0': dependencies: - '@typescript-eslint/types': 8.55.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.56.0 + eslint-visitor-keys: 5.0.0 '@vitejs/plugin-basic-ssl@1.2.0(vite@6.4.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': dependencies: @@ -5889,7 +5893,7 @@ snapshots: transitivePeerDependencies: - debug - b4a@1.7.4: {} + b4a@1.7.5: {} babel-plugin-react-compiler@1.0.0: dependencies: @@ -5919,7 +5923,7 @@ snapshots: blake2b-wasm@2.4.0: dependencies: - b4a: 1.7.4 + b4a: 1.7.5 nanoassert: 2.0.0 transitivePeerDependencies: - react-native-b4a @@ -6224,6 +6228,8 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.0: {} + eslint@9.39.2(jiti@2.6.1): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) @@ -6436,9 +6442,9 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@13.0.3: + glob@13.0.5: dependencies: - minimatch: 10.2.0 + minimatch: 10.2.1 minipass: 7.1.2 path-scurry: 2.0.1 @@ -6771,7 +6777,7 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} - minimatch@10.2.0: + minimatch@10.2.1: dependencies: brace-expansion: 5.0.2 @@ -6975,9 +6981,9 @@ snapshots: reusify@1.1.0: {} - rimraf@6.1.2: + rimraf@6.1.3: dependencies: - glob: 13.0.3 + glob: 13.0.5 package-json-from-dist: 1.0.1 rolldown-plugin-dts@0.20.0(rolldown@1.0.0-beta.58)(typescript@5.9.3): @@ -7247,12 +7253,12 @@ snapshots: typescript: 5.9.3 yaml: 2.8.2 - typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: diff --git a/scripts/pr.sh b/scripts/pr.sh deleted file mode 100755 index 0b94ee4..0000000 --- a/scripts/pr.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Usage: pr.sh [--base ] [--title ] [--body <body>] -# Opens a GitHub PR creation page. Uses Claude to generate title/body if not provided. - -command -v jq &>/dev/null || { echo "ERROR: 'jq' is required" >&2; exit 1; } - -branch=$(git branch --show-current) -repo=$(git remote get-url origin | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') -base=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||') -base=${base:-master} - -title="" body="" -while [[ $# -gt 0 ]]; do - case "$1" in - --base) base="$2"; shift 2 ;; - --title) title="$2"; shift 2 ;; - --body) body="$2"; shift 2 ;; - *) shift ;; - esac -done - -if [[ "$branch" == "$base" ]]; then - echo "Already on $base, nothing to open a PR for." >&2 - exit 1 -fi - -if ! git rev-parse --verify "origin/$branch" &>/dev/null; then - echo "Warning: $branch has not been pushed to origin. Push first or the PR link will 404." >&2 -fi - -# Generate title/body with Claude, fall back to branch name + commit log -if [[ -z "$title" || -z "$body" ]]; then - log=$(git log "$base"..HEAD --no-merges --format="- %s") - - if command -v claude &>/dev/null; then - # Prevent nested Claude Code detection - unset CLAUDECODE 2>/dev/null || true - - if output=$(claude --print --model sonnet --no-session-persistence \ - -p 'Output ONLY a JSON object with "title" (concise, under 70 chars) and "body" (markdown with ## Why and ## Changes sections). Be brief.' <<EOF -Branch: $branch - -Commits: -$log - -Diff stat: -$(git diff "$base"...HEAD --stat) -EOF - ); then - # Parse null-delimited title and body from JSON - { IFS= read -rd '' parsed_title; IFS= read -rd '' parsed_body; } \ - < <(sed '/^```\(json\)\?$/d' <<< "$output" | jq -rj '[.title // "", "\u0000", .body // "", "\u0000"] | add') || true - [[ -z "$title" && -n "${parsed_title:-}" ]] && title="$parsed_title" - [[ -z "$body" && -n "${parsed_body:-}" ]] && body="$parsed_body" - fi - fi - - # Fallback - [[ -z "$title" ]] && title="$branch" - [[ -z "$body" ]] && body="## Changes -$log" -fi - -# Open PR creation page -urlencode() { printf '%s' "$1" | jq -sRr @uri; } -url="$repo/compare/$base...$branch?quick_pull=1&title=$(urlencode "$title")&body=$(urlencode "$body")" - -if [[ -n "${BROWSER:-}" ]]; then "$BROWSER" "$url" -elif command -v xdg-open &>/dev/null; then xdg-open "$url" -elif command -v open &>/dev/null; then open "$url" -else echo "$url" -fi diff --git a/scripts/review.sh b/scripts/review.sh deleted file mode 100755 index cb57d03..0000000 --- a/scripts/review.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Usage: review.sh [--pr <number>] -# Fetches and displays PR review comments from GitHub. -# Auto-detects the PR for the current branch, or pass --pr <number>. - -for cmd in jq curl; do - command -v "$cmd" &>/dev/null || { echo "ERROR: '$cmd' is required" >&2; exit 1; } -done - -# Extract owner/repo from remote -remote=$(git remote get-url origin | sed 's/\.git$//' | sed 's|git@github.com:|https://github.com/|') -owner_repo=$(echo "$remote" | sed 's|https://github.com/||') - -pr_number="" -while [[ $# -gt 0 ]]; do - case "$1" in - --pr) pr_number="$2"; shift 2 ;; - *) shift ;; - esac -done - -api="https://api.github.com/repos/$owner_repo" -auth_header=() -if [[ -n "${GITHUB_TOKEN:-}" ]]; then - auth_header=(-H "Authorization: token $GITHUB_TOKEN") -fi - -# curl with retry on transient failures (5xx, rate-limit, network errors) -fetch() { - curl -sf --retry 3 --retry-delay 2 --retry-all-errors \ - "${auth_header[@]+"${auth_header[@]}"}" "$1" -} - -# Auto-detect PR number from current branch -if [[ -z "$pr_number" ]]; then - branch=$(git branch --show-current) - pr_number=$(fetch "$api/pulls?head=$(echo "$owner_repo" | cut -d/ -f1):$branch&state=open" \ - | jq -r '.[0].number // empty') - - if [[ -z "$pr_number" ]]; then - echo "No open PR found for branch '$branch'." >&2 - exit 1 - fi -fi - -echo "=== PR #$pr_number — $(fetch "$api/pulls/$pr_number" | jq -r '.title') ===" -echo - -# General conversation comments (issue-level) -conversation=$(fetch "$api/issues/$pr_number/comments") -count=$(echo "$conversation" | jq length) -if [[ "$count" -gt 0 ]]; then - echo "--- Conversation ---" - echo "$conversation" | jq -r '.[] | "[\(.user.login)] \(.created_at)\n\(.body)\n"' -fi - -# Review-level comments (approve/request changes/comment summaries) -reviews=$(fetch "$api/pulls/$pr_number/reviews") -review_count=$(echo "$reviews" | jq length) -if [[ "$review_count" -gt 0 ]]; then - echo "--- Reviews ---" - echo "$reviews" | jq -r '.[] | select(.body != "" and .body != null) | "[\(.user.login)] \(.state)\n\(.body)\n"' -fi - -# Inline code comments -inline=$(fetch "$api/pulls/$pr_number/comments") -inline_count=$(echo "$inline" | jq length) -if [[ "$inline_count" -gt 0 ]]; then - echo "--- Inline Comments ---" - echo "$inline" | jq -r '.[] | "[\(.user.login)] \(.path):\(.line // .original_line // "?")\n\(.body)\n"' -fi - -if [[ "$count" -eq 0 && "$review_count" -eq 0 && "$inline_count" -eq 0 ]]; then - echo "No comments yet." -fi From 6ca3271aea6a584fad6732c74162a5b4c7aff695 Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:32:06 +0000 Subject: [PATCH 3/5] feat(ccc-dev): add status check and safety guards Add status.sh to detect pending work in ccc-dev/ccc/ (diverged HEAD, uncommitted changes, untracked files). Record.sh now aborts if pending work exists instead of silently overwriting. Patch.sh creates a deterministic git commit (fixed author/date derived from merge count) so record and replay produce the same pins/HEAD hash. Replay.sh gets improved error messaging on divergence. --- ccc-dev/patch.sh | 13 +++++++++++-- ccc-dev/pins/HEAD | 2 +- ccc-dev/record.sh | 20 ++++++++++---------- ccc-dev/replay.sh | 7 +++++-- ccc-dev/status.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 ccc-dev/status.sh diff --git a/ccc-dev/patch.sh b/ccc-dev/patch.sh index 87e7fac..d259929 100755 --- a/ccc-dev/patch.sh +++ b/ccc-dev/patch.sh @@ -2,9 +2,10 @@ set -euo pipefail # Patch a CCC clone for use in the stack workspace. -# Usage: ccc-dev/patch.sh <ccc-repo-dir> +# Usage: ccc-dev/patch.sh <ccc-repo-dir> <merge-count> -REPO_DIR="${1:?Usage: patch.sh <ccc-repo-dir>}" +REPO_DIR="${1:?Usage: patch.sh <ccc-repo-dir> <merge-count>}" +MERGE_COUNT="${2:?Missing merge-count argument}" # Remove CCC's own lockfile so deps are recorded in the root pnpm-lock.yaml rm -f "$REPO_DIR/pnpm-lock.yaml" @@ -24,3 +25,11 @@ for pkg_json in "$REPO_DIR"/packages/*/package.json; do else . end ) else . end' "$pkg_json" > "$pkg_json.tmp" && mv "$pkg_json.tmp" "$pkg_json" done + +# Commit patched files with deterministic identity so record and replay produce the same hash +export GIT_AUTHOR_NAME="ci" GIT_AUTHOR_EMAIL="ci@local" +export GIT_COMMITTER_NAME="ci" GIT_COMMITTER_EMAIL="ci@local" +PATCH_TS="@$((MERGE_COUNT + 1)) +0000" +export GIT_AUTHOR_DATE="$PATCH_TS" GIT_COMMITTER_DATE="$PATCH_TS" +git -C "$REPO_DIR" add -A +git -C "$REPO_DIR" commit -m "patch: source-level type resolution" diff --git a/ccc-dev/pins/HEAD b/ccc-dev/pins/HEAD index 29779a1..d2b2d1a 100644 --- a/ccc-dev/pins/HEAD +++ b/ccc-dev/pins/HEAD @@ -1 +1 @@ -c6eb9a0aeb74f91b5fceedd56dde418ed71d151e +ddd976a81f71d14135714a2b365cb6e3653126fc diff --git a/ccc-dev/record.sh b/ccc-dev/record.sh index 248f49c..c9c5f8b 100644 --- a/ccc-dev/record.sh +++ b/ccc-dev/record.sh @@ -13,9 +13,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_DIR="$SCRIPT_DIR/ccc" PATCH_DIR="$SCRIPT_DIR/pins" -# Verify Claude CLI is available (needed for conflict resolution) -if [ $# -gt 0 ] && ! command -v claude &>/dev/null; then - echo "ERROR: 'claude' CLI required for conflict resolution (run: pnpm install)" >&2 +# Guard: abort if ccc-dev/ccc/ has pending work +if ! bash "$SCRIPT_DIR/status.sh" >/dev/null 2>&1; then + bash "$SCRIPT_DIR/status.sh" >&2 + echo "" >&2 + echo "ERROR: ccc-dev/ccc/ has pending work that would be lost." >&2 + echo "Push with 'pnpm ccc:push', commit, or remove ccc-dev/ccc/ manually." >&2 exit 1 fi @@ -30,9 +33,6 @@ git clone --filter=blob:none "$REPO_URL" "$REPO_DIR" BASE_SHA=$(git -C "$REPO_DIR" rev-parse HEAD) git -C "$REPO_DIR" checkout -b wip -# Prevent nested Claude Code detection when invoking claude CLI below -unset CLAUDECODE 2>/dev/null || true - MERGE_IDX=0 for REF in "$@"; do @@ -70,15 +70,15 @@ for REF in "$@"; do # Capture conflicted file list BEFORE resolution mapfile -t CONFLICTED < <(git -C "$REPO_DIR" diff --name-only --diff-filter=U) - # Resolve each conflicted file with Claude + # Resolve each conflicted file with AI Coworker for FILE in "${CONFLICTED[@]}"; do - claude --print --model sonnet --no-session-persistence \ + pnpm --silent coworker:ask \ -p "You are a merge conflict resolver. Output ONLY the resolved file content. Merge both sides meaningfully. No explanations, no code fences, no extra text." \ < "$REPO_DIR/$FILE" > "$REPO_DIR/${FILE}.resolved" # Validate resolution if [ ! -s "$REPO_DIR/${FILE}.resolved" ]; then - echo "ERROR: Claude returned empty resolution for $FILE" >&2 + echo "ERROR: AI Coworker returned empty resolution for $FILE" >&2 exit 1 fi if grep -q '<<<<<<<' "$REPO_DIR/${FILE}.resolved"; then @@ -107,7 +107,7 @@ for REF in "$@"; do echo "$MERGE_SHA $REF" >> "$PATCH_DIR/REFS" done -bash "$SCRIPT_DIR/patch.sh" "$REPO_DIR" +bash "$SCRIPT_DIR/patch.sh" "$REPO_DIR" "$MERGE_IDX" # Prepend BASE SHA as first line of REFS mkdir -p "$PATCH_DIR" diff --git a/ccc-dev/replay.sh b/ccc-dev/replay.sh index 1c7efd8..d07feff 100644 --- a/ccc-dev/replay.sh +++ b/ccc-dev/replay.sh @@ -76,12 +76,15 @@ while IFS=' ' read -r SHA REF_NAME; do fi done < <(tail -n +2 "$PATCH_DIR/REFS") -bash "$SCRIPT_DIR/patch.sh" "$REPO_DIR" +bash "$SCRIPT_DIR/patch.sh" "$REPO_DIR" "$MERGE_IDX" # Verify HEAD SHA matches recording ACTUAL=$(git -C "$REPO_DIR" rev-parse HEAD) EXPECTED=$(cat "$PATCH_DIR/HEAD") if [ "$ACTUAL" != "$EXPECTED" ]; then - echo "ERROR: replay diverged from recording (expected $EXPECTED, got $ACTUAL)" >&2 + echo "FAIL: replay HEAD ($ACTUAL) != pinned HEAD ($EXPECTED)" >&2 + echo "Pins are stale or corrupted. Re-record with 'pnpm ccc:record'." >&2 exit 1 fi + +echo "OK — replay HEAD matches pinned HEAD ($EXPECTED)" diff --git a/ccc-dev/status.sh b/ccc-dev/status.sh new file mode 100644 index 0000000..86b3d8e --- /dev/null +++ b/ccc-dev/status.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Check whether ccc-dev/ccc/ is safe to wipe. +# Exit 0 → safe (not cloned, or matches pins exactly) +# Exit 1 → has custom work (any changes vs pinned commit, diverged HEAD, or no pins to compare) + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$SCRIPT_DIR/ccc" +PINS_DIR="$SCRIPT_DIR/pins" + +if [ ! -d "$REPO_DIR" ]; then + echo "ccc-dev/ccc/ is not cloned" + exit 0 +fi + +if [ ! -f "$PINS_DIR/HEAD" ]; then + echo "ccc-dev/ccc/ exists but no pins/HEAD — custom clone" + exit 1 +fi + +PINNED=$(cat "$PINS_DIR/HEAD") +ACTUAL=$(git -C "$REPO_DIR" rev-parse HEAD) + +if [ "$ACTUAL" != "$PINNED" ]; then + echo "HEAD diverged from pins/HEAD:" + echo " pinned $PINNED" + echo " actual $ACTUAL" + git -C "$REPO_DIR" log --oneline "$PINNED..$ACTUAL" 2>/dev/null || true + exit 1 +fi + +# Compare pinned commit directly against working tree. +# git diff <commit> catches unstaged AND staged changes in one shot. +if ! git -C "$REPO_DIR" diff "$PINNED" --quiet 2>/dev/null \ + || [ -n "$(git -C "$REPO_DIR" ls-files --others --exclude-standard 2>/dev/null)" ]; then + echo "ccc-dev/ccc/ has changes relative to pins:" + git -C "$REPO_DIR" diff "$PINNED" --stat 2>/dev/null || true + git -C "$REPO_DIR" ls-files --others --exclude-standard 2>/dev/null || true + exit 1 +fi + +echo "ccc-dev/ccc/ is clean (matches pins)" From c9547df43c962876d95b0c5d56f7f0d0529a25ed Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:32:26 +0000 Subject: [PATCH 4/5] chore(devcontainer): add telemetry opt-outs and clipboard support Add env vars to disable telemetry (DO_NOT_TRACK, DEVCONTAINERS_NO_TELEMETRY, NEXT_TELEMETRY_DISABLED, NO_UPDATE_NOTIFIER). Install wl-clipboard for clipboard support used by AGENTS.md copy-to-clipboard workflow. Rename claude alias to coworker. --- .devcontainer/devcontainer.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index dcb839b..f0e2002 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,13 @@ "containerEnv": { "DEVCONTAINER": "true", - "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" + "DEVCONTAINERS_NO_TELEMETRY": "true", + "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1", + "CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY": "1", + "DO_NOT_TRACK": "1", + "NEXT_TELEMETRY_DISABLED": "1", + "NO_UPDATE_NOTIFIER": "1", + "npm_config_update_notifier": "false" }, // Persisted volumes to speed installs and avoid repeated network requests @@ -24,10 +30,12 @@ "postCreateCommand": { // Enable corepack and fix workspace ownership for non-root user "permissions": "sudo corepack enable && sudo chown -R node:node /home/node ${containerWorkspaceFolder}", - // Needed for anthropic.claude-code extension to work with a pnpm claude installation - "aliases": "echo \"alias claude='pnpm claude'\" >> ~/.bash_aliases", + // Needed for AI Coworker extension to work with a pnpm installation + "aliases": "echo \"alias claude='pnpm coworker'\" >> ~/.bash_aliases", // Install GSD - "gsd": "pnpm dlx get-shit-done-cc@latest --claude --global --force-statusline" + "gsd": "pnpm dlx get-shit-done-cc@latest --claude --global --force-statusline", + // Clipboard support for wl-copy/wl-paste (used by CLAUDE.md) + "clipboard": "sudo apt-get update -qq && sudo apt-get install -y -qq wl-clipboard" }, // Run each time the container starts. Ensures updated dependencies are installed inside the container user environment. From 93b3cce0f3717649d15a66587c9e1a8bfa4cf3d9 Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:32:48 +0000 Subject: [PATCH 5/5] docs: update README, planning docs, and ccc-dev references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update README with corrected dependency graph (core → dao edge), new developer scripts table, and repo clone URL. Update planning docs to reference AI Coworker instead of Claude CLI, document ccc:status/ccc:clean/ccc:reset commands, and remove obsolete scripts/ section. Update ccc-dev/README.md with status check docs and deterministic commit explanation. --- .planning/codebase/CONVENTIONS.md | 2 +- .planning/codebase/STACK.md | 4 +-- .planning/codebase/STRUCTURE.md | 16 ++++------- README.md | 46 +++++++++++++++++-------------- ccc-dev/README.md | 12 ++++---- 5 files changed, 41 insertions(+), 39 deletions(-) diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md index 09159be..6de6226 100644 --- a/.planning/codebase/CONVENTIONS.md +++ b/.planning/codebase/CONVENTIONS.md @@ -5,7 +5,7 @@ ## Important Context **Tooling:** -- `gh` CLI is NOT available and must NOT be installed. Use `pnpm pr` to open PRs and `pnpm review` to fetch PR comments. +- `gh` CLI is NOT available and must NOT be installed. PR and review workflows are handled inline by the AI Coworker (see AGENTS.md). **Legacy vs. New code:** - `@ickb/lumos-utils@1.4.2` and `@ickb/v1-core@1.4.2` are **LEGACY and DEPRECATED** npm packages. The apps (`apps/bot`, `apps/tester`, `apps/interface`) still depend on them. diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md index 301548a..4692690 100644 --- a/.planning/codebase/STACK.md +++ b/.planning/codebase/STACK.md @@ -101,11 +101,11 @@ The repo supports using a local development build of CCC for testing unpublished **`ccc-dev/record.sh`:** - Clones the CCC repo (`https://github.com/ckb-devrel/ccc.git`) into `./ccc-dev/ccc/` - Accepts refs as args: branch names, PR numbers, or commit SHAs -- Merges specified refs onto a `wip` branch (uses Claude CLI for merge conflict resolution) +- Merges specified refs onto a `wip` branch (uses AI Coworker CLI for merge conflict resolution) - Builds CCC locally: `pnpm build:prepare && pnpm build` - Run via: `pnpm ccc:record` (default invocation: `bash ccc-dev/record.sh releases/next releases/udt`) - The `ccc-dev/ccc/` directory is gitignored -- Skips if `ccc-dev/ccc/` already exists (remove it to redo setup) +- Aborts if `ccc-dev/ccc/` has pending work (any changes vs pinned commit, diverged HEAD, or untracked files) **`.pnpmfile.cjs`:** - A pnpm `readPackage` hook that auto-discovers all packages in `ccc-dev/ccc/packages/*/package.json` diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md index 1cb8c7b..b13ff40 100644 --- a/.planning/codebase/STRUCTURE.md +++ b/.planning/codebase/STRUCTURE.md @@ -73,7 +73,7 @@ │ │ ├── REFS # Pinned branch HEADs │ │ └── resolutions/ # Merge conflict resolutions │ ├── ccc/ # Gitignored: ephemeral CCC clone (auto-generated) -│ ├── record.sh # Records new pins with Claude conflict resolution +│ ├── record.sh # Records new pins with AI Coworker conflict resolution │ ├── replay.sh # Deterministically rebuilds ccc/ from pins │ └── tsc.mjs # TypeScript compilation script override ├── contracts/ # Reference: Rust on-chain contracts (git-ignored, clone via `pnpm reference`) @@ -345,19 +345,15 @@ - System: Record/replay mechanism for deterministic, conflict-free builds - `pins/`: Committed directory containing: - `REFS`: Pinned branch HEADs for merging - - `resolutions/`: Serialized conflict resolutions with Claude aid + - `resolutions/`: Serialized conflict resolutions with AI Coworker aid - `ccc/`: Generated from pins; auto-deleted and rebuilt on `pnpm install` - Activation: `.pnpmfile.cjs` hook triggers `replay.sh` and overrides package resolution - Commands: - - Record: `pnpm ccc:record releases/next releases/udt` (requires Claude CLI) + - Record: `pnpm ccc:record releases/next releases/udt` (requires AI Coworker CLI) + - Status: `pnpm ccc:status` (check for pending work in `ccc/`) - Rebuild: `pnpm install` (automatic) - - Clean: `pnpm ccc:nuke && pnpm install` - -**scripts/:** -- Purpose: Developer convenience scripts -- Files: `pr.sh` (GitHub PR creation), `review.sh` (fetch PR comments) -- Execution: Via `pnpm pr` and `pnpm review` shortcuts -- Committed: Yes + - Clean (re-replay): `pnpm ccc:clean && pnpm install` (guarded) + - Reset (published): `pnpm ccc:reset && pnpm install` (guarded) **node_modules/:** - Purpose: Installed npm/pnpm dependencies diff --git a/README.md b/README.md index 6eab3d9..62be57a 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,23 @@ This monorepo is developing the **new generation** of iCKB libraries, replacing **New packages** (under `packages/`, built on CCC): -| Package | Purpose | Status | -|---|---|---| +| Package | Purpose | Status | +| ------------- | -------------------------------------------------------------------------- | ------------------ | | `@ickb/utils` | Blockchain primitives, transaction helpers, epoch arithmetic, UDT handling | Active development | -| `@ickb/dao` | Nervos DAO abstraction layer | Active development | -| `@ickb/order` | Limit order cell management | Active development | -| `@ickb/core` | iCKB core protocol logic (deposits, receipts, owned owner) | Active development | -| `@ickb/sdk` | High-level SDK composing all packages | Active development | +| `@ickb/dao` | Nervos DAO abstraction layer | Active development | +| `@ickb/order` | Limit order cell management | Active development | +| `@ickb/core` | iCKB core protocol logic (deposits, receipts, owned owner) | Active development | +| `@ickb/sdk` | High-level SDK composing all packages | Active development | **Apps migration status:** -| App | Purpose | Stack | -|---|---|---| -| `apps/faucet` | Testnet CKB distribution | **Migrated** to new packages + CCC | -| `apps/sampler` | iCKB exchange rate sampling | **Migrated** to new packages + CCC | -| `apps/bot` | Automated order matching | Legacy (`@ickb/v1-core` + Lumos) | -| `apps/tester` | Order creation simulator | Legacy (`@ickb/v1-core` + Lumos) | -| `apps/interface` | React web UI | Legacy (`@ickb/v1-core` + Lumos) | +| App | Purpose | Stack | +| ---------------- | --------------------------- | ---------------------------------- | +| `apps/faucet` | Testnet CKB distribution | **Migrated** to new packages + CCC | +| `apps/sampler` | iCKB exchange rate sampling | **Migrated** to new packages + CCC | +| `apps/bot` | Automated order matching | Legacy (`@ickb/v1-core` + Lumos) | +| `apps/tester` | Order creation simulator | Legacy (`@ickb/v1-core` + Lumos) | +| `apps/interface` | React web UI | Legacy (`@ickb/v1-core` + Lumos) | **Key upstream contributions:** UDT and Epoch support were contributed to CCC upstream and have been merged. Some local utilities may overlap with features now available natively in CCC. @@ -36,6 +36,7 @@ graph TD; C["@ickb/dao"] --> A; C --> B; D["@ickb/core"] --> A; + D --> B; D --> C; E["@ickb/order"] --> A; E --> B; @@ -58,10 +59,10 @@ graph TD; When `ccc-dev/pins/REFS` is committed, `pnpm install` automatically sets up the CCC local development environment on first run (by replaying pinned merges via `ccc-dev/replay.sh`). No manual setup step is needed — just clone and install: ```bash -git clone <repo-url> && cd stack && pnpm install +git clone git@github.com:ickb/stack.git && cd stack && pnpm install ``` -To redo the setup from scratch: `rm -rf ccc-dev/ccc && pnpm install`. +To redo the setup from scratch: `pnpm ccc:clean && pnpm install`. See [ccc-dev/README.md](ccc-dev/README.md) for recording new pins, developing CCC PRs, and the full workflow. @@ -80,12 +81,15 @@ This clones two repos into the project root (both are git-ignored and made read- ## Developer Scripts -| Command | Description | -|---|---| -| `pnpm pr` | Open a GitHub PR creation page for the current branch. Uses Claude to auto-generate title and body when available, falls back to branch name and commit log. | -| `pnpm review` | Fetch and display PR review comments from GitHub for the current branch (or `pnpm review -- --pr <number>` for a specific PR). | - -> **Note:** `gh` CLI is not available in this environment. Use `pnpm pr` and `pnpm review` instead. +| Command | Description | +| ------------------- | ------------------------------------------------------------------------------------- | +| `pnpm coworker` | Launch an interactive AI Coworker session (full autonomy, opus model). | +| `pnpm coworker:ask` | One-shot AI query for scripting (sonnet model, stateless). Used by `pnpm ccc:record`. | +| `pnpm ccc:status` | Check if CCC clone matches pinned state. Exit 0 = safe to wipe. | +| `pnpm ccc:record` | Record CCC pins (clone, merge refs, build). Guarded against pending work. | +| `pnpm ccc:clean` | Remove CCC clone, keep pins (guarded). Re-replay on next `pnpm install`. | +| `pnpm ccc:reset` | Remove CCC clone and pins (guarded). Restores published CCC packages. | +| `pnpm check:full` | Wipe derived state and validate from scratch. Skips wipe if CCC has pending work. | ## Epoch Semantic Versioning diff --git a/ccc-dev/README.md b/ccc-dev/README.md index 361517c..170e7df 100644 --- a/ccc-dev/README.md +++ b/ccc-dev/README.md @@ -10,7 +10,7 @@ CCC has unreleased branches (`releases/next`, `releases/udt`) that this project 2. **Workspace override** — When `ccc-dev/ccc/` is present, `.pnpmfile.cjs` auto-discovers all CCC packages and rewrites `@ckb-ccc/*` dependencies to `workspace:*` — no manual `pnpm.overrides` needed. This is necessary because `catalog:` specifiers resolve to a semver range _before_ pnpm considers workspace linking — even with `link-workspace-packages = true`, pnpm fetches from the registry without this hook. When CCC is not cloned, the hook is a no-op and deps resolve from the registry normally. -3. **Source-level types** — `patch.sh` (called by both `record.sh` and `replay.sh`) patches CCC's `package.json` exports to point TypeScript at `.ts` source instead of built `.d.ts`. This gives real-time type feedback when editing across the CCC/stack boundary — changes in CCC source are immediately visible to stack packages without rebuilding. +3. **Source-level types** — `patch.sh` (called by both `record.sh` and `replay.sh`) patches CCC's `package.json` exports to point TypeScript at `.ts` source instead of built `.d.ts`, then creates a deterministic git commit (fixed author/date) so record and replay produce the same `pins/HEAD` hash. This gives real-time type feedback when editing across the CCC/stack boundary — changes in CCC source are immediately visible to stack packages without rebuilding. 4. **Diagnostic filtering** — `ccc-dev/tsc.mjs` is a tsc wrapper used by stack package builds. Because CCC `.ts` source is type-checked under the stack's stricter tsconfig (`verbatimModuleSyntax`, `noImplicitOverride`, `noUncheckedIndexedAccess`), plain `tsc` would report hundreds of CCC diagnostics that aren't real integration errors. The wrapper emits output normally and only fails on diagnostics from stack source files. When CCC is not cloned, packages fall back to plain `tsc`. @@ -33,7 +33,7 @@ Recording captures the current upstream state and any conflict resolutions: pnpm ccc:record ``` -This runs `ccc-dev/record.sh` which clones CCC, merges the configured refs, uses Claude CLI to resolve any conflicts, patches for source-level type resolution, and writes `pins/`. Commit the resulting `ccc-dev/pins/` directory so other contributors get the same build. +This runs `ccc-dev/record.sh` which clones CCC, merges the configured refs, uses AI Coworker to resolve any conflicts, patches for source-level type resolution, and writes `pins/`. Commit the resulting `ccc-dev/pins/` directory so other contributors get the same build. The `ccc:record` script in `package.json` is preconfigured with the current refs: @@ -61,7 +61,7 @@ bash ccc-dev/record.sh 268 releases/next bash ccc-dev/record.sh abc1234 ``` -Refs are merged sequentially onto a `wip` branch, then CCC is built. On merge conflicts, the script auto-resolves them using Claude. +Refs are merged sequentially onto a `wip` branch, then CCC is built. On merge conflicts, the script auto-resolves them using AI Coworker. ## Developing CCC PRs @@ -96,15 +96,17 @@ git checkout wip # return to development ## Switching modes +**Check for pending work:** `pnpm ccc:status` — exit 0 if `ccc-dev/ccc/` matches pinned state (safe to wipe), exit 1 otherwise. + **Local CCC (default when `pins/` is committed):** `pnpm install` auto-replays pins and overrides deps. **Published CCC:** `pnpm ccc:reset && pnpm install` — removes clone and pins, restores published packages. -**Re-record:** `pnpm ccc:record` wipes and re-records everything from scratch. +**Re-record:** `pnpm ccc:record` wipes and re-records everything from scratch. Aborts if `ccc-dev/ccc/` has pending work. **Force re-replay:** `pnpm ccc:clean && pnpm install` — removes clone but keeps pins, replays on next install. ## Requirements -- **Recording** (`pnpm ccc:record`): Requires [Claude CLI](https://www.npmjs.com/package/@anthropic-ai/claude-code) for automated conflict resolution (only when merging refs). +- **Recording** (`pnpm ccc:record`): Requires the AI Coworker CLI (installed as a devDependency; invoked via `pnpm coworker:ask`) for automated conflict resolution (only when merging refs). - **Replay** (`pnpm install`): No extra tools needed — works for any contributor with just pnpm.