chainlesschain 0.162.30 → 0.162.32
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/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-CsNttUU7.js → AIOps-Cg_uWAVl.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lgohjckQ.js → ActionButton-DSFtQ1c2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-ccV3LAca.js → Analytics-BMxpkw8y.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-tgVxlmsx.js +9 -0
- package/src/assets/web-panel/assets/{Audit-B1gFM5U9.js → Audit-DwzGllcp.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BeWE3ERo.js → Backup-BG28Y2MV.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CDkPsNG2.js → BaseInput-TXthbazl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-ztb9ia6e.js → Chat-D096SxaD.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Dlw_6n3M.js → ChatBubbleRenderer-PIx0Eu9I.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BcfRBlIY.js → Checkbox-Czttw1JS.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DOs99xkr.js → Codegen-DZtMgv4q.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D1X6tYlj.js → Col-D3DnfExY.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DTksIWtz.js → Community-Bj5AdwqY.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DIJtAYBO.js → Compact-BQ8Zszub.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BBf7LF_k.js → Compliance-DXacb34n.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-UBPXQ40s.js → Cowork-BgMUBTkw.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CkRm1jPB.js → Cron-fqBWOqlN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-qALlTl7e.js → Crosschain-E4oa1MWy.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CqyqVS6E.js → DID-pwgfYZaV.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-n8mdLFIR.js +3 -0
- package/src/assets/web-panel/assets/{Dropdown-Cb5UzbSZ.js → Dropdown--6DYqxk7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CarBq8Fk.js → EmailListRenderer-CkjQluz3.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CSiGXaZz.js → FamilyGuardDashboard-u-QTQ-OC.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DUxhVoBN.js → Federation-D219M5Qc.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BoMQpkhx.js → FormItemContext-BBU_aopC.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DTVqC_CX.js → GenericCardRenderer-pTMCIHcM.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_XuPtK5.js → Git-ClcCARWt.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BZyqlqz-.js → Governance-CvUi3I93.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DdZVUimI.js → Inference-DT-a4pVg.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-IzZ-jnCn.js → KnowledgeGraph-DHMs2LY8.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-koTK6eNc.js → Logs-D2s4eV1N.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-6zpJ1L8n.js → Marketplace-YC5-fx-6.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Ywc4IVks.js → McpTools-7JHTEC4T.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C_zB9dUa.js → Memory-BudotVLD.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Nc05r24L.js → MobileBridge-CAiRyLVU.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-BJGxL526.js → MobileProjects-CrJJOCFw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Im7SIcz1.js → Mtc-d0iY0CeK.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BFFzvzMD.js → MtcAudit-aI2cG1UP.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CcNEbycq.js → Multisig-4bF70khG.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CDH6OTXN.js → NLProgramming-CwLib1S7.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Dqg3QXcU.js → Notes-Wt7AuFRU.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CDVmK1eU.js → NotificationSettings-D081vV_7.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DCPei1L9.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DJb9bRQS.js → Organization-BNEsUNdP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CK7Q5dje.js → Overflow-B_1iUXDD.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CJIyYfwc.js → P2P-Dbc-kNwJ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-uqRULcuw.js → PdhVaultBrowser-D8Xh289k.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Crvwt6bq.js → Permissions-C77mM6-n.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DcN5OWzg.js → PersonalDataHub-Dj0J3r_K.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DfWJvvJW.js → Pipeline-B6F0WQ2C.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DepD0S3v.js → Privacy-eDKOkyyq.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-B7OKhH27.js → ProjectInit-DAWwhr5_.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BJ4ueRFv.js → ProjectSettings-DwdK8k6I.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Cb3p5QAP.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Dl0FT1S3.js → Providers--DcYxQfN.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-V2hYLhfp.js → QuickAsk-DU268niT.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-8Kaiodgv.js → Recommend-ChnflhV1.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-CsxB3JGg.js → Reputation-DSsY3bQG.js} +1 -1
- package/src/assets/web-panel/assets/{Row-6-x7tEYq.js → Row-Zb-EjmgQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Buv6f5tw.js → RssFeed-CGLiixZB.js} +3 -3
- package/src/assets/web-panel/assets/{Search-ABrDz84n.js → Search-Dhr_po-U.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DqOJmz18.js → Security-GMYNhGsR.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Cq4Tda3q.js → Services-DiOpnVY0.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-n74QlyYq.js → Skeleton-DG3ez6ME.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CC0iozL5.js → Skills-DZGptytP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-hwRgJ99Z.js → Sla-CtGpE3xA.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-B6Bs6_-8.js → SpeechSettings-DQFw6Cf9.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CTp2dZ0z.js → SyncSettings-C8X78RpX.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D70Lis6S.js → Tasks-DtVkhWCV.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Cags0ssw.js → Templates-SF9_ZWsV.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BxCMzzGt.js → Tenant-BbIQSVZz.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-v05SDqHd.js → Terminal-DKr5zDwu.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BLUDHbBL.js → TimelineRenderer-BtLaNaWr.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D-xKLJYv.js → Tokens-CfYbk2NG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B47tVIbH.js → Trigger-BLX_XDP0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DmRU9kfs.js → Trust-BWxUv9PR.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DzgSGs-c.js → UkeySign-DRwTyQD4.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C6qu58up.js → VideoEditing-BsC4VOSo.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dh8ZWx8f.js → Wallet-CSsO1NJU.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DFHOVuAY.js → WebAuthn-z1MxiFzS.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B_fyQ3Y_.js → WorkflowEditor-B1vV7uuJ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BR-WxnCQ.js → chat-C0NJRaL2.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C-6RysQe.js → colors-CHRiteWF.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-B_9_SCKN.js → compact-item-2XmBBKPD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D6rklIbE.js → createContext-DkedHC38.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DmNpkOdC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BrotgSvd.js → hasIn-Bpn9Xrlw.js} +1 -1
- package/src/assets/web-panel/assets/index-7nAysteg.js +1 -0
- package/src/assets/web-panel/assets/{index-MCmNzIC7.js → index-B5NGWgHp.js} +1 -1
- package/src/assets/web-panel/assets/{index-GzuCTHVZ.js → index-BItcSqan.js} +3 -3
- package/src/assets/web-panel/assets/index-BKWSQilQ.js +1 -0
- package/src/assets/web-panel/assets/{index-DTCUOKu9.js → index-BN068mCR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bv9BrnD2.js → index-BOsIgPge.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfqUsPl2.js → index-BYUd69vM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cn21XmDt.js → index-BYmwEaIk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWmJukRW.js → index-BZ1gOoiG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bwkg_EJk.js → index-BfY9U3X5.js} +1 -1
- package/src/assets/web-panel/assets/{index-MBOwmoOi.js → index-BveL_4n3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJ70GAW2.js → index-CCg6ZY4t.js} +1 -1
- package/src/assets/web-panel/assets/{index-B85rQNYG.js → index-CJOoo72F.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cn5ghmbB.js → index-CToQxpWz.js} +1 -1
- package/src/assets/web-panel/assets/{index-rWiOF7Iu.js → index-CWgWrrWs.js} +1 -1
- package/src/assets/web-panel/assets/{index-PzM_GlKb.js → index-CdR7RfRP.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZehgEQYa.js → index-Cljnfuxu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsDNNDBN.js → index-CxvA72CP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6KqyxG1.js → index-CyJpmSHZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-E_5VXq8H.js → index-D7U411hK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJgp_QFo.js → index-D9mNfpxi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTpElYJs.js → index-DAFLFMXQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMnomft7.js → index-DAeHmElB.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2yXH6vy.js → index-DDy_RDjs.js} +1 -1
- package/src/assets/web-panel/assets/{index-kkjq_hwC.js → index-DE5Qm9UI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTh0fWI4.js → index-DM9JrnYi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DigjvHuo.js → index-DMbF-Euw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkpDFJRn.js → index-DUBsq_1G.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIiCIC2j.js → index-De49R7TX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsWVDOd2.js → index-De5vOO9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAfRNHna.js → index-Dk7P-q3n.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdDmzoPE.js → index-DryKGM_t.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTQkYbir.js → index-DtU4qZRF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK8YwdNd.js → index-NuBsCRaR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaLhL3Tj.js → index-Sk3-3tKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTpxOc5s.js → index-alGjpoM1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrGp-4E2.js → index-cfSUlOfY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbRl_gIW.js → index-i4W_EAuh.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCWzUY8K.js → index-uHGxyZtQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C2v_L5na.js → initDefaultProps-DlDE-QgI.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DNDqGbfr.js → motion-CodUbIRF.js} +1 -1
- package/src/assets/web-panel/assets/{move-xvpQ_6hJ.js → move-DaLwsHeR.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Cb0FsfrO.js → omit-DdVg-3rL.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BxhYpnum.js → pickAttrs-KLR1EVCo.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B3soaW4h.js → placementArrow-ChV7HvNw.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B-eRSLvd.js → responsiveObserve-BB_A8dBt.js} +1 -1
- package/src/assets/web-panel/assets/{slide--cM2ZOx-.js → slide-Bc1tQnIK.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DjBhfi8Q.js → statusUtils-CgrveSb0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C30mMh8o.js → styleChecker-vXAYhhjz.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-f7y2Qlzs.js → useFlexGapSupport-BCIMPfq9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-iTCXoLoZ.js → useFs-DMZGdr6G.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BH0RXmVF.js → usePersonalDataHub-118tWI_Z.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DQtmeDXM.js → vnode-Z7O2Y7JP.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-vw50zkLZ.js → zoom-BXym6zmD.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +333 -1
- package/src/commands/ask.js +35 -1
- package/src/commands/checkpoint.js +439 -0
- package/src/commands/compact.js +150 -0
- package/src/commands/cost.js +114 -0
- package/src/commands/goal.js +417 -0
- package/src/commands/hub.js +7 -0
- package/src/commands/session.js +22 -2
- package/src/harness/prompt-compressor.js +71 -1
- package/src/index.js +8 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/checkpoint-store.js +523 -0
- package/src/lib/file-checkpoint.js +300 -0
- package/src/lib/goal-context.js +87 -0
- package/src/lib/goal-store.js +308 -0
- package/src/lib/llm-pricing.js +227 -0
- package/src/lib/personal-data-hub-wiring.js +30 -0
- package/src/lib/recent-session.js +72 -0
- package/src/lib/session-picker.js +68 -0
- package/src/repl/agent-repl.js +101 -9
- package/src/repl/chat-repl.js +16 -1
- package/src/runtime/agent-core.js +313 -32
- package/src/runtime/fallback-model.js +109 -0
- package/src/runtime/file-ref-expander.js +258 -0
- package/src/runtime/headless-runner.js +601 -0
- package/src/runtime/headless-stream.js +315 -0
- package/src/runtime/policies/agent-policy.js +7 -0
- package/src/runtime/quiet-stdout.js +35 -0
- package/src/runtime/system-prompt.js +60 -0
- package/src/assets/web-panel/assets/AppLayout-B0hl5cPk.js +0 -9
- package/src/assets/web-panel/assets/Dashboard-XlMpT7K_.js +0 -3
- package/src/assets/web-panel/assets/OrderTableRenderer-Bg0bkfjR.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dl_hPdhU.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BiN5HELJ.js +0 -1
- package/src/assets/web-panel/assets/index-BhxiT2LJ.js +0 -1
- package/src/assets/web-panel/assets/index-DBNSZ2oz.js +0 -1
package/src/index.js
CHANGED
|
@@ -56,6 +56,10 @@ import { registerMemoryCommand } from "./commands/memory.js";
|
|
|
56
56
|
import { registerPermMemCommand } from "./commands/permmem.js";
|
|
57
57
|
import { registerRCacheCommand } from "./commands/rcache.js";
|
|
58
58
|
import { registerSessionCommand } from "./commands/session.js";
|
|
59
|
+
import { registerCostCommand } from "./commands/cost.js";
|
|
60
|
+
import { registerCheckpointCommand } from "./commands/checkpoint.js";
|
|
61
|
+
import { registerGoalCommand } from "./commands/goal.js";
|
|
62
|
+
import { registerCompactCommand } from "./commands/compact.js";
|
|
59
63
|
import { registerConsolCommand } from "./commands/consol.js";
|
|
60
64
|
import { registerImportCommand } from "./commands/import.js";
|
|
61
65
|
import { registerExportCommand } from "./commands/export.js";
|
|
@@ -448,6 +452,10 @@ export function createProgram(opts = {}) {
|
|
|
448
452
|
registerPermMemCommand(program);
|
|
449
453
|
registerRCacheCommand(program);
|
|
450
454
|
registerSessionCommand(program);
|
|
455
|
+
registerCostCommand(program);
|
|
456
|
+
registerCheckpointCommand(program);
|
|
457
|
+
registerGoalCommand(program);
|
|
458
|
+
registerCompactCommand(program);
|
|
451
459
|
registerConsolCommand(program);
|
|
452
460
|
|
|
453
461
|
// Phase 2: Knowledge & content management
|
package/src/lib/agent-core.js
CHANGED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint store — file-level snapshot + rewind via git plumbing.
|
|
3
|
+
*
|
|
4
|
+
* A checkpoint is a full working-tree snapshot captured as a "shadow commit"
|
|
5
|
+
* under the `refs/cc-checkpoints/<session>/<id>` ref namespace. It is built with
|
|
6
|
+
* a TEMPORARY index (GIT_INDEX_FILE) so the user's real index and working tree
|
|
7
|
+
* are never touched on capture — safe to run alongside a normal `git` workflow
|
|
8
|
+
* or a parallel session (see memory `plumbing_rebase_parallel_session`).
|
|
9
|
+
*
|
|
10
|
+
* Rewind restores the working tree to a checkpoint's tree (this DOES write the
|
|
11
|
+
* working tree — that's the point), auto-creating a safety checkpoint first so
|
|
12
|
+
* a rewind is itself undoable.
|
|
13
|
+
*
|
|
14
|
+
* Snapshots respect .gitignore (via `git add -A`), so node_modules / build
|
|
15
|
+
* output are not captured — only the source the agent can meaningfully edit.
|
|
16
|
+
*
|
|
17
|
+
* Requires the cwd to be inside a git work tree; coding-agent cwd almost always
|
|
18
|
+
* is. When it is not, callers should fall back / report unavailable.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { spawnSync } from "node:child_process";
|
|
22
|
+
import { rmSync } from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
|
|
25
|
+
const REF_NS = "refs/cc-checkpoints";
|
|
26
|
+
// Deterministic identity for shadow commits so `git commit-tree` never trips on
|
|
27
|
+
// a missing user.name / user.email config.
|
|
28
|
+
const CHECKPOINT_IDENTITY = Object.freeze({
|
|
29
|
+
GIT_AUTHOR_NAME: "cc-checkpoint",
|
|
30
|
+
GIT_AUTHOR_EMAIL: "checkpoint@chainlesschain.local",
|
|
31
|
+
GIT_COMMITTER_NAME: "cc-checkpoint",
|
|
32
|
+
GIT_COMMITTER_EMAIL: "checkpoint@chainlesschain.local",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run git with an argv array (no shell → no quoting hazards). UTF-8 in/out.
|
|
37
|
+
*
|
|
38
|
+
* @param {string[]} args
|
|
39
|
+
* @param {object} [opts] { cwd, env, input }
|
|
40
|
+
* @returns {string} trimmed stdout
|
|
41
|
+
* @throws {Error} with git's stderr when the command fails
|
|
42
|
+
*/
|
|
43
|
+
function git(args, { cwd, env, input } = {}) {
|
|
44
|
+
const res = spawnSync("git", args, {
|
|
45
|
+
cwd,
|
|
46
|
+
input,
|
|
47
|
+
encoding: "utf-8",
|
|
48
|
+
windowsHide: true,
|
|
49
|
+
maxBuffer: 128 * 1024 * 1024,
|
|
50
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
51
|
+
});
|
|
52
|
+
if (res.error) throw res.error;
|
|
53
|
+
if (res.status !== 0) {
|
|
54
|
+
const msg = (res.stderr || res.stdout || "").toString().trim();
|
|
55
|
+
throw new Error(msg || `git ${args.join(" ")} failed (exit ${res.status})`);
|
|
56
|
+
}
|
|
57
|
+
return (res.stdout || "").toString().trim();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Best-effort: is this a usable git work tree (and is git on PATH)? */
|
|
61
|
+
export function isCheckpointAvailable(cwd = process.cwd()) {
|
|
62
|
+
try {
|
|
63
|
+
return git(["rev-parse", "--is-inside-work-tree"], { cwd }) === "true";
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Absolute top-level of the work tree — all snapshots are repo-wide. */
|
|
70
|
+
function repoRoot(cwd) {
|
|
71
|
+
return git(["rev-parse", "--show-toplevel"], { cwd });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Absolute .git dir, where temp index files live. */
|
|
75
|
+
function gitDir(root) {
|
|
76
|
+
return path.resolve(root, git(["rev-parse", "--git-dir"], { cwd: root }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Ref-safe session segment. */
|
|
80
|
+
function sanitizeSession(session) {
|
|
81
|
+
const s = String(session || "default").replace(/[^A-Za-z0-9._-]/g, "-");
|
|
82
|
+
return s || "default";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function sessionPrefix(session) {
|
|
86
|
+
return `${REF_NS}/${sanitizeSession(session)}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Unique temp index path inside .git (never the real index). */
|
|
90
|
+
function tempIndexPath(dir) {
|
|
91
|
+
return path.join(dir, `cc-checkpoint-index-${process.pid}-${Date.now()}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Snapshot the current working tree to a git tree object WITHOUT creating a
|
|
96
|
+
* commit and WITHOUT touching the real index. Returns the tree sha.
|
|
97
|
+
*/
|
|
98
|
+
function snapshotTree(root, dir) {
|
|
99
|
+
const tmpIndex = tempIndexPath(dir);
|
|
100
|
+
const env = { GIT_INDEX_FILE: tmpIndex };
|
|
101
|
+
try {
|
|
102
|
+
try {
|
|
103
|
+
git(["read-tree", "HEAD"], { cwd: root, env });
|
|
104
|
+
} catch {
|
|
105
|
+
/* fresh repo — empty temp index is fine */
|
|
106
|
+
}
|
|
107
|
+
git(["add", "-A"], { cwd: root, env });
|
|
108
|
+
return git(["write-tree"], { cwd: root, env });
|
|
109
|
+
} finally {
|
|
110
|
+
rmSync(tmpIndex, { force: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Capture the current working tree as a shadow commit.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} cwd
|
|
118
|
+
* @param {object} [opts] { session, label, now, skipIfUnchanged }
|
|
119
|
+
* skipIfUnchanged: when the work tree is identical to the last checkpoint in
|
|
120
|
+
* the session, reuse it instead of creating a duplicate ref (returns
|
|
121
|
+
* `{ ...prior, reused: true }`). Matters for per-tool auto-checkpointing.
|
|
122
|
+
* @returns {{ id, ref, commit, tree, parent, label, session, createdAt, files, reused? }}
|
|
123
|
+
*/
|
|
124
|
+
export function createCheckpoint(cwd = process.cwd(), opts = {}) {
|
|
125
|
+
const root = repoRoot(cwd);
|
|
126
|
+
const dir = gitDir(root);
|
|
127
|
+
const session = sanitizeSession(opts.session);
|
|
128
|
+
const label = (opts.label || "").toString().replace(/\s+/g, " ").trim();
|
|
129
|
+
const createdAt = (opts.now ? new Date(opts.now) : new Date()).toISOString();
|
|
130
|
+
|
|
131
|
+
const tmpIndex = tempIndexPath(dir);
|
|
132
|
+
const env = { GIT_INDEX_FILE: tmpIndex };
|
|
133
|
+
try {
|
|
134
|
+
// Seed the temp index from HEAD when there is one, so `add -A` only diffs
|
|
135
|
+
// (faster on large repos). Harmless to skip in a fresh repo.
|
|
136
|
+
try {
|
|
137
|
+
git(["read-tree", "HEAD"], { cwd: root, env });
|
|
138
|
+
} catch {
|
|
139
|
+
/* no HEAD yet — empty temp index is fine */
|
|
140
|
+
}
|
|
141
|
+
git(["add", "-A"], { cwd: root, env });
|
|
142
|
+
const tree = git(["write-tree"], { cwd: root, env });
|
|
143
|
+
|
|
144
|
+
// Dedup: when nothing changed since the last checkpoint, reuse it instead of
|
|
145
|
+
// piling up identical refs (per-tool auto-checkpointing relies on this).
|
|
146
|
+
if (opts.skipIfUnchanged) {
|
|
147
|
+
try {
|
|
148
|
+
const tip = git(
|
|
149
|
+
[
|
|
150
|
+
"rev-parse",
|
|
151
|
+
"--verify",
|
|
152
|
+
"--quiet",
|
|
153
|
+
`${sessionPrefix(session)}/_tip`,
|
|
154
|
+
],
|
|
155
|
+
{ cwd: root },
|
|
156
|
+
);
|
|
157
|
+
if (
|
|
158
|
+
tip &&
|
|
159
|
+
git(["rev-parse", `${tip}^{tree}`], { cwd: root }) === tree
|
|
160
|
+
) {
|
|
161
|
+
const row = listRefs(root, session).find((r) => r.commit === tip);
|
|
162
|
+
if (row) {
|
|
163
|
+
return {
|
|
164
|
+
id: row.id,
|
|
165
|
+
ref: row.ref,
|
|
166
|
+
commit: tip,
|
|
167
|
+
tree,
|
|
168
|
+
parent: null,
|
|
169
|
+
label: row.label,
|
|
170
|
+
session,
|
|
171
|
+
createdAt: row.createdAt,
|
|
172
|
+
files: 0,
|
|
173
|
+
reused: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
/* fall through to a normal checkpoint */
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Chain onto the prior tip (or HEAD) so checkpoints form a readable history.
|
|
183
|
+
let parent = null;
|
|
184
|
+
try {
|
|
185
|
+
parent = git(
|
|
186
|
+
["rev-parse", "--verify", "--quiet", `${sessionPrefix(session)}/_tip`],
|
|
187
|
+
{
|
|
188
|
+
cwd: root,
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
} catch {
|
|
192
|
+
/* no tip yet */
|
|
193
|
+
}
|
|
194
|
+
if (!parent) {
|
|
195
|
+
try {
|
|
196
|
+
parent = git(["rev-parse", "--verify", "--quiet", "HEAD"], {
|
|
197
|
+
cwd: root,
|
|
198
|
+
});
|
|
199
|
+
} catch {
|
|
200
|
+
/* fresh repo, root commit */
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const message = `cc-checkpoint${label ? `: ${label}` : ""}\n`;
|
|
205
|
+
const commitArgs = ["commit-tree", tree];
|
|
206
|
+
if (parent) commitArgs.push("-p", parent);
|
|
207
|
+
const commit = git(commitArgs, {
|
|
208
|
+
cwd: root,
|
|
209
|
+
input: message,
|
|
210
|
+
env: { ...env, ...CHECKPOINT_IDENTITY },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const id = nextId(root, session);
|
|
214
|
+
const ref = `${sessionPrefix(session)}/${id}`;
|
|
215
|
+
git(["update-ref", ref, commit], { cwd: root });
|
|
216
|
+
git(["update-ref", `${sessionPrefix(session)}/_tip`, commit], {
|
|
217
|
+
cwd: root,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// File count in the snapshot (cheap, informative).
|
|
221
|
+
let files = 0;
|
|
222
|
+
try {
|
|
223
|
+
const out = git(["ls-tree", "-r", "--name-only", tree], { cwd: root });
|
|
224
|
+
files = out ? out.split("\n").filter(Boolean).length : 0;
|
|
225
|
+
} catch {
|
|
226
|
+
/* non-critical */
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return { id, ref, commit, tree, parent, label, session, createdAt, files };
|
|
230
|
+
} finally {
|
|
231
|
+
rmSync(tmpIndex, { force: true });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Next free sequential id (cp0001…) for a session. */
|
|
236
|
+
function nextId(root, session) {
|
|
237
|
+
const existing = new Set(listRefs(root, session).map((r) => r.id));
|
|
238
|
+
let n = existing.size + 1;
|
|
239
|
+
let id = `cp${String(n).padStart(4, "0")}`;
|
|
240
|
+
while (existing.has(id)) {
|
|
241
|
+
n += 1;
|
|
242
|
+
id = `cp${String(n).padStart(4, "0")}`;
|
|
243
|
+
}
|
|
244
|
+
return id;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Raw ref rows for a session (excludes the internal _tip pointer). */
|
|
248
|
+
function listRefs(root, session) {
|
|
249
|
+
const prefix = sessionPrefix(session);
|
|
250
|
+
let out = "";
|
|
251
|
+
try {
|
|
252
|
+
out = git(
|
|
253
|
+
[
|
|
254
|
+
"for-each-ref",
|
|
255
|
+
"--sort=creatordate",
|
|
256
|
+
"--format=%(refname)\t%(objectname)\t%(creatordate:iso-strict)\t%(contents:subject)",
|
|
257
|
+
prefix,
|
|
258
|
+
],
|
|
259
|
+
{ cwd: root },
|
|
260
|
+
);
|
|
261
|
+
} catch {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
if (!out) return [];
|
|
265
|
+
const rows = [];
|
|
266
|
+
for (const line of out.split("\n")) {
|
|
267
|
+
if (!line.trim()) continue;
|
|
268
|
+
const [refname, commit, createdAt, subject = ""] = line.split("\t");
|
|
269
|
+
const id = refname.slice(prefix.length + 1);
|
|
270
|
+
if (id === "_tip") continue;
|
|
271
|
+
const label = subject.replace(/^cc-checkpoint:?\s?/, "");
|
|
272
|
+
rows.push({ id, ref: refname, commit, createdAt, label });
|
|
273
|
+
}
|
|
274
|
+
return rows;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* List checkpoints for a session, newest-first.
|
|
279
|
+
*
|
|
280
|
+
* @returns {Array<{ id, ref, commit, createdAt, label }>}
|
|
281
|
+
*/
|
|
282
|
+
export function listCheckpoints(cwd = process.cwd(), opts = {}) {
|
|
283
|
+
const root = repoRoot(cwd);
|
|
284
|
+
return listRefs(root, opts.session).reverse();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Resolve a checkpoint id (cp0003), a ref, or a raw commit-ish to a commit sha.
|
|
289
|
+
*
|
|
290
|
+
* @throws {Error} when it cannot be resolved to a checkpoint commit
|
|
291
|
+
*/
|
|
292
|
+
export function resolveCheckpoint(cwd, idOrRef, opts = {}) {
|
|
293
|
+
const root = repoRoot(cwd);
|
|
294
|
+
const session = sanitizeSession(opts.session);
|
|
295
|
+
const candidates = [
|
|
296
|
+
`${sessionPrefix(session)}/${idOrRef}`, // bare id
|
|
297
|
+
idOrRef, // full ref or sha
|
|
298
|
+
];
|
|
299
|
+
for (const c of candidates) {
|
|
300
|
+
try {
|
|
301
|
+
return git(["rev-parse", "--verify", "--quiet", `${c}^{commit}`], {
|
|
302
|
+
cwd: root,
|
|
303
|
+
});
|
|
304
|
+
} catch {
|
|
305
|
+
/* try next */
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
throw new Error(`Checkpoint not found: ${idOrRef}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Compute what differs between a checkpoint and the current working tree.
|
|
313
|
+
*
|
|
314
|
+
* @returns {{ modified:string[], added:string[], deleted:string[] }}
|
|
315
|
+
* added = exists now, not in the checkpoint (a rewind would delete it)
|
|
316
|
+
* deleted = in the checkpoint, gone now (a rewind would recreate it)
|
|
317
|
+
*/
|
|
318
|
+
export function statusAgainst(cwd = process.cwd(), idOrRef, opts = {}) {
|
|
319
|
+
const root = repoRoot(cwd);
|
|
320
|
+
const dir = gitDir(root);
|
|
321
|
+
const session = sanitizeSession(opts.session);
|
|
322
|
+
const commit = resolveCheckpoint(root, idOrRef, { session });
|
|
323
|
+
const targetTree = git(["rev-parse", `${commit}^{tree}`], { cwd: root });
|
|
324
|
+
const currentTree = snapshotTree(root, dir);
|
|
325
|
+
return {
|
|
326
|
+
modified: diffNames(root, targetTree, currentTree, "M"),
|
|
327
|
+
added: diffNames(root, targetTree, currentTree, "A"),
|
|
328
|
+
deleted: diffNames(root, targetTree, currentTree, "D"),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Restore the working tree to a checkpoint. Creates a safety checkpoint first
|
|
334
|
+
* (unless dryRun / skipSafety).
|
|
335
|
+
*
|
|
336
|
+
* @param {object} [opts] { session, dryRun, skipSafety, now }
|
|
337
|
+
* @returns {{ restored, dryRun, target, safetyId, modified, deleted, recreated }}
|
|
338
|
+
*/
|
|
339
|
+
export function rewindTo(cwd = process.cwd(), idOrRef, opts = {}) {
|
|
340
|
+
const root = repoRoot(cwd);
|
|
341
|
+
const dir = gitDir(root);
|
|
342
|
+
const session = sanitizeSession(opts.session);
|
|
343
|
+
const targetCommit = resolveCheckpoint(root, idOrRef, { session });
|
|
344
|
+
const targetTree = git(["rev-parse", `${targetCommit}^{tree}`], {
|
|
345
|
+
cwd: root,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// What will change, relative to a fresh snapshot of the current state.
|
|
349
|
+
const currentTree = snapshotTree(root, dir);
|
|
350
|
+
const modified = diffNames(root, targetTree, currentTree, "M");
|
|
351
|
+
const added = diffNames(root, targetTree, currentTree, "A"); // → delete
|
|
352
|
+
const recreated = diffNames(root, targetTree, currentTree, "D"); // → recreate
|
|
353
|
+
|
|
354
|
+
if (opts.dryRun) {
|
|
355
|
+
return {
|
|
356
|
+
restored: false,
|
|
357
|
+
dryRun: true,
|
|
358
|
+
target: targetCommit,
|
|
359
|
+
safetyId: null,
|
|
360
|
+
modified: modified.length,
|
|
361
|
+
deleted: added.length,
|
|
362
|
+
recreated: recreated.length,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Safety net — snapshot the current state so this rewind is undoable.
|
|
367
|
+
let safetyId = null;
|
|
368
|
+
if (!opts.skipSafety) {
|
|
369
|
+
safetyId = createCheckpoint(root, {
|
|
370
|
+
session,
|
|
371
|
+
label: `auto: before rewind to ${idOrRef}`,
|
|
372
|
+
now: opts.now,
|
|
373
|
+
}).id;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Write the target tree over the working tree via a temp index.
|
|
377
|
+
const tmpIndex = tempIndexPath(dir);
|
|
378
|
+
const env = { GIT_INDEX_FILE: tmpIndex };
|
|
379
|
+
try {
|
|
380
|
+
git(["read-tree", targetTree], { cwd: root, env });
|
|
381
|
+
git(["checkout-index", "-a", "-f"], { cwd: root, env });
|
|
382
|
+
} finally {
|
|
383
|
+
rmSync(tmpIndex, { force: true });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Remove files the target snapshot does not contain (created since).
|
|
387
|
+
for (const rel of added) {
|
|
388
|
+
rmSync(path.resolve(root, rel), { force: true });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
restored: true,
|
|
393
|
+
dryRun: false,
|
|
394
|
+
target: targetCommit,
|
|
395
|
+
safetyId,
|
|
396
|
+
modified: modified.length,
|
|
397
|
+
deleted: added.length,
|
|
398
|
+
recreated: recreated.length,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** name-only diff between two trees filtered by status (A/M/D…). */
|
|
403
|
+
function diffNames(root, treeA, treeB, filter) {
|
|
404
|
+
try {
|
|
405
|
+
const out = git(
|
|
406
|
+
["diff", "--name-only", `--diff-filter=${filter}`, treeA, treeB],
|
|
407
|
+
{ cwd: root },
|
|
408
|
+
);
|
|
409
|
+
return out ? out.split("\n").filter(Boolean) : [];
|
|
410
|
+
} catch {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Diff a checkpoint against the current working tree.
|
|
417
|
+
*
|
|
418
|
+
* @param {object} [opts] { session, stat }
|
|
419
|
+
* @returns {string} git diff (stat or full patch)
|
|
420
|
+
*/
|
|
421
|
+
export function diffCheckpoint(cwd = process.cwd(), idOrRef, opts = {}) {
|
|
422
|
+
const root = repoRoot(cwd);
|
|
423
|
+
const dir = gitDir(root);
|
|
424
|
+
const session = sanitizeSession(opts.session);
|
|
425
|
+
const targetCommit = resolveCheckpoint(root, idOrRef, { session });
|
|
426
|
+
const targetTree = git(["rev-parse", `${targetCommit}^{tree}`], {
|
|
427
|
+
cwd: root,
|
|
428
|
+
});
|
|
429
|
+
// Snapshot current state so untracked files are included in the diff.
|
|
430
|
+
const currentTree = snapshotTree(root, dir);
|
|
431
|
+
const args = [
|
|
432
|
+
"diff",
|
|
433
|
+
opts.stat ? "--stat" : null,
|
|
434
|
+
targetTree,
|
|
435
|
+
currentTree,
|
|
436
|
+
].filter(Boolean);
|
|
437
|
+
return git(args, { cwd: root });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Inspect a checkpoint: metadata + the files it captured (with sizes).
|
|
442
|
+
*
|
|
443
|
+
* @returns {{ id, commit, createdAt, label, fileCount, files:Array<{rel,bytes}> }}
|
|
444
|
+
*/
|
|
445
|
+
export function showCheckpoint(cwd = process.cwd(), idOrRef, opts = {}) {
|
|
446
|
+
const root = repoRoot(cwd);
|
|
447
|
+
const session = sanitizeSession(opts.session);
|
|
448
|
+
const commit = resolveCheckpoint(root, idOrRef, { session });
|
|
449
|
+
const tree = git(["rev-parse", `${commit}^{tree}`], { cwd: root });
|
|
450
|
+
const meta = listRefs(root, session).find((r) => r.commit === commit) || {};
|
|
451
|
+
const files = [];
|
|
452
|
+
try {
|
|
453
|
+
// `-l` adds the blob size as the 4th whitespace-delimited field.
|
|
454
|
+
const out = git(["ls-tree", "-r", "-l", tree], { cwd: root });
|
|
455
|
+
for (const line of out.split("\n")) {
|
|
456
|
+
if (!line.trim()) continue;
|
|
457
|
+
const [head, rel] = line.split("\t"); // "<mode> <type> <sha> <size>\t<path>"
|
|
458
|
+
const bytes = parseInt(head.trim().split(/\s+/)[3], 10) || 0;
|
|
459
|
+
files.push({ rel, bytes });
|
|
460
|
+
}
|
|
461
|
+
} catch {
|
|
462
|
+
/* non-critical */
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
id: meta.id || idOrRef,
|
|
466
|
+
commit,
|
|
467
|
+
createdAt: meta.createdAt || null,
|
|
468
|
+
label: meta.label || "",
|
|
469
|
+
fileCount: files.length,
|
|
470
|
+
files,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Delete a single checkpoint ref.
|
|
476
|
+
*
|
|
477
|
+
* @returns {boolean} true if it existed and was removed
|
|
478
|
+
*/
|
|
479
|
+
export function deleteCheckpoint(cwd = process.cwd(), idOrRef, opts = {}) {
|
|
480
|
+
const root = repoRoot(cwd);
|
|
481
|
+
const session = sanitizeSession(opts.session);
|
|
482
|
+
let commit;
|
|
483
|
+
try {
|
|
484
|
+
commit = resolveCheckpoint(root, idOrRef, { session });
|
|
485
|
+
} catch {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
const row = listRefs(root, session).find((r) => r.commit === commit);
|
|
489
|
+
const ref = row ? row.ref : `${sessionPrefix(session)}/${idOrRef}`;
|
|
490
|
+
try {
|
|
491
|
+
git(["update-ref", "-d", ref], { cwd: root });
|
|
492
|
+
return true;
|
|
493
|
+
} catch {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Delete all checkpoints for a session (including the _tip pointer).
|
|
500
|
+
*
|
|
501
|
+
* @returns {number} count of checkpoint refs removed (excludes _tip)
|
|
502
|
+
*/
|
|
503
|
+
export function clearCheckpoints(cwd = process.cwd(), opts = {}) {
|
|
504
|
+
const root = repoRoot(cwd);
|
|
505
|
+
const session = sanitizeSession(opts.session);
|
|
506
|
+
const rows = listRefs(root, session);
|
|
507
|
+
for (const r of rows) {
|
|
508
|
+
try {
|
|
509
|
+
git(["update-ref", "-d", r.ref], { cwd: root });
|
|
510
|
+
} catch {
|
|
511
|
+
/* best-effort */
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
git(["update-ref", "-d", `${sessionPrefix(session)}/_tip`], { cwd: root });
|
|
516
|
+
} catch {
|
|
517
|
+
/* _tip may not exist */
|
|
518
|
+
}
|
|
519
|
+
return rows.length;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Exposed for unit tests / advanced callers.
|
|
523
|
+
export const _internals = { git, repoRoot, sanitizeSession, snapshotTree };
|