chainlesschain 0.162.39 → 0.162.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +368 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-DCjoAX_u.js → AIOps-Ut7EevnG.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-XHoOmsbP.js → ActionButton-Dv6BlfJg.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics--xaFkDnL.js → Analytics-TQVQuJ7u.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CSa3FBn8.js → AppLayout-MSqLm2WK.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-ONWXiAwG.js → Audit-mw81HwVy.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CKOPNdgy.js → Backup-BQcPWDb1.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-PNj4uVqg.js → BaseInput-BYo_pwBH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CZCulyXV.js → Chat-zi3YUKx2.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CjuJpfpV.js → ChatBubbleRenderer-DWSm1XJJ.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-jvy668lD.js → Checkbox-BvC8Erjt.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DhUebOQD.js → Codegen-C32vx0OP.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BiBvHfdT.js → Col-DMBwmqyZ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CmEdEti-.js → Community-nDWncmKV.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CtxpF4R5.js → Compact-lIc1HFn8.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CvPTrTAJ.js → Compliance-D14I_gd2.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BMafGHjy.js → Cowork-BiNI-_ZL.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-mdg_4TR1.js → Cron-N13sFzHb.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain--dGxsUvn.js → Crosschain-Dlnl0-v6.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C9oKaCml.js → DID-CxtYS31I.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CoGxKMvy.js → Dashboard-G4UnHlTR.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CDDu3ZZ3.js → Dropdown-BazlxFGY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Dy7_r9Ag.js → EmailListRenderer-BrpNdihm.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CNg6vImJ.js → FamilyGuardDashboard-HD7jbOOR.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CT61bf3u.js → Federation-Bz8lzAGI.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CSLRnXhg.js → FormItemContext-CcyzGS00.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CZ4NE5N3.js → GenericCardRenderer-DRo9cwmp.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DBuOma3L.js → Git-B7bn333J.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BTU_SEef.js → Governance-DZX9CWAM.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-47SAmLC_.js → Inference-B3XhsL6W.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DCrK5vP4.js → KnowledgeGraph-CxFRTlQe.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BqiDxdav.js → Logs-xuys6mKH.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CReUjsDt.js → Marketplace-CXyxv4WU.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-agZBV3p8.js → McpTools-BzZLQVI3.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-C_YvUtyS.js → Memory-BANtaBa7.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-41fP1Tui.js → MobileBridge-BJIwjmxr.js} +3 -3
- package/src/assets/web-panel/assets/{MobileProjects-BkqLvGfL.js → MobileProjects-B857uSAZ.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-JFJCXUnk.js → Mtc-Cn7ceFEz.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BHNpPZC9.js → MtcAudit-B0zE978G.js} +6 -6
- package/src/assets/web-panel/assets/{Multisig-DuCRumiz.js → Multisig-CQFT0wXW.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DK-g0fKY.js → NLProgramming-DSxKdVY-.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BSMcjsPf.js → Notes-DtlTfam8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-9ouC118H.js → NotificationSettings-CHQwayAg.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Brpmzh9n.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DSV7oRnR.js → Organization-nF_tzZDT.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DVkkORc3.js → Overflow-CgCSf_PH.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BXXjkkQD.js → P2P-Bvn46bLY.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-O5hNnLTP.js → PdhVaultBrowser-Bzl9k7Gj.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-D_s0H5Av.js → Permissions-Dmezbuo8.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CzMDrwUi.js → PersonalDataHub-lCKRxwZr.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-i9krLVTL.js → Pipeline-DDCGm9PA.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-cMQcj9I8.js → Privacy-Cgu18Kjl.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Ca_l7avo.js → ProjectInit-CkF1AeRY.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BkaIhd6b.js → ProjectSettings-D0Q-orz1.js} +2 -2
- package/src/assets/web-panel/assets/Projects-KfGELrSY.js +1 -0
- package/src/assets/web-panel/assets/{Providers-D0nzYiqz.js → Providers-BACLV0z8.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bzzr9d0f.js → QuickAsk-CPsZUqDl.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-C-UFbQnX.js → Recommend-5jX0OI1-.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BKMIKO5F.js → Reputation-5JKv54z0.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Bs7htK1T.js → Row-DLiTF5LY.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-v6MdULUh.js → RssFeed-CFdGmCKW.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DlRWYzvz.js → Search-BjIOnmA7.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DXWO37xX.js → Security-BujPqQSo.js} +4 -4
- package/src/assets/web-panel/assets/{Services-C2tWA-O0.js → Services-ChciPnMu.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Q8pIYY4a.js → Skeleton-Cwswp1Jv.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D7XBlErj.js → Skills-CtwR4vJV.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CiyMVPJ1.js → Sla-pRIevich.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CadCeeiR.js → SpeechSettings-BRqB28Ai.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DzNAUhQq.js → SyncSettings-BYyj58_h.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DTLpT48U.js +1 -0
- package/src/assets/web-panel/assets/{Templates-DfgEpUa4.js → Templates-Bbz_h7oW.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C8ajkuYi.js → Tenant-D-H4E3cu.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B9rHwQQx.js → Terminal-CLLi0-lV.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D1ZVNezX.js → TimelineRenderer-BKI6eG0k.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CAkED4mx.js → Tokens-rsE_yDjM.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CJSrm6X0.js → Trigger-8TpwuTGk.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B-TeorSk.js → Trust-sMtZkHPs.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Di7Ymofy.js → UkeySign-BAy2bAdG.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DM1eYNZe.js → VideoEditing-CBeR_DYK.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DvRWkbmR.js → Wallet-BymDnBcq.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CeZ3Y622.js → WebAuthn-DQIjmqNz.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Cq8c4h5j.js → WorkflowEditor-Cj7PB73f.js} +1 -1
- package/src/assets/web-panel/assets/{chat-7-WfML6Q.js → chat-DYnGj4vi.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D6FgCmB-.js → colors-qOLKZNvN.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-ClYV25qi.js → compact-item-BpjCLPcW.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CDhtjdkV.js → createContext-CfakUZVQ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DgtRXlrj.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DZSH5LQd.js → hasIn-C9RW1s7t.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4PMzmOx.js → index-8Ia91vNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-B78X5S22.js → index-B4kS312z.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDX4QU3k.js → index-BE67I0SW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPEYvNvq.js → index-BFOSDeeo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKgS8E_X.js → index-BIz-pX0k.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di9pFrHV.js → index-BJoWi1aR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHeK8I5A.js → index-B_K0YtG2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpzOUiSb.js → index-BdR8XRyF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWRoh3_3.js → index-BfyRXPyV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7pQa2is.js → index-Bl5LBZJM.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZ4zuoCP.js → index-BlxRICmz.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_mMFQ4S.js → index-BxiHBsfU.js} +1 -1
- package/src/assets/web-panel/assets/{index---azBCXl.js → index-C2S1hUWG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ7mrOaB.js → index-CEHyZ77C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxwfFZ1u.js → index-CJZ2noI2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGj1orXm.js → index-COYEuArt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DL6GFJAd.js → index-CVZTLSL1.js} +1 -1
- package/src/assets/web-panel/assets/{index-z-R0KaJS.js → index-CbnJ6FsO.js} +1 -1
- package/src/assets/web-panel/assets/{index-tU6pZ1TP.js → index-CvWFTG56.js} +1 -1
- package/src/assets/web-panel/assets/{index-rCs9VJJp.js → index-D-RzTqlR.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6VWGnwq.js → index-DA80prWe.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0YzTJJO.js → index-DAjszh8P.js} +1 -1
- package/src/assets/web-panel/assets/index-DIGTMmnW.js +1 -0
- package/src/assets/web-panel/assets/{index-DjG82V0v.js → index-DQvVYNoJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLizxxId.js → index-DSWdpR3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sC56w8.js → index-DadPmrxI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlBF_l8m.js → index-DgMJagCq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bj8hZiyL.js → index-DkmLJFE_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrTmxbL8.js → index-DzXYG5YJ.js} +1 -1
- package/src/assets/web-panel/assets/index-Ef5jERRW.js +1 -0
- package/src/assets/web-panel/assets/{index-BUOPjAUM.js → index-JkOMWGMX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmU631Je.js → index-T3bIqK_p.js} +3 -3
- package/src/assets/web-panel/assets/{index-BqOIoEo6.js → index-UiiqS5k2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSjoWPxB.js → index-VYIJmPvJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B13QnrnE.js → index-ZCtDWP2C.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgaF1F0W.js → index-f9yoj84i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Or_McYjX.js → index-lPc7EzUi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGJK8D0l.js → index-m9JeDv6B.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWOkL-8O.js → index-qf0fAus7.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CSdsIGy3.js → initDefaultProps-DgsgQr1H.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Do-AcZV4.js → motion-TeUH7wzx.js} +1 -1
- package/src/assets/web-panel/assets/{move-BmgOoMsi.js → move-DdkIeWQx.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D4Tm7-s9.js → omit-BH_PH6HT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CuWA8-lj.js → pickAttrs-CllCh-Nl.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BSbEF5op.js → placementArrow-BCjE2AzM.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-GIMJwB_9.js → responsiveObserve-BAVGAvRQ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DlZxpIBe.js → slide-D4ZW-Inn.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BZ26LPlh.js → statusUtils-j4pxhmKV.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Yn_3FZ0l.js → styleChecker-DH2SLtPg.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-O_LOE1AB.js → useFlexGapSupport-CYMMs-_Q.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-VFMyQqtl.js → useFs-BOX2ddKh.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-B_hyrGB-.js → usePersonalDataHub-BwcnN5z_.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D4LttGy7.js → vnode-Cwalh7Hj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-KnTK1fjj.js → zoom-B2_q_nbu.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +38 -4
- package/src/commands/init.js +115 -2
- package/src/commands/mcp.js +57 -0
- package/src/commands/memory.js +62 -0
- package/src/commands/session.js +106 -12
- package/src/index.js +10 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/agent-session-export.js +124 -0
- package/src/lib/ide-context.js +62 -0
- package/src/lib/init-ai-refine.js +66 -0
- package/src/lib/json-schema-output.js +181 -0
- package/src/lib/mcp-serve.js +259 -0
- package/src/lib/project-instructions.js +364 -0
- package/src/lib/project-inventory.js +355 -0
- package/src/lib/repl-bang-memorize.js +142 -0
- package/src/lib/repl-completer.js +25 -4
- package/src/lib/repl-rewind.js +107 -0
- package/src/lib/update-notice-refresh.mjs +10 -0
- package/src/lib/update-notice.js +154 -0
- package/src/repl/agent-repl.js +263 -1
- package/src/runtime/agent-core.js +162 -0
- package/src/runtime/system-prompt.js +21 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-LG2nUO5y.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dy9yNmDg.js +0 -1
- package/src/assets/web-panel/assets/Tasks-BjdHjZeb.js +0 -1
- package/src/assets/web-panel/assets/devWarning-O0FVFeZg.js +0 -1
- package/src/assets/web-panel/assets/index--ANIKvhL.js +0 -1
- package/src/assets/web-panel/assets/index-DUfp4rnQ.js +0 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project inventory — offline resource census for `cc init --memory`.
|
|
3
|
+
*
|
|
4
|
+
* Scans the target folder (bounded walk: depth/entry caps, heavy dirs
|
|
5
|
+
* skipped) and produces a deterministic snapshot of what is already there:
|
|
6
|
+
* languages by extension, package manager, npm scripts, workspaces/monorepo
|
|
7
|
+
* layout, notable tool configs (TS/bundlers/docker/CI/lint), test runners and
|
|
8
|
+
* a README summary. `renderMemoryFile` turns that snapshot into a starter
|
|
9
|
+
* `cc.md` project-memory file (the file `cc agent` auto-loads — see
|
|
10
|
+
* project-instructions.js). No LLM, no network: the census is pure I/O and
|
|
11
|
+
* unit-testable via the `deps` seam; reads are explicit UTF-8 (encoding.md).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fsDefault from "fs";
|
|
15
|
+
import pathDefault from "path";
|
|
16
|
+
|
|
17
|
+
export const SKIP_DIRS = new Set([
|
|
18
|
+
"node_modules",
|
|
19
|
+
".git",
|
|
20
|
+
"dist",
|
|
21
|
+
"build",
|
|
22
|
+
"out",
|
|
23
|
+
"coverage",
|
|
24
|
+
".next",
|
|
25
|
+
".nuxt",
|
|
26
|
+
".venv",
|
|
27
|
+
"venv",
|
|
28
|
+
"__pycache__",
|
|
29
|
+
"target",
|
|
30
|
+
".gradle",
|
|
31
|
+
".idea",
|
|
32
|
+
".vscode",
|
|
33
|
+
"Pods",
|
|
34
|
+
"DerivedData",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_MAX_DEPTH = 4;
|
|
38
|
+
export const DEFAULT_MAX_ENTRIES = 20000;
|
|
39
|
+
|
|
40
|
+
const EXT_LANG = {
|
|
41
|
+
".js": "JavaScript",
|
|
42
|
+
".cjs": "JavaScript",
|
|
43
|
+
".mjs": "JavaScript",
|
|
44
|
+
".ts": "TypeScript",
|
|
45
|
+
".tsx": "TypeScript",
|
|
46
|
+
".jsx": "JavaScript",
|
|
47
|
+
".vue": "Vue",
|
|
48
|
+
".py": "Python",
|
|
49
|
+
".java": "Java",
|
|
50
|
+
".kt": "Kotlin",
|
|
51
|
+
".kts": "Kotlin",
|
|
52
|
+
".swift": "Swift",
|
|
53
|
+
".go": "Go",
|
|
54
|
+
".rs": "Rust",
|
|
55
|
+
".rb": "Ruby",
|
|
56
|
+
".php": "PHP",
|
|
57
|
+
".cs": "C#",
|
|
58
|
+
".c": "C",
|
|
59
|
+
".h": "C/C++ header",
|
|
60
|
+
".cpp": "C++",
|
|
61
|
+
".cc": "C++",
|
|
62
|
+
".m": "Objective-C",
|
|
63
|
+
".sql": "SQL",
|
|
64
|
+
".sh": "Shell",
|
|
65
|
+
".ps1": "PowerShell",
|
|
66
|
+
".md": "Markdown",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const CONFIG_MARKERS = [
|
|
70
|
+
["tsconfig.json", "TypeScript"],
|
|
71
|
+
["vite.config.js", "Vite"],
|
|
72
|
+
["vite.config.ts", "Vite"],
|
|
73
|
+
["webpack.config.js", "Webpack"],
|
|
74
|
+
["rollup.config.js", "Rollup"],
|
|
75
|
+
["next.config.js", "Next.js"],
|
|
76
|
+
["nuxt.config.ts", "Nuxt"],
|
|
77
|
+
["docker-compose.yml", "Docker Compose"],
|
|
78
|
+
["docker-compose.yaml", "Docker Compose"],
|
|
79
|
+
["Dockerfile", "Docker"],
|
|
80
|
+
["pom.xml", "Maven"],
|
|
81
|
+
["build.gradle", "Gradle"],
|
|
82
|
+
["build.gradle.kts", "Gradle (Kotlin DSL)"],
|
|
83
|
+
["settings.gradle.kts", "Gradle (Kotlin DSL)"],
|
|
84
|
+
["Cargo.toml", "Cargo / Rust"],
|
|
85
|
+
["go.mod", "Go modules"],
|
|
86
|
+
["pyproject.toml", "Python (pyproject)"],
|
|
87
|
+
["requirements.txt", "Python (requirements)"],
|
|
88
|
+
["pubspec.yaml", "Flutter/Dart"],
|
|
89
|
+
[".eslintrc.json", "ESLint"],
|
|
90
|
+
["eslint.config.js", "ESLint"],
|
|
91
|
+
[".prettierrc", "Prettier"],
|
|
92
|
+
["vitest.config.js", "Vitest"],
|
|
93
|
+
["vitest.config.ts", "Vitest"],
|
|
94
|
+
["jest.config.js", "Jest"],
|
|
95
|
+
["playwright.config.ts", "Playwright"],
|
|
96
|
+
["electron-builder.yml", "electron-builder"],
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const LOCKFILE_PM = [
|
|
100
|
+
["package-lock.json", "npm"],
|
|
101
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
102
|
+
["yarn.lock", "yarn"],
|
|
103
|
+
["bun.lockb", "bun"],
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
function resolveDeps(opts) {
|
|
107
|
+
return {
|
|
108
|
+
fs: opts.deps?.fs || fsDefault,
|
|
109
|
+
path: opts.deps?.path || pathDefault,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readJson(fs, p) {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function readText(fs, p) {
|
|
122
|
+
try {
|
|
123
|
+
return fs.readFileSync(p, "utf-8");
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** First markdown paragraph after the title — a cheap README synopsis. */
|
|
130
|
+
export function readmeSynopsis(text, maxChars = 400) {
|
|
131
|
+
if (!text) return null;
|
|
132
|
+
const lines = String(text).split(/\r?\n/);
|
|
133
|
+
const out = [];
|
|
134
|
+
let started = false;
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const t = line.trim();
|
|
137
|
+
if (!t) {
|
|
138
|
+
if (started) break;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (/^#/.test(t) || /^!\[/.test(t) || /^<.*>$/.test(t) || /^\[!\[/.test(t))
|
|
142
|
+
continue; // headings / badges / bare HTML
|
|
143
|
+
out.push(t);
|
|
144
|
+
started = true;
|
|
145
|
+
}
|
|
146
|
+
const para = out.join(" ").trim();
|
|
147
|
+
if (!para) return null;
|
|
148
|
+
return para.length > maxChars ? `${para.slice(0, maxChars)}…` : para;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Walk the tree (bounded) counting files per language and remembering
|
|
153
|
+
* top-level directories with file counts.
|
|
154
|
+
*/
|
|
155
|
+
function census(root, { fs, path, maxDepth, maxEntries }) {
|
|
156
|
+
const languages = new Map(); // lang -> count
|
|
157
|
+
const topDirs = new Map(); // top-level dir -> file count
|
|
158
|
+
let entries = 0;
|
|
159
|
+
let truncated = false;
|
|
160
|
+
|
|
161
|
+
const walk = (dir, depth, top) => {
|
|
162
|
+
if (entries >= maxEntries) {
|
|
163
|
+
truncated = true;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let list;
|
|
167
|
+
try {
|
|
168
|
+
list = fs.readdirSync(dir, { withFileTypes: true });
|
|
169
|
+
} catch {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
for (const e of list) {
|
|
173
|
+
if (entries >= maxEntries) {
|
|
174
|
+
truncated = true;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
entries += 1;
|
|
178
|
+
if (e.isDirectory()) {
|
|
179
|
+
if (SKIP_DIRS.has(e.name) || e.name.startsWith(".")) continue;
|
|
180
|
+
if (depth < maxDepth) {
|
|
181
|
+
walk(path.join(dir, e.name), depth + 1, top || e.name);
|
|
182
|
+
}
|
|
183
|
+
} else if (e.isFile()) {
|
|
184
|
+
if (top) topDirs.set(top, (topDirs.get(top) || 0) + 1);
|
|
185
|
+
const ext = path.extname(e.name).toLowerCase();
|
|
186
|
+
const lang = EXT_LANG[ext];
|
|
187
|
+
if (lang) languages.set(lang, (languages.get(lang) || 0) + 1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
walk(root, 0, null);
|
|
192
|
+
return { languages, topDirs, truncated };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Inventory the folder. Pure data out — rendering is separate.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} cwd
|
|
199
|
+
* @param {object} [opts] { deps, maxDepth, maxEntries }
|
|
200
|
+
*/
|
|
201
|
+
export function inventoryProject(cwd, opts = {}) {
|
|
202
|
+
const { fs, path } = resolveDeps(opts);
|
|
203
|
+
const root = path.resolve(cwd);
|
|
204
|
+
const maxDepth = Number.isFinite(opts.maxDepth)
|
|
205
|
+
? opts.maxDepth
|
|
206
|
+
: DEFAULT_MAX_DEPTH;
|
|
207
|
+
const maxEntries = Number.isFinite(opts.maxEntries)
|
|
208
|
+
? opts.maxEntries
|
|
209
|
+
: DEFAULT_MAX_ENTRIES;
|
|
210
|
+
|
|
211
|
+
const pkg = readJson(fs, path.join(root, "package.json"));
|
|
212
|
+
|
|
213
|
+
let packageManager = null;
|
|
214
|
+
for (const [lock, pm] of LOCKFILE_PM) {
|
|
215
|
+
if (fs.existsSync(path.join(root, lock))) {
|
|
216
|
+
packageManager = pm;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const configs = [];
|
|
222
|
+
for (const [file, label] of CONFIG_MARKERS) {
|
|
223
|
+
if (fs.existsSync(path.join(root, file)) && !configs.includes(label)) {
|
|
224
|
+
configs.push(label);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let ciWorkflows = 0;
|
|
229
|
+
try {
|
|
230
|
+
ciWorkflows = fs
|
|
231
|
+
.readdirSync(path.join(root, ".github", "workflows"))
|
|
232
|
+
.filter((f) => /\.ya?ml$/.test(f)).length;
|
|
233
|
+
} catch {
|
|
234
|
+
/* no CI dir */
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const readme =
|
|
238
|
+
readText(fs, path.join(root, "README.md")) ||
|
|
239
|
+
readText(fs, path.join(root, "readme.md"));
|
|
240
|
+
|
|
241
|
+
const { languages, topDirs, truncated } = census(root, {
|
|
242
|
+
fs,
|
|
243
|
+
path,
|
|
244
|
+
maxDepth,
|
|
245
|
+
maxEntries,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const workspaces = Array.isArray(pkg?.workspaces)
|
|
249
|
+
? pkg.workspaces
|
|
250
|
+
: Array.isArray(pkg?.workspaces?.packages)
|
|
251
|
+
? pkg.workspaces.packages
|
|
252
|
+
: [];
|
|
253
|
+
|
|
254
|
+
const scripts = pkg?.scripts ? Object.entries(pkg.scripts) : [];
|
|
255
|
+
|
|
256
|
+
const existingMemory = ["cc.md", "CLAUDE.md", "AGENTS.md"].filter((f) =>
|
|
257
|
+
fs.existsSync(path.join(root, f)),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
root,
|
|
262
|
+
name: pkg?.name || path.basename(root),
|
|
263
|
+
description: pkg?.description || null,
|
|
264
|
+
synopsis: readmeSynopsis(readme),
|
|
265
|
+
packageManager,
|
|
266
|
+
configs,
|
|
267
|
+
ciWorkflows,
|
|
268
|
+
workspaces,
|
|
269
|
+
scripts,
|
|
270
|
+
languages: [...languages.entries()].sort((a, b) => b[1] - a[1]),
|
|
271
|
+
topDirs: [...topDirs.entries()].sort((a, b) => b[1] - a[1]),
|
|
272
|
+
truncated,
|
|
273
|
+
existingMemory,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Render the inventory as a starter cc.md project-memory file. */
|
|
278
|
+
export function renderMemoryFile(inv, opts = {}) {
|
|
279
|
+
const today = opts.date || new Date().toISOString().slice(0, 10);
|
|
280
|
+
const L = [];
|
|
281
|
+
L.push(`# ${inv.name} — Project Memory`);
|
|
282
|
+
L.push("");
|
|
283
|
+
L.push(
|
|
284
|
+
`> Generated by \`cc init --memory\` on ${today} from an inventory of this folder.`,
|
|
285
|
+
);
|
|
286
|
+
L.push(
|
|
287
|
+
"> `cc agent` auto-loads this file as authoritative project context — edit freely.",
|
|
288
|
+
);
|
|
289
|
+
L.push("");
|
|
290
|
+
|
|
291
|
+
// cc.md outranks CLAUDE.md/AGENTS.md in discovery — import any existing
|
|
292
|
+
// memory file so generating cc.md never shadows hand-maintained content.
|
|
293
|
+
const existing = (inv.existingMemory || []).filter((f) => f !== "cc.md");
|
|
294
|
+
if (existing.length) {
|
|
295
|
+
L.push("## Existing project memory (imported)");
|
|
296
|
+
L.push("");
|
|
297
|
+
for (const f of existing) L.push(`@${f}`);
|
|
298
|
+
L.push("");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (inv.synopsis || inv.description) {
|
|
302
|
+
L.push("## Overview");
|
|
303
|
+
L.push("");
|
|
304
|
+
L.push(inv.synopsis || inv.description);
|
|
305
|
+
L.push("");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
L.push("## Stack");
|
|
309
|
+
L.push("");
|
|
310
|
+
if (inv.languages.length) {
|
|
311
|
+
const langs = inv.languages
|
|
312
|
+
.slice(0, 8)
|
|
313
|
+
.map(([lang, n]) => `${lang} (${n})`)
|
|
314
|
+
.join(", ");
|
|
315
|
+
L.push(`- Languages by file count: ${langs}${inv.truncated ? " — scan truncated" : ""}`);
|
|
316
|
+
}
|
|
317
|
+
if (inv.packageManager) L.push(`- Package manager: ${inv.packageManager}`);
|
|
318
|
+
if (inv.configs.length) L.push(`- Tooling: ${inv.configs.join(", ")}`);
|
|
319
|
+
if (inv.ciWorkflows)
|
|
320
|
+
L.push(`- CI: GitHub Actions (${inv.ciWorkflows} workflows)`);
|
|
321
|
+
L.push("");
|
|
322
|
+
|
|
323
|
+
if (inv.scripts.length) {
|
|
324
|
+
L.push("## Commands");
|
|
325
|
+
L.push("");
|
|
326
|
+
const pm = inv.packageManager || "npm";
|
|
327
|
+
for (const [name, cmd] of inv.scripts.slice(0, 12)) {
|
|
328
|
+
L.push(`- \`${pm} run ${name}\` — \`${cmd}\``);
|
|
329
|
+
}
|
|
330
|
+
if (inv.scripts.length > 12) {
|
|
331
|
+
L.push(`- … ${inv.scripts.length - 12} more scripts in package.json`);
|
|
332
|
+
}
|
|
333
|
+
L.push("");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (inv.workspaces.length || inv.topDirs.length) {
|
|
337
|
+
L.push("## Layout");
|
|
338
|
+
L.push("");
|
|
339
|
+
if (inv.workspaces.length) {
|
|
340
|
+
L.push(`- Workspaces: ${inv.workspaces.join(", ")}`);
|
|
341
|
+
}
|
|
342
|
+
for (const [dir, n] of inv.topDirs.slice(0, 10)) {
|
|
343
|
+
L.push(`- \`${dir}/\` — ${n} files`);
|
|
344
|
+
}
|
|
345
|
+
L.push("");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
L.push("## Conventions");
|
|
349
|
+
L.push("");
|
|
350
|
+
L.push("- (add project rules here — code style, commit format, test policy)");
|
|
351
|
+
L.push("");
|
|
352
|
+
return L.join("\n");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export const _deps = { fs: fsDefault, path: pathDefault };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL `!` bash passthrough + `#` quick-memorize (Claude-Code parity).
|
|
3
|
+
*
|
|
4
|
+
* Pure logic for two agent-REPL input prefixes, extracted so it is unit
|
|
5
|
+
* testable without driving readline:
|
|
6
|
+
*
|
|
7
|
+
* - `! <cmd>` — run the shell command immediately (no LLM round-trip) and
|
|
8
|
+
* return a `<bash-input>/<bash-output>` context message so the model sees
|
|
9
|
+
* what happened on the next turn. Windows runs through `cmd.exe /d /s /c`
|
|
10
|
+
* with a `chcp 65001` prefix (encoding.md rule); POSIX through `/bin/sh -c`.
|
|
11
|
+
*
|
|
12
|
+
* - `# <note>` — append a one-line note to the project memory file (cc.md at
|
|
13
|
+
* the git root — the file project-instructions.js auto-loads). Creates the
|
|
14
|
+
* file/`## Notes` section when missing; next sessions pick it up
|
|
15
|
+
* automatically and the caller can also inject it into the live context.
|
|
16
|
+
*
|
|
17
|
+
* All process/fs access goes through `_deps` for tests.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { spawnSync as spawnSyncDefault } from "child_process";
|
|
21
|
+
import fsDefault from "fs";
|
|
22
|
+
import pathDefault from "path";
|
|
23
|
+
import { findProjectRoot } from "./project-instructions.js";
|
|
24
|
+
|
|
25
|
+
export const BANG_TIMEOUT_MS = 120_000;
|
|
26
|
+
export const BANG_MAX_BUFFER = 10 * 1024 * 1024;
|
|
27
|
+
export const BANG_OUTPUT_CAP = 30_000;
|
|
28
|
+
|
|
29
|
+
export const _deps = {
|
|
30
|
+
spawnSync: spawnSyncDefault,
|
|
31
|
+
fs: fsDefault,
|
|
32
|
+
path: pathDefault,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function cap(s) {
|
|
36
|
+
const str = s || "";
|
|
37
|
+
return str.length > BANG_OUTPUT_CAP
|
|
38
|
+
? `${str.slice(0, BANG_OUTPUT_CAP)}\n… [truncated]`
|
|
39
|
+
: str;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** True when the REPL line is a `!` bash passthrough. */
|
|
43
|
+
export function isBangCommand(trimmed) {
|
|
44
|
+
return (
|
|
45
|
+
typeof trimmed === "string" &&
|
|
46
|
+
trimmed.startsWith("!") &&
|
|
47
|
+
trimmed.slice(1).trim().length > 0
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** True when the REPL line is a `#` quick-memorize. */
|
|
52
|
+
export function isMemorizeLine(trimmed) {
|
|
53
|
+
return (
|
|
54
|
+
typeof trimmed === "string" &&
|
|
55
|
+
trimmed.startsWith("#") &&
|
|
56
|
+
trimmed.slice(1).trim().length > 0
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Run a `!` command synchronously.
|
|
62
|
+
*
|
|
63
|
+
* @returns {{ cmd, stdout, stderr, exitCode, error, contextMessage }}
|
|
64
|
+
* `contextMessage` is ready to push as a user-role message.
|
|
65
|
+
*/
|
|
66
|
+
export function runBangCommand(line, opts = {}) {
|
|
67
|
+
const spawnSync = opts.deps?.spawnSync || _deps.spawnSync;
|
|
68
|
+
const cwd = opts.cwd || process.cwd();
|
|
69
|
+
const isWin =
|
|
70
|
+
opts.platform != null ? opts.platform === "win32" : process.platform === "win32";
|
|
71
|
+
const cmd = String(line).replace(/^!/, "").trim();
|
|
72
|
+
|
|
73
|
+
const res = isWin
|
|
74
|
+
? spawnSync("cmd.exe", ["/d", "/s", "/c", `chcp 65001 >nul && ${cmd}`], {
|
|
75
|
+
encoding: "utf-8",
|
|
76
|
+
timeout: BANG_TIMEOUT_MS,
|
|
77
|
+
maxBuffer: BANG_MAX_BUFFER,
|
|
78
|
+
cwd,
|
|
79
|
+
})
|
|
80
|
+
: spawnSync("/bin/sh", ["-c", cmd], {
|
|
81
|
+
encoding: "utf-8",
|
|
82
|
+
timeout: BANG_TIMEOUT_MS,
|
|
83
|
+
maxBuffer: BANG_MAX_BUFFER,
|
|
84
|
+
cwd,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const exitCode = res.status == null ? (res.error ? -1 : 0) : res.status;
|
|
88
|
+
const stdout = cap(res.stdout);
|
|
89
|
+
const stderr = cap(res.stderr);
|
|
90
|
+
const body = [stdout, stderr].filter(Boolean).join("\n");
|
|
91
|
+
return {
|
|
92
|
+
cmd,
|
|
93
|
+
stdout,
|
|
94
|
+
stderr,
|
|
95
|
+
exitCode,
|
|
96
|
+
error: res.error || null,
|
|
97
|
+
contextMessage: {
|
|
98
|
+
role: "user",
|
|
99
|
+
content: `<bash-input>${cmd}</bash-input>\n<bash-output exit-code="${exitCode}">\n${body}\n</bash-output>`,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Append a `#` note to the project cc.md (created at the git root — falls
|
|
106
|
+
* back to cwd outside a repo). Inserts under a `## Notes` heading, creating
|
|
107
|
+
* file/section as needed.
|
|
108
|
+
*
|
|
109
|
+
* @returns {{ target, line, created }}
|
|
110
|
+
*/
|
|
111
|
+
export function appendMemoryNote(rawLine, opts = {}) {
|
|
112
|
+
const fs = opts.deps?.fs || _deps.fs;
|
|
113
|
+
const path = opts.deps?.path || _deps.path;
|
|
114
|
+
const cwd = opts.cwd || process.cwd();
|
|
115
|
+
const note = String(rawLine).replace(/^#/, "").trim();
|
|
116
|
+
const stamp = opts.date || new Date().toISOString().slice(0, 10);
|
|
117
|
+
|
|
118
|
+
const root =
|
|
119
|
+
findProjectRoot(cwd, { deps: { fs, path } }) || path.resolve(cwd);
|
|
120
|
+
const target = opts.target || path.join(root, "cc.md");
|
|
121
|
+
const line = `- ${note} _(noted ${stamp})_`;
|
|
122
|
+
|
|
123
|
+
let text = null;
|
|
124
|
+
try {
|
|
125
|
+
text = fs.readFileSync(target, "utf-8");
|
|
126
|
+
} catch {
|
|
127
|
+
/* file does not exist yet */
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let created = false;
|
|
131
|
+
if (text == null) {
|
|
132
|
+
text = `# Project Memory\n\n## Notes\n\n${line}\n`;
|
|
133
|
+
created = true;
|
|
134
|
+
} else if (/^## Notes\s*$/m.test(text)) {
|
|
135
|
+
// insert right after the heading (keeps newest notes on top)
|
|
136
|
+
text = text.replace(/^## Notes\s*$/m, (m) => `${m}\n\n${line}`);
|
|
137
|
+
} else {
|
|
138
|
+
text = `${text.trimEnd()}\n\n## Notes\n\n${line}\n`;
|
|
139
|
+
}
|
|
140
|
+
fs.writeFileSync(target, text, "utf-8");
|
|
141
|
+
return { target, line, note, created };
|
|
142
|
+
}
|
|
@@ -78,9 +78,12 @@ export function fileCandidates(prefix, { cwd = process.cwd(), deps } = {}) {
|
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Build a readline completer. Returns `completer(line)` → `[hits, replaced]`
|
|
81
|
-
* per the readline contract
|
|
81
|
+
* per the readline contract. Completes `@path` tokens anywhere in the line
|
|
82
|
+
* and `/command` names at line start (while the command token is still being
|
|
83
|
+
* typed); everything else completes to nothing.
|
|
82
84
|
*
|
|
83
|
-
* @param {object} opts { cwd?, getIdeOpenFiles?: () => Promise<string[]>,
|
|
85
|
+
* @param {object} opts { cwd?, getIdeOpenFiles?: () => Promise<string[]>,
|
|
86
|
+
* slashCommands?: string[], deps? }
|
|
84
87
|
*/
|
|
85
88
|
export function makeAtCompleter(opts = {}) {
|
|
86
89
|
const cwd = opts.cwd || process.cwd();
|
|
@@ -104,8 +107,12 @@ export function makeAtCompleter(opts = {}) {
|
|
|
104
107
|
.map((f) => {
|
|
105
108
|
const rel = path.relative(cwd, f);
|
|
106
109
|
// Keep workspace files relative (the natural @ref form);
|
|
107
|
-
// out-of-workspace files keep their absolute path.
|
|
108
|
-
|
|
110
|
+
// out-of-workspace files keep their absolute path. On
|
|
111
|
+
// Windows a cross-drive relative() returns an *absolute*
|
|
112
|
+
// path (no ".." prefix), so isAbsolute must also gate it.
|
|
113
|
+
return rel && !rel.startsWith("..") && !path.isAbsolute(rel)
|
|
114
|
+
? fwd(rel)
|
|
115
|
+
: fwd(f);
|
|
109
116
|
})
|
|
110
117
|
: [];
|
|
111
118
|
ideFetchedAt = now();
|
|
@@ -118,7 +125,21 @@ export function makeAtCompleter(opts = {}) {
|
|
|
118
125
|
});
|
|
119
126
|
};
|
|
120
127
|
|
|
128
|
+
const slashCommands = Array.isArray(opts.slashCommands)
|
|
129
|
+
? [...opts.slashCommands].sort()
|
|
130
|
+
: [];
|
|
131
|
+
|
|
121
132
|
const completer = (line) => {
|
|
133
|
+
// `/command` completion (Claude-Code parity): only while typing the
|
|
134
|
+
// command token itself — once a space follows, args are the user's.
|
|
135
|
+
const slash = /^\/([A-Za-z_-]*)$/.exec(line);
|
|
136
|
+
if (slash && slashCommands.length) {
|
|
137
|
+
const pref = `/${slash[1].toLowerCase()}`;
|
|
138
|
+
const hits = slashCommands.filter((c) =>
|
|
139
|
+
c.toLowerCase().startsWith(pref),
|
|
140
|
+
);
|
|
141
|
+
return [hits, line];
|
|
142
|
+
}
|
|
122
143
|
const at = extractAtPrefix(line);
|
|
123
144
|
if (!at) return [[], line];
|
|
124
145
|
refreshIde(); // async top-up for the NEXT tab; this one uses the cache
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL conversation rewind — Claude-Code double-Esc parity (v1: conversation
|
|
3
|
+
* state; file state stays on `cc checkpoint restore`, hinted alongside).
|
|
4
|
+
*
|
|
5
|
+
* Pure helpers over the REPL's live `messages` array so the picker logic is
|
|
6
|
+
* unit-testable without readline:
|
|
7
|
+
* - listUserTurns(): newest-first numbered list of user messages
|
|
8
|
+
* - rewindToTurn(): truncate the conversation BACK TO BEFORE turn #n and
|
|
9
|
+
* return the original text so the caller can prefill the input line
|
|
10
|
+
* (edit-and-resend, like Claude Code's rewind).
|
|
11
|
+
*
|
|
12
|
+
* Trigger surfaces (wired in agent-repl): `/rewind` lists, `/rewind <n>`
|
|
13
|
+
* rewinds, and a double-Esc while idle prints the same list as a shortcut.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_LIST_LIMIT = 10;
|
|
17
|
+
export const PREVIEW_CHARS = 60;
|
|
18
|
+
|
|
19
|
+
function previewOf(content) {
|
|
20
|
+
const text =
|
|
21
|
+
typeof content === "string" ? content : JSON.stringify(content || "");
|
|
22
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
23
|
+
return flat.length > PREVIEW_CHARS
|
|
24
|
+
? `${flat.slice(0, PREVIEW_CHARS)}…`
|
|
25
|
+
: flat;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Newest-first user turns.
|
|
30
|
+
* @returns {Array<{n:number, index:number, preview:string, content:any}>}
|
|
31
|
+
* n is the 1-based pick number (1 = most recent user message).
|
|
32
|
+
*/
|
|
33
|
+
export function listUserTurns(messages, { limit = DEFAULT_LIST_LIMIT } = {}) {
|
|
34
|
+
const turns = [];
|
|
35
|
+
for (let i = (messages || []).length - 1; i >= 0; i--) {
|
|
36
|
+
const m = messages[i];
|
|
37
|
+
if (!m || m.role !== "user") continue;
|
|
38
|
+
turns.push({
|
|
39
|
+
n: turns.length + 1,
|
|
40
|
+
index: i,
|
|
41
|
+
preview: previewOf(m.content),
|
|
42
|
+
content: m.content,
|
|
43
|
+
});
|
|
44
|
+
if (turns.length >= limit) break;
|
|
45
|
+
}
|
|
46
|
+
return turns;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Rewind the conversation to BEFORE the picked user turn (mutates `messages`
|
|
51
|
+
* in place — everything from that user message onward is dropped).
|
|
52
|
+
*
|
|
53
|
+
* @param {Array} messages live conversation array
|
|
54
|
+
* @param {number} n 1-based pick from listUserTurns
|
|
55
|
+
* @returns {{ removed:number, text:string|null }|null} null on bad pick;
|
|
56
|
+
* `text` is the original user text when it was a plain string
|
|
57
|
+
* (caller prefills the input line with it).
|
|
58
|
+
*/
|
|
59
|
+
export function rewindToTurn(messages, n) {
|
|
60
|
+
const turns = listUserTurns(messages, { limit: 1000 });
|
|
61
|
+
const turn = turns.find((t) => t.n === Number(n));
|
|
62
|
+
if (!turn) return null;
|
|
63
|
+
const removed = messages.length - turn.index;
|
|
64
|
+
messages.splice(turn.index);
|
|
65
|
+
return {
|
|
66
|
+
removed,
|
|
67
|
+
text: typeof turn.content === "string" ? turn.content : null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Offline extractive recap for a resumed conversation ("where were we") —
|
|
73
|
+
* no LLM call: turn counts + last ask + last reply previews.
|
|
74
|
+
* @returns {string[]|null} lines to print, or null when nothing to recap.
|
|
75
|
+
*/
|
|
76
|
+
export function buildResumeRecap(messages, { previewChars = 160 } = {}) {
|
|
77
|
+
const list = messages || [];
|
|
78
|
+
const flat = (c) =>
|
|
79
|
+
(typeof c === "string" ? c : JSON.stringify(c || ""))
|
|
80
|
+
.replace(/\s+/g, " ")
|
|
81
|
+
.trim();
|
|
82
|
+
const cap = (s) =>
|
|
83
|
+
s.length > previewChars ? `${s.slice(0, previewChars)}…` : s;
|
|
84
|
+
const lastOf = (role) => {
|
|
85
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
86
|
+
if (list[i]?.role === role) return list[i].content;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
};
|
|
90
|
+
const users = list.filter((m) => m?.role === "user").length;
|
|
91
|
+
const assistants = list.filter((m) => m?.role === "assistant").length;
|
|
92
|
+
if (!users && !assistants) return null;
|
|
93
|
+
const lines = [`${users} user / ${assistants} assistant turns`];
|
|
94
|
+
const lu = lastOf("user");
|
|
95
|
+
if (lu) lines.push(`last ask : ${cap(flat(lu))}`);
|
|
96
|
+
const la = lastOf("assistant");
|
|
97
|
+
if (la) lines.push(`last reply: ${cap(flat(la))}`);
|
|
98
|
+
return lines;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Render the picker list (shared by /rewind and double-Esc). */
|
|
102
|
+
export function renderTurnList(turns) {
|
|
103
|
+
if (!turns.length) return " (no user turns yet)";
|
|
104
|
+
return turns
|
|
105
|
+
.map((t) => ` ${String(t.n).padStart(2)}. ${t.preview}`)
|
|
106
|
+
.join("\n");
|
|
107
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detached cache refresher for the startup update notice.
|
|
3
|
+
* Spawned unref'd by update-notice.js; argv[2] = cache file path.
|
|
4
|
+
* Exits quietly on any failure — the notice is strictly best-effort.
|
|
5
|
+
*/
|
|
6
|
+
import { refreshCacheOnce } from "./update-notice.js";
|
|
7
|
+
|
|
8
|
+
refreshCacheOnce({ cacheFile: process.argv[2] })
|
|
9
|
+
.catch(() => {})
|
|
10
|
+
.finally(() => process.exit(0));
|