chainlesschain 0.162.39 → 0.162.40
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-CPmKv82o.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-XHoOmsbP.js → ActionButton-BNDYY7Qd.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics--xaFkDnL.js → Analytics-BgCMCOsk.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CSa3FBn8.js → AppLayout-Dv4oJcqS.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-ONWXiAwG.js → Audit-5iV3yrGa.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CKOPNdgy.js → Backup-CHDhnbzF.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-PNj4uVqg.js → BaseInput-B6reFkra.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CZCulyXV.js → Chat-DwS5YyE2.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CjuJpfpV.js → ChatBubbleRenderer-CqXa87Hw.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-jvy668lD.js → Checkbox-yiW0M4RE.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DhUebOQD.js → Codegen-DoiVuD_g.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BiBvHfdT.js → Col-BVASLexk.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CmEdEti-.js → Community-D6KQ7JoU.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CtxpF4R5.js → Compact-Bl9Uhb6v.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CvPTrTAJ.js → Compliance-MM31-dba.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BMafGHjy.js → Cowork-PjU_1ieD.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-mdg_4TR1.js → Cron-DorNtPZL.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain--dGxsUvn.js → Crosschain-Bm5ts2Kw.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C9oKaCml.js → DID-7Y3jlFdY.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CoGxKMvy.js → Dashboard-1oE532bG.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CDDu3ZZ3.js → Dropdown-hJlOPs0s.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Dy7_r9Ag.js → EmailListRenderer-BEqJxKaO.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CNg6vImJ.js → FamilyGuardDashboard-BvCGwB6X.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CT61bf3u.js → Federation-CsXI72e5.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CSLRnXhg.js → FormItemContext-Dh9SMul-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CZ4NE5N3.js → GenericCardRenderer-9edWzrtG.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DBuOma3L.js → Git-ZYhNL8Xk.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BTU_SEef.js → Governance-BwAdp8QA.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-47SAmLC_.js → Inference-5C-M1XsH.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DCrK5vP4.js → KnowledgeGraph-zFAi-zCi.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BqiDxdav.js → Logs-BZsEdbgE.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CReUjsDt.js → Marketplace-BP6gErRK.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-agZBV3p8.js → McpTools-CXVzoLrd.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-C_YvUtyS.js → Memory-BIpChb4-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-41fP1Tui.js → MobileBridge-B4O7wDT8.js} +3 -3
- package/src/assets/web-panel/assets/MobileProjects-7VPMoHus.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-JFJCXUnk.js → Mtc-BTmEyTM5.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-BHNpPZC9.js → MtcAudit-CsbG9LlV.js} +6 -6
- package/src/assets/web-panel/assets/{Multisig-DuCRumiz.js → Multisig-CL8yoGon.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DK-g0fKY.js → NLProgramming-C2cIlIp_.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BSMcjsPf.js → Notes-7aBk_n_M.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-9ouC118H.js → NotificationSettings-BuhQk4rJ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-LG2nUO5y.js → OrderTableRenderer-mqMFZu0x.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DSV7oRnR.js → Organization-CAdq-170.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DVkkORc3.js → Overflow--Xn0E787.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BXXjkkQD.js → P2P-DYt3YAXI.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-O5hNnLTP.js → PdhVaultBrowser-Bgb_v8WN.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D_s0H5Av.js → Permissions-DoFlmoaW.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CzMDrwUi.js → PersonalDataHub-C-FJB3a0.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-i9krLVTL.js → Pipeline-3bL2RzzL.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-cMQcj9I8.js → Privacy-c4igYUCF.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Ca_l7avo.js → ProjectInit-C0QS1UPR.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BkaIhd6b.js → ProjectSettings-CkYC0xkE.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Dy9yNmDg.js → Projects-Di17SYft.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D0nzYiqz.js → Providers-41NySsLt.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bzzr9d0f.js → QuickAsk-DHq9pD7z.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-C-UFbQnX.js → Recommend-CLjgFPLv.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BKMIKO5F.js → Reputation-EIrgErm3.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Bs7htK1T.js → Row-GAvKzKH7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-v6MdULUh.js → RssFeed-CYCNsVmD.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DlRWYzvz.js → Search-DWOE32k8.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DXWO37xX.js → Security-Dgh8Jevn.js} +4 -4
- package/src/assets/web-panel/assets/{Services-C2tWA-O0.js → Services-BxdgP67N.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Q8pIYY4a.js → Skeleton-D-xT4ZkA.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D7XBlErj.js → Skills-BKN4lfSa.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CiyMVPJ1.js → Sla--N1TudpS.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CadCeeiR.js → SpeechSettings-B0vfJpEh.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DzNAUhQq.js → SyncSettings-BuBAbPAh.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BjdHjZeb.js → Tasks-4XugjJ87.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DfgEpUa4.js → Templates-DI2giLgc.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C8ajkuYi.js → Tenant-BiTWvm0g.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B9rHwQQx.js → Terminal-vV6AWGDi.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D1ZVNezX.js → TimelineRenderer-BmgzKdAp.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CAkED4mx.js → Tokens-Nvupdm6p.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CJSrm6X0.js → Trigger-DRfR77WJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B-TeorSk.js → Trust-De0Jal_6.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Di7Ymofy.js → UkeySign-Dzo4-VAM.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DM1eYNZe.js → VideoEditing-hg2ytiJB.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DvRWkbmR.js → Wallet--bU5-gRh.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CeZ3Y622.js → WebAuthn-DZptt-PV.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Cq8c4h5j.js → WorkflowEditor-Dy9223bY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-7-WfML6Q.js → chat-DaxGeI9w.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D6FgCmB-.js → colors-Cu2VEci3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-ClYV25qi.js → compact-item-CGolhyJq.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CDhtjdkV.js → createContext-DY7EFhkD.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DV2BNd59.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DZSH5LQd.js → hasIn-Bpc-NoFN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKgS8E_X.js → index-1D4sfByw.js} +1 -1
- package/src/assets/web-panel/assets/{index-Or_McYjX.js → index-8h9y5S6X.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlBF_l8m.js → index-BP9P6chP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSjoWPxB.js → index-BQ2z6Ky5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjG82V0v.js → index-BRAgl2J_.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_mMFQ4S.js → index-BTvwiqJE.js} +1 -1
- package/src/assets/web-panel/assets/index-BZqtTmyG.js +1 -0
- package/src/assets/web-panel/assets/{index-D0YzTJJO.js → index-BjfxHEmX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgaF1F0W.js → index-BlHq81Ow.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bj8hZiyL.js → index-Bn5gM9Oy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPEYvNvq.js → index-Bz83ngs0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DL6GFJAd.js → index-C-Hkl_2G.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ7mrOaB.js → index-C0_zeYnx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrTmxbL8.js → index-C2RpsAiO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDX4QU3k.js → index-CBSk_VrT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZ4zuoCP.js → index-CFAnEzRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6VWGnwq.js → index-CGqeHu_F.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7pQa2is.js → index-CJFYF8F9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWOkL-8O.js → index-CLNqZF55.js} +1 -1
- package/src/assets/web-panel/assets/{index-tU6pZ1TP.js → index-CaKXhpEu.js} +1 -1
- package/src/assets/web-panel/assets/{index---azBCXl.js → index-Ciw5-X1B.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLizxxId.js → index-D0GN5tdM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHeK8I5A.js → index-D63ObMdQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-z-R0KaJS.js → index-DAov-rJR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUOPjAUM.js → index-DElatOQ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di9pFrHV.js → index-DNX81oSR.js} +1 -1
- package/src/assets/web-panel/assets/index-DUpwdJt9.js +1 -0
- package/src/assets/web-panel/assets/{index-B13QnrnE.js → index-DZ4Vm8dQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sC56w8.js → index-DexYD87j.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxwfFZ1u.js → index-DfKmAEtE.js} +1 -1
- package/src/assets/web-panel/assets/{index-B78X5S22.js → index-DldaToUA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpzOUiSb.js → index-DpRSzAFl.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4PMzmOx.js → index-DxXkr-NS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqOIoEo6.js → index-RumxOD0S.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGJK8D0l.js → index-VBRPxZeE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWRoh3_3.js → index-eF9RV_4c.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGj1orXm.js → index-lfP8sdzB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmU631Je.js → index-oJQgRCrR.js} +3 -3
- package/src/assets/web-panel/assets/{index-rCs9VJJp.js → index-rkm7dHwG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CSdsIGy3.js → initDefaultProps-CkJZfCo8.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Do-AcZV4.js → motion-BerbusV1.js} +1 -1
- package/src/assets/web-panel/assets/{move-BmgOoMsi.js → move-DyRzKPD4.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D4Tm7-s9.js → omit-CCdrTUAs.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CuWA8-lj.js → pickAttrs-mVDeZx2m.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BSbEF5op.js → placementArrow-Bb_-Fs_o.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-GIMJwB_9.js → responsiveObserve-C6TMj1R_.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DlZxpIBe.js → slide-CdCNsy1J.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BZ26LPlh.js → statusUtils-Ccxd1rFd.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Yn_3FZ0l.js → styleChecker-3IL-yw1V.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-O_LOE1AB.js → useFlexGapSupport-CH8DjUHl.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-VFMyQqtl.js → useFs-Cn9nE2sp.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-B_hyrGB-.js → usePersonalDataHub-BPyT0HO7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D4LttGy7.js → vnode-Mfm7vy07.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-KnTK1fjj.js → zoom-CTpAiAE9.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/init.js +84 -2
- package/src/commands/session.js +36 -12
- package/src/index.js +10 -0
- package/src/lib/agent-session-export.js +124 -0
- package/src/lib/ide-context.js +62 -0
- package/src/lib/project-instructions.js +275 -0
- package/src/lib/project-inventory.js +355 -0
- package/src/lib/repl-bang-memorize.js +142 -0
- package/src/lib/repl-completer.js +17 -2
- package/src/lib/update-notice-refresh.mjs +10 -0
- package/src/lib/update-notice.js +154 -0
- package/src/repl/agent-repl.js +118 -0
- package/src/runtime/agent-core.js +162 -0
- package/src/runtime/system-prompt.js +21 -1
- package/src/assets/web-panel/assets/MobileProjects-BkqLvGfL.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,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-memory loader — file-based project instructions for `cc agent`
|
|
3
|
+
* (Claude-Code CLAUDE.md-hierarchy parity, with our own file name).
|
|
4
|
+
*
|
|
5
|
+
* The primary instruction file is **`cc.md`** (ChainlessChain branding);
|
|
6
|
+
* `CLAUDE.md` and `AGENTS.md` are accepted as compatibility fallbacks so any
|
|
7
|
+
* repo that already carries Claude-Code/agent memory works with zero setup.
|
|
8
|
+
*
|
|
9
|
+
* Discovery order (first existing name wins per location):
|
|
10
|
+
*
|
|
11
|
+
* 1. user scope : `~/.chainlesschain/cc.md`, else `~/.claude/CLAUDE.md`
|
|
12
|
+
* 2. project scope: per directory from <git-root> down to <cwd> —
|
|
13
|
+
* `cc.md` → `CLAUDE.md` → `AGENTS.md`
|
|
14
|
+
* (root-first, so deeper files refine shallower ones)
|
|
15
|
+
* 3. local scope : `cc.local.md` → `CLAUDE.local.md` next to each project
|
|
16
|
+
* file (gitignored personal notes)
|
|
17
|
+
*
|
|
18
|
+
* `@path` import lines inside an instruction file pull in the referenced file
|
|
19
|
+
* (resolved relative to the importing file; `~/` works too), recursively up to
|
|
20
|
+
* MAX_IMPORT_DEPTH with cycle protection. Tokens inside fenced code blocks and
|
|
21
|
+
* tokens that don't resolve to a real file (npm scopes like `@scope/pkg`,
|
|
22
|
+
* emails) are ignored silently.
|
|
23
|
+
*
|
|
24
|
+
* Loading is fail-open: any I/O error yields an empty block — composing the
|
|
25
|
+
* system prompt must never crash because of a bad memory file. All fs access
|
|
26
|
+
* goes through an injectable `deps` seam (project `_deps` philosophy) and all
|
|
27
|
+
* reads are explicit UTF-8 (encoding.md rule).
|
|
28
|
+
*
|
|
29
|
+
* Disable globally with `CC_PROJECT_MEMORY=0`, per-call with
|
|
30
|
+
* `projectMemory: false` on composeSystemPrompt.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import fsDefault from "fs";
|
|
34
|
+
import pathDefault from "path";
|
|
35
|
+
import osDefault from "os";
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_MAX_FILE_BYTES = 48 * 1024; // per instruction/import file
|
|
38
|
+
export const DEFAULT_MAX_TOTAL_BYTES = 192 * 1024; // whole block budget
|
|
39
|
+
export const MAX_IMPORT_DEPTH = 5;
|
|
40
|
+
|
|
41
|
+
/** Per-directory project file names, first match wins. */
|
|
42
|
+
export const PROJECT_FILE_NAMES = ["cc.md", "CLAUDE.md", "AGENTS.md"];
|
|
43
|
+
/** Local (gitignored) companion names, first match wins. */
|
|
44
|
+
export const LOCAL_FILE_NAMES = ["cc.local.md", "CLAUDE.local.md"];
|
|
45
|
+
|
|
46
|
+
// Same boundary rule as file-ref-expander: `@` at start / after whitespace or
|
|
47
|
+
// an opening bracket-quote, so emails and decorative @ never match.
|
|
48
|
+
const IMPORT_TOKEN_RE = /(^|[\s("'`[{])@([^\s"'`)\]}]+)/g;
|
|
49
|
+
|
|
50
|
+
function resolveDeps(opts) {
|
|
51
|
+
return {
|
|
52
|
+
fs: opts.deps?.fs || fsDefault,
|
|
53
|
+
path: opts.deps?.path || pathDefault,
|
|
54
|
+
os: opts.deps?.os || osDefault,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isFile(fs, p) {
|
|
59
|
+
try {
|
|
60
|
+
return fs.statSync(p).isFile();
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** First existing candidate among `names` inside `dir`, or null. */
|
|
67
|
+
function firstExisting(fs, path, dir, names) {
|
|
68
|
+
for (const name of names) {
|
|
69
|
+
const p = path.join(dir, name);
|
|
70
|
+
if (isFile(fs, p)) return p;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Walk up from cwd looking for a `.git` marker; null when not in a repo. */
|
|
76
|
+
export function findProjectRoot(cwd, opts = {}) {
|
|
77
|
+
const { fs, path } = resolveDeps(opts);
|
|
78
|
+
let dir = path.resolve(cwd);
|
|
79
|
+
for (;;) {
|
|
80
|
+
try {
|
|
81
|
+
if (fs.existsSync(path.join(dir, ".git"))) return dir;
|
|
82
|
+
} catch {
|
|
83
|
+
/* keep walking */
|
|
84
|
+
}
|
|
85
|
+
const parent = path.dirname(dir);
|
|
86
|
+
if (parent === dir) return null;
|
|
87
|
+
dir = parent;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Ordered instruction-file discovery (user → project root → … → cwd, with the
|
|
93
|
+
* local companion right after its project file). Only existing files are
|
|
94
|
+
* returned. Deduped by absolute path (covers cwd == home corner cases).
|
|
95
|
+
*
|
|
96
|
+
* @returns {Array<{path:string, scope:"user"|"project"|"local"|"rules"}>}
|
|
97
|
+
*/
|
|
98
|
+
export function findInstructionFiles(opts = {}) {
|
|
99
|
+
const { fs, path, os } = resolveDeps(opts);
|
|
100
|
+
const cwd = path.resolve(opts.cwd || process.cwd());
|
|
101
|
+
const home = opts.home || os.homedir() || "";
|
|
102
|
+
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
const out = [];
|
|
105
|
+
const push = (p, scope) => {
|
|
106
|
+
if (!p) return;
|
|
107
|
+
const abs = path.resolve(p);
|
|
108
|
+
if (seen.has(abs) || !isFile(fs, abs)) return;
|
|
109
|
+
seen.add(abs);
|
|
110
|
+
out.push({ path: abs, scope });
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (home) {
|
|
114
|
+
push(
|
|
115
|
+
firstExisting(fs, path, home, [
|
|
116
|
+
path.join(".chainlesschain", "cc.md"),
|
|
117
|
+
path.join(".claude", "CLAUDE.md"),
|
|
118
|
+
]),
|
|
119
|
+
"user",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const root = findProjectRoot(cwd, opts) || cwd;
|
|
124
|
+
const chain = [];
|
|
125
|
+
let dir = cwd;
|
|
126
|
+
for (;;) {
|
|
127
|
+
chain.unshift(dir);
|
|
128
|
+
if (dir === root) break;
|
|
129
|
+
const parent = path.dirname(dir);
|
|
130
|
+
if (parent === dir) break;
|
|
131
|
+
dir = parent;
|
|
132
|
+
}
|
|
133
|
+
for (const d of chain) {
|
|
134
|
+
push(firstExisting(fs, path, d, PROJECT_FILE_NAMES), "project");
|
|
135
|
+
push(firstExisting(fs, path, d, LOCAL_FILE_NAMES), "local");
|
|
136
|
+
// Template-scaffolded project rules (`cc init -t` writes these) join the
|
|
137
|
+
// chain too, so scaffold-flow and memory-flow projects both feed the agent.
|
|
138
|
+
push(path.join(d, ".chainlesschain", "rules.md"), "rules");
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Collect `@path` import tokens from instruction text, skipping fenced code
|
|
145
|
+
* blocks (``` / ~~~). Line-level scanning is good enough for memory files,
|
|
146
|
+
* which use imports on their own prose lines.
|
|
147
|
+
*/
|
|
148
|
+
export function collectImportTokens(text) {
|
|
149
|
+
const found = [];
|
|
150
|
+
let inFence = false;
|
|
151
|
+
for (const line of String(text).split(/\r?\n/)) {
|
|
152
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
153
|
+
inFence = !inFence;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (inFence) continue;
|
|
157
|
+
IMPORT_TOKEN_RE.lastIndex = 0;
|
|
158
|
+
let m;
|
|
159
|
+
while ((m = IMPORT_TOKEN_RE.exec(line)) !== null) {
|
|
160
|
+
if (m[2]) found.push(m[2]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function readCapped(fs, abs, maxFileBytes) {
|
|
167
|
+
const buf = fs.readFileSync(abs);
|
|
168
|
+
const truncated = buf.length > maxFileBytes;
|
|
169
|
+
const content = (truncated ? buf.slice(0, maxFileBytes) : buf).toString(
|
|
170
|
+
"utf-8",
|
|
171
|
+
);
|
|
172
|
+
return { content, bytes: buf.length, truncated };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Load the full instruction set: hierarchy files + recursive imports.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} [opts] { cwd, home, deps, maxFileBytes, maxTotalBytes }
|
|
179
|
+
* @returns {{ files: Array<{path,scope,bytes,truncated,content}>, warnings: string[] }}
|
|
180
|
+
*/
|
|
181
|
+
export function loadProjectInstructions(opts = {}) {
|
|
182
|
+
const { fs, path, os } = resolveDeps(opts);
|
|
183
|
+
const home = opts.home || os.homedir() || "";
|
|
184
|
+
const maxFileBytes = Number.isFinite(opts.maxFileBytes)
|
|
185
|
+
? opts.maxFileBytes
|
|
186
|
+
: DEFAULT_MAX_FILE_BYTES;
|
|
187
|
+
const maxTotalBytes = Number.isFinite(opts.maxTotalBytes)
|
|
188
|
+
? opts.maxTotalBytes
|
|
189
|
+
: DEFAULT_MAX_TOTAL_BYTES;
|
|
190
|
+
|
|
191
|
+
const roots = findInstructionFiles(opts);
|
|
192
|
+
const visited = new Set(roots.map((r) => r.path));
|
|
193
|
+
const out = [];
|
|
194
|
+
const warnings = [];
|
|
195
|
+
let total = 0;
|
|
196
|
+
|
|
197
|
+
// Queue of { abs, scope, depth } — imports inherit "import" scope.
|
|
198
|
+
const queue = roots.map((r) => ({ abs: r.path, scope: r.scope, depth: 0 }));
|
|
199
|
+
|
|
200
|
+
while (queue.length) {
|
|
201
|
+
const { abs, scope, depth } = queue.shift();
|
|
202
|
+
if (total >= maxTotalBytes) {
|
|
203
|
+
warnings.push(
|
|
204
|
+
`project-memory budget (${maxTotalBytes} bytes) exhausted — remaining files skipped`,
|
|
205
|
+
);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
let entry;
|
|
209
|
+
try {
|
|
210
|
+
entry = readCapped(fs, abs, maxFileBytes);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
warnings.push(`${abs} — cannot read: ${err.message}`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
total += Math.min(entry.bytes, maxFileBytes);
|
|
216
|
+
out.push({ path: abs, scope, ...entry });
|
|
217
|
+
|
|
218
|
+
if (depth >= MAX_IMPORT_DEPTH) continue;
|
|
219
|
+
const baseDir = path.dirname(abs);
|
|
220
|
+
for (const raw of collectImportTokens(entry.content)) {
|
|
221
|
+
let target = raw;
|
|
222
|
+
if (target.startsWith("~/") || target === "~") {
|
|
223
|
+
if (!home) continue;
|
|
224
|
+
target = path.join(home, target.slice(1));
|
|
225
|
+
}
|
|
226
|
+
const resolved = path.resolve(baseDir, target);
|
|
227
|
+
if (visited.has(resolved) || !isFile(fs, resolved)) continue; // silent:
|
|
228
|
+
// non-files are decorative @tokens (npm scopes, emails), not imports.
|
|
229
|
+
visited.add(resolved);
|
|
230
|
+
queue.push({ abs: resolved, scope: "import", depth: depth + 1 });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { files: out, warnings };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function escapeAttr(s) {
|
|
237
|
+
return String(s).replace(/"/g, """);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Render loaded instructions as a single system-prompt block ("" if none). */
|
|
241
|
+
export function renderProjectInstructionsBlock(loaded) {
|
|
242
|
+
const files = loaded?.files || [];
|
|
243
|
+
if (!files.length) return "";
|
|
244
|
+
const parts = [
|
|
245
|
+
'<project-instructions note="project memory auto-loaded from cc.md / CLAUDE.md / AGENTS.md; follow these as authoritative project conventions">',
|
|
246
|
+
];
|
|
247
|
+
for (const f of files) {
|
|
248
|
+
const attrs =
|
|
249
|
+
`path="${escapeAttr(f.path)}" scope="${f.scope}"` +
|
|
250
|
+
(f.truncated ? ` truncated="true" total-bytes="${f.bytes}"` : "");
|
|
251
|
+
parts.push(`<file ${attrs}>`);
|
|
252
|
+
parts.push(f.content.trimEnd());
|
|
253
|
+
if (f.truncated) {
|
|
254
|
+
parts.push(`… [truncated — file is ${f.bytes} bytes]`);
|
|
255
|
+
}
|
|
256
|
+
parts.push(`</file>`);
|
|
257
|
+
}
|
|
258
|
+
parts.push("</project-instructions>");
|
|
259
|
+
return parts.join("\n");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* One-call convenience for composeSystemPrompt: returns the rendered block or
|
|
264
|
+
* "" — and never throws (fail-open by design).
|
|
265
|
+
*/
|
|
266
|
+
export function loadProjectInstructionsBlock(opts = {}) {
|
|
267
|
+
try {
|
|
268
|
+
const loaded = loadProjectInstructions(opts);
|
|
269
|
+
return renderProjectInstructionsBlock(loaded);
|
|
270
|
+
} catch {
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const _deps = { fs: fsDefault, path: pathDefault, os: osDefault };
|
|
@@ -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 };
|