LifeOS PROJECTS.md 项目注册表实战指南:路由别名、部署门禁与记忆系统集成
【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS
PROJECTS.md 是 LifeOS 身份层(USER/)中的项目注册表:它以一张紧凑的表格登记你正在维护的全部项目(本地路径、线上 URL、部署命令、技术栈),并配套一组"路由别名",让 DA 在每次会话启动时就能把 "my blog""the workspace" 这类自然语言指代准确路由到对应代码库。读完本文,你将掌握 PROJECTS.md 的完整字段语义、初始化与维护流程,并理解 Atlas、Pulse、ProposalScope、DeployRegistrationGate 等子系统是如何以这张表格为唯一事实源进行解析、路由与强制校验的。
PROJECTS.md 在 LifeOS 中的定位
在 USER/README.md 描述的目录布局中,LIFEOS/USER/是 LifeOS 的"身份层",其中明确标注了:
LIFEOS/USER/ ├── PROJECTS/PROJECTS.md # Project registry + routing aliases (loaded at startup)PROJECTS.md 是五个"启动即加载"的身份文件之一。在 CLAUDE.template.md 中,它与其他四个身份文件(PRINCIPAL_TELOS、PRINCIPAL_IDENTITY、DA_IDENTITY、OPERATIONAL_RULES)一起被声明为顶层@-import:
# @LIFEOS/USER/TELOS/PRINCIPAL_TELOS.md # @LIFEOS/USER/PRINCIPAL/PRINCIPAL_IDENTITY.md # @LIFEOS/USER/DIGITAL_ASSISTANT/DA_IDENTITY.md # @LIFEOS/USER/PROJECTS.md # @LIFEOS/USER/CONFIG/OPERATIONAL_RULES.md从源码结构看,这些 import 默认处于注释状态,由 agentic 的/LifeOS setup流程(通过 ActivateImports.ts)在 USER 脚手架填充完成后统一激活。由于 Claude Code 不会跟随被导入文件内部的传递式@-import,所以每个身份文件都必须在该路由表顶层单独列出。这意味着:一旦激活,每次会话启动时 PROJECTS.md 的全文都会被加载进 DA 的上下文——这也正是 ConfigSystem.md 把它归类为"USER 项目注册表(@-imported)"、lifeos-context.ts 将其纳入每次会话 CONTEXT 注入块的原因。
表格结构与字段语义
原文档给出的模板表格如下:
| Project | Path | URL | Deploy | Stack |
|---|---|---|---|---|
| (interview — first project) | ~/code/example | example.com | bun run deploy | TS, React |
五个字段的语义如下:
- Project(项目名):DA 用于指代该项目的规范名称。建议用加粗
**Name**包裹(ProposalScope 与 Pulse 都会优先解析加粗内容),并可附加状态标记,如🎯(system-of-record,系统事实源)、🚨(sensitive,敏感)、🚧(建设中)、✅、🚀等,或(in design)、(decommissioned)、(concept)这类斜体状态。 - Path(本地路径):代码库在本机的绝对路径,如
`~/code/example`。反引号会被解析器剥除。 - URL(线上地址):该项目的公网域名或完整 URL,可以是裸域名、带端口的 host 或完整
https://链接;—或-表示无线上地址,~~删除线~~表示已下线的基础设施(退役行不会被渲染为链接)。 - Deploy(部署命令):可复制的部署命令,如
bun run deploy。命令中合法的管道符需要用\|转义(如grep … \| xargs),解析器会把转义还原为字面|。 - Stack(技术栈):项目技术栈或一句话描述,如
TS, React。这一列历史上经历过重命名(Stack → ISA / detail),因此解析器不锁定末列列名。
解析器的真实读取规则
Pulse 的 projects.ts 是理解这张表格格式约束的最佳教材(它是只读表面,"零数据持有":每次请求实时解析 markdown)。其parseProjects()的规则包括:
- 表头必须同时包含
Project与Deploy关键词(/\bProject\b/i与/\bDeploy\b/i),解析器以此为定位锚点; - 行必须至少有 5 个单元格,否则跳过;
- 项目名提取优先取
**加粗**片段,随后剥离状态 emoji、(in design|decommissioned|concept)等描述性标记; - URL 列会尝试还原为真实
https://链接(localhost则补http://),删除线开头的链接返回null; - 另支持可选的
## Open Sessions to Resume小节:会话标签以项目名为前缀时,该项目被标记为openSession。
Atlas 的项目收集器 Projects.ts 则遵循略有不同的解析约定:它只解析## Routing Aliases之前的"主表格",跳过表头与分隔线,并从单元格中提取两类观测:
- SERVES 边:从 URL 列提取域名(跳过
localhost、.lan、github.com),建立"项目 → 域名"的服务关系; - DEPLOYED_FROM 边:行内任意位置出现
github.com/owner/repo时,建立"项目 → GitHub 仓库"的部署来源关系。
两个解析器都强调故障降级而非抛错:源文件缺失时返回空结果,绝不中断流水线(对应 Atlas 的 ratchet gate 3);但文件存在而格式漂移(有内容却解析不出任何行)会被标记为 degraded 状态并告警——projects.ts 的健康检查里记录了一次真实的列名重命名事故,曾让页面静默置零一周。这提醒你:改表头列名或破坏表格结构会影响下游解析。
Routing Aliases:自然语言路由的核心机制
原文档强调:DA 在每次会话启动时读取该表,用于把别名路由到具体仓库。这就是 Routing Aliases 小节的作用:
| When you say... | The DA routes to... |
|---|---|
| "my site", "the blog" | (interview — primary site) |
| "the workspace", "that project" | (interview — main active project) |
它的价值在于让 DA 从"上下文推断"升级为"查表路由":当你说 "check the blog deploy" 时,DA 直接命中别名行,得到对应的本地路径、URL 与部署命令,无需在庞杂的会话历史中猜测。这种"别名 → 规范条目"的映射是身份层文件常见的模式(CONTACTS.md 的人名路由同理),也是 PROJECTS.md 保持轻量、可增量维护的原因——原文档的结语很直白:"保持它是最新的——增量维护很便宜,事后重建很昂贵。"
初始化:从样板行到真实身份
原文档开头有两条醒目提示:
Bootstrap default — functional before interview. Run
/interview(projects phase) to personalize. ⚠ INTERVIEW REQUIRED — run/interviewto populate this file with your real identity content. The DA loads it at every session start; without your content, the model operates on placeholders.
也就是说,新安装的 LifeOS 携带的是样例行((interview — first project)),此时 DA 加载到的只是占位符。个性化流程有两条路径:
/interview的 projects 阶段:面试会逐个询问你的活跃项目,并把结果以行追加到表中,同时生成对应的路由别名;/LifeOS setup的 Phase 0:在 Phase0Setup.md 中,Step 0.5 明确要求"至少一个项目,让 PROJECTS 路由可用",验收标准是"PROJECTS.md 拥有 ≥1 行非样例行"。
Phase 0 的扫描器是 InterviewScan.ts,它把USER/PROJECTS.md列为 phase 0 / setup 类目标(leverage 权重 8),可用以下命令查看完成度:
bun ~/.claude/LIFEOS/TOOLS/InterviewScan.ts --json | jq '.targets[] | select(.phase == 0)'若所有 phase 0 目标 ≥80% 完成,工作流自动跳过并转向 TelosCheckin。完成 Phase 0 后,流程会重新生成PRINCIPAL_TELOS.md并发送 Pulse/reload,让运行中的守护进程加载新身份。日常维护则交给 ContextCheckin.md 的例行检查——它会主动询问:"PROJECTS 距今 N 天。有没有新项目要加,或者已完成的项目要退役?路由别名还符合你的叫法吗?"并将 PROJECTS.md 与新鲜度预算(如 45 行预算)对比,超预算时提示裁剪或拆分。
谁在读这个文件:六个消费方
PROJECTS.md 不是一份给人看的档案,而是一个被多个子系统读取的结构化事实源:
| 消费方 | 文件 | 读取方式 |
|---|---|---|
| 会话上下文 | CLAUDE.template.md、lifeos-context.ts | @-import全文加载 / CONTEXT 注入块 |
| Atlas 观测 | collectors/Projects.ts | 解析主表格,生成 SERVES / DEPLOYED_FROM 边 |
| Pulse 仪表盘 | modules/projects.ts | GET /api/projects实时解析(Live Apps / TELOS / Retired 三组) |
| Proposal 作用域 | ProposalScope.ts | 读取加粗项目名,判定记忆提案是全局还是项目级 |
| 部署门禁 | DeployRegistrationGate.hook.ts | 检查自定义域名是否已登记 |
| 记忆写入边界 | MutationTier.ts | 判定 PROJECTS.md 属于 Tier B |
下面展开几个最值得关注的机制。
部署门禁:新上线站点必须"先登记、后部署"
DeployRegistrationGate.hook.ts 是 PROJECTS.md 最硬核的消费方。它的契约(OPERATIONAL_RULES § Bunker registration)规定:本次会话中任何带 CUSTOM DOMAIN 的wrangler deploy,必须在会话结束时同时登记到 PROJECTS.md 表格行和 ARBOL 的 infra-inventory.ts,否则 Stop 时该 hook 会返回decision: "block"阻止会话结束。
实现上有几个细节值得注意:
- 域名识别用正则
CUSTOM_DOMAIN_RE从 transcript 中提取 wrangler 的触发行(<domain> (custom domain)),并以行边界锚定避免误匹配正文中的提法; - 校验是字面子串检查:
if (!projects.includes(d)) missing.push("PROJECTS.md row (+ routing alias)")——所以登记的域名必须与部署的域名逐字符一致(会去掉www.前缀); - 每个域名每会话只拦截一次(状态文件去重),任何异常都"fail open",绝不因为本 gate 让 Stop 中断;
- ARBOL 定制不存在时自动跳过第二项检查。
这解释了原文档"保持它是最新的"为什么是一条被强制执行的纪律:在 LifeOS 的运营规则里,项目登记不是事后整理,而是部署动作的一部分。
记忆系统:Tier B 追加 + 项目级作用域
在 MemorySystem.md 的记忆分类体系中,PROJECTS.md 被明确列为projects类型的提案目标:
| target_kind | Target file | 触发条件 |
|---|---|---|
projects | USER/PROJECTS.md | 主理人提到一个应进入项目路由表的新项目 |
写入边界由 MutationTier.ts 的四层分类器决定:LIFEOS/USER/PROJECTS.md属于Tier B(logged-append with audit)——记忆评审器可以追加,但每次写入都要在tier-b-writes.jsonl记录审计行;它不像 Tier C 的身份文件那样"只提案不可直改",也不像 Tier D 那样完全不可触碰。这对应了原文档"interview 一次一个项目、逐行追加到表格"的增量维护设计:追加是系统允许的默认形态。
值得注意的边界设计:在 MemoryTypes.ts 中,projects类型被故意排除在作用域门控之外——因为"PROJECTS.md 一行的全部职责就是命名一个项目",对它做作用域限制会把本应落在注册表的提案挡在门外。反观 ProposalScope.ts,它正是从"路由表格的加粗第一列"(/^\|\s*\*\*([^*|]{2,40})\*\*/)读取项目名,用来判定某条记忆提案是全局教义还是归属于某个具体项目("Vector's portal invites are never sent" → 归入 Vector 项目)。由于它是实时读文件而不是硬编码,新项目只要加入表格就立刻具备可作用域性——表格即注册表,无需第二处维护点。
Pulse 仪表盘与 Freshness 检查
Pulse 的 projects.ts 模块提供GET /api/projects,把项目按三个来源分组展示:
- Live Apps(来自
USER/PROJECTS.md主表格); - TELOS(来自
USER/TELOS/TELOS.md的## Projects散文小节,每行一个项目,无 path/URL/deploy 信息); - Retired(来自
USER/PROJECTS_RETIRED.md)。
此外,FreshnessSystem.md 把LIFEOS/USER/PROJECTS.md列为受新鲜度约定(pai-freshness-v1)约束的"项目注册表 + 路由别名"文件,tab-freshness.ts 会持续跟踪它的最后修改时间,配合 ContextCheckin.md 的例行检查驱动周期性的"增删项目 / 校准别名"维护。工作系统 WorkSystem.md 也会读取它:登记在 PROJECTS.md 中、但 14 天无提交且无未关闭 issue 的项目,会被标记为[Project-Check]待办。
维护最佳实践
综合原文档与各消费方的解析约定,维护 PROJECTS.md 时有几条实操准则:
- 保持至少一行真实项目:Phase 0 的验收标准是"≥1 行非样例行",样例行会让 DA 基于占位符工作。
- 项目名用加粗包裹,状态标记用 emoji 或斜体后缀:
**Blog** 🎯同时服务 ProposalScope(加粗列)与 Pulse 徽章(🎯=system-of-record、🚨=sensitive、decommissioned/concept 等),并注意解析器会剥离这些标记,显示名是干净的。 - URL 列只放可路由的域名:
localhost、.lan后缀、github.com会被 Atlas 的 SERVES 边过滤;需要关联 GitHub 仓库时直接在行内写github.com/owner/repo(触发 DEPLOYED_FROM 边)。 - 新项目先加行再加表项,退役项目移入
PROJECTS_RETIRED.md(Pulse 有独立的 Retired 分组,删除线 URL 不会被渲染为链接)。 - 改列名要谨慎:Pulse 解析器只锁定"含 Project 与 Deploy 关键词"的表头,但列数少于 5 或破坏表格行格式会导致解析漂移并触发 degraded 告警。
- 别名与表格同步更新:路由别名是自然语言路由的入口,新增/重命名项目时务必同步,否则"check the blog deploy"这类指令会命中过期映射。
- 不要手动绕过门禁:上线自定义域名时,先按 DeployRegistrationGate.hook.ts 的要求完成 PROJECTS.md 行 + ARBOL infra-inventory 登记,再执行部署,否则 Stop 会被拦截并提示补齐注册。
小结
PROJECTS.md 以一张五行表格 + 一组别名,承担了 LifeOS 中"项目身份"的单一事实源职责:它被@-import进每次会话上下文支撑自然语言路由,被 Atlas 解析为领域与仓库的关联图,被 Pulse 渲染为 Live Apps 仪表盘,被 ProposalScope 用作项目级记忆提案的作用域字典,并被 DeployRegistrationGate 用作公网部署的强制注册清单。维护好它,本质上是维护 DA 对"你在做什么"的结构化认知——这正是原文档那句"增量维护便宜、事后重建昂贵"的底层原因。
【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考