@thinkingai/ae-cli 6.1.25 → 6.1.27

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.
Files changed (121) hide show
  1. package/README.md +28 -9
  2. package/README.zh.md +25 -9
  3. package/bin/ae-cli.js +6 -1
  4. package/cli-installation-guide.md +91 -0
  5. package/dist/{auth-LHM7NQPR.js → auth-WA7YGGRT.js} +6 -8
  6. package/dist/automation-VAAQBM7Y.js +459 -0
  7. package/dist/{capability-SJI5KOCK.js → capability-GAAYJ3IQ.js} +6 -6
  8. package/dist/{chunk-KJDOTPYU.js → chunk-3YVEAZUN.js} +59 -26
  9. package/dist/{chunk-KS4DE3MX.js → chunk-5XJYLEVC.js} +4 -4
  10. package/dist/{chunk-BW7JUDEI.js → chunk-6VNIR4AP.js} +514 -213
  11. package/dist/{chunk-5XRQ3CZR.js → chunk-7KU2WNQE.js} +43 -8
  12. package/dist/{chunk-7WZACOAI.js → chunk-AP7YP2NX.js} +2 -2
  13. package/dist/{chunk-XKIW3QMF.js → chunk-AQJPNWZF.js} +3 -3
  14. package/dist/chunk-CI24BDR2.js +43 -0
  15. package/dist/{chunk-SO5LKIWP.js → chunk-CT7QDJS6.js} +2 -2
  16. package/dist/{chunk-DU72X2IO.js → chunk-GHXZK54S.js} +22 -10
  17. package/dist/{chunk-2FJM4HRV.js → chunk-MWIA43UJ.js} +3 -3
  18. package/dist/{chunk-N4BLPJC7.js → chunk-OP44TT4H.js} +1 -1
  19. package/dist/chunk-Q5US7JC5.js +133 -0
  20. package/dist/chunk-RK6GLDGI.js +27 -0
  21. package/dist/chunk-RPBOAQQQ.js +56 -0
  22. package/dist/{chunk-GK7WDY7E.js → chunk-S6M42KND.js} +1 -1
  23. package/dist/{chunk-QYXDJOLA.js → chunk-VZZNLV3I.js} +19 -11
  24. package/dist/{chunk-YVGXGTD6.js → chunk-WNGFSIOB.js} +7 -1
  25. package/dist/{chunk-IG3DYEIR.js → chunk-ZL7SGNIA.js} +5 -3
  26. package/dist/{community-report-client-6VJDDMJJ.js → community-report-client-PEERQCX2.js} +1 -1
  27. package/dist/{config-XQWOPDHN.js → config-JOD5DOPE.js} +4 -4
  28. package/dist/context-NGVFPCBK.js +136 -0
  29. package/dist/{data-integration-XBTCGXQT.js → data-integration-BCLQFTGU.js} +6 -6
  30. package/dist/index.js +123 -46
  31. package/dist/{local-data-upload-client-3P7NIVQ4.js → local-data-upload-client-TWMHFLUA.js} +1 -1
  32. package/dist/memory-CQKH2JAV.js +580 -0
  33. package/dist/{metadata-JRYKNUS5.js → metadata-NR3PHR23.js} +7 -7
  34. package/dist/{model-ZS7HVGKN.js → model-VX5M2CJ6.js} +9 -5
  35. package/dist/{personal-semantic-preference-IDE2GE5T.js → personal-semantic-preference-2RAI36P3.js} +117 -11
  36. package/dist/{project-semantic-36KOCYN7.js → project-semantic-3BUGP3ZM.js} +6 -6
  37. package/dist/{sync-EQLSTUS7.js → sync-RZIAV37C.js} +12 -8
  38. package/dist/{te-agent-Z3HCTKR7.js → te-agent-AORRE2FM.js} +90 -454
  39. package/dist/{te-analysis-5ZZ6YHVH.js → te-analysis-GP7DSQ7Q.js} +297 -20
  40. package/dist/{te-community-Z7JACGF3.js → te-community-42M7ICDO.js} +3 -3
  41. package/dist/{te-dataops-F7GQOIS6.js → te-dataops-BNKNZ4F2.js} +5 -5
  42. package/dist/{te-engage-RNRMYDO3.js → te-engage-2CO6LRCX.js} +6 -6
  43. package/dist/{te-experiment-BTAC2UNK.js → te-experiment-652CLXMU.js} +6 -6
  44. package/dist/{te-kb-QJLJEX5K.js → te-kb-ULATOTQ3.js} +494 -214
  45. package/dist/{te-system-IXC43QHZ.js → te-system-UGHVKBVV.js} +10 -9
  46. package/dist/{te-team-BIZIKIHZ.js → te-team-YA6SNOJ3.js} +6 -6
  47. package/dist/things-DTCOQIQB.js +1091 -0
  48. package/dist/{update-W2SGQNKT.js → update-ESNYKUVR.js} +59 -51
  49. package/dist/workspace-JEOHKFPX.js +554 -0
  50. package/package.json +21 -9
  51. package/skills/ae-agent/SKILL.md +91 -139
  52. package/skills/ae-agent/references/add-model.md +1 -1
  53. package/skills/ae-agent/references/create-agent.md +1 -1
  54. package/skills/ae-agent/references/find-archived-conversations.md +12 -5
  55. package/skills/ae-agent/references/get-agent.md +1 -1
  56. package/skills/ae-agent/references/list-agents.md +2 -2
  57. package/skills/ae-agent/references/list-models.md +2 -2
  58. package/skills/ae-agent/references/restore-conversation.md +3 -1
  59. package/skills/ae-analysis/SKILL.md +15 -6
  60. package/skills/ae-analysis/references/ai_models/funnel.md +5 -3
  61. package/skills/ae-analysis/references/ai_models.md +27 -0
  62. package/skills/ae-analysis/references/command_index.md +12 -4
  63. package/skills/ae-analysis/references/dashboard_move.md +21 -0
  64. package/skills/ae-analysis/references/dashboard_report_add.md +2 -0
  65. package/skills/ae-analysis/references/dashboard_report_get.md +19 -0
  66. package/skills/ae-analysis/references/dashboard_report_update.md +27 -0
  67. package/skills/ae-analysis/references/dashboard_update.md +1 -1
  68. package/skills/ae-analysis/references/event_report_presentation.md +76 -0
  69. package/skills/ae-analysis/references/feedback_submit.md +37 -0
  70. package/skills/ae-analysis/references/metadata_resolution.md +15 -0
  71. package/skills/ae-analysis/references/personal_semantic_preference_review.md +9 -0
  72. package/skills/ae-analysis/references/personal_semantic_preference_review_list.md +7 -0
  73. package/skills/ae-analysis/references/personal_semantic_preference_submit.md +7 -0
  74. package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +68 -27
  75. package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +40 -1
  76. package/skills/ae-analysis/references/report_create.md +13 -3
  77. package/skills/ae-analysis/references/report_get.md +5 -1
  78. package/skills/ae-analysis/references/report_update.md +15 -4
  79. package/skills/ae-analysis/references/system_query_monitor_overview.md +2 -0
  80. package/skills/ae-analysis/references/system_query_task_cancel.md +2 -0
  81. package/skills/ae-analysis/references/system_query_task_export.md +6 -2
  82. package/skills/ae-analysis/references/system_query_task_get.md +2 -0
  83. package/skills/ae-analysis/references/system_query_task_list.md +7 -3
  84. package/skills/ae-analysis/references/system_query_task_options.md +2 -2
  85. package/skills/ae-analysis/references/system_query_task_realtime_list.md +15 -0
  86. package/skills/ae-analysis/references/user_tag_models.md +2 -0
  87. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-empty-skeleton.mjs +105 -0
  88. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs +337 -22
  89. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/company-kb.mjs +21 -8
  90. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-rules.md +24 -11
  91. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-schema.md +66 -0
  92. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +1 -0
  93. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/package-wiki-source-zip.mjs +109 -40
  94. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/plan-kb-source-sync.mjs +1 -1
  95. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/prepare-compile-schema.mjs +64 -0
  96. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/refresh-state-parts.mjs +91 -0
  97. package/skills/ae-automation/SKILL.md +16 -0
  98. package/skills/ae-automation/references/cli.md +53 -0
  99. package/skills/ae-automation/references/command_index.md +17 -0
  100. package/skills/ae-capability/SKILL.md +15 -6
  101. package/skills/ae-current-context/SKILL.md +3 -2
  102. package/skills/ae-kb/SKILL.md +62 -12
  103. package/skills/ae-kb/references/analysis-workflow.md +73 -36
  104. package/skills/ae-kb/references/query-workflow.md +67 -33
  105. package/skills/ae-kb/references/schema-import.md +5 -3
  106. package/skills/ae-kb/references/source-mutations.md +6 -4
  107. package/skills/ae-kb/references/text-sources.md +53 -0
  108. package/skills/ae-kb-discovery/SKILL.md +1 -1
  109. package/skills/ae-team/SKILL.md +23 -17
  110. package/skills/ae-things/SKILL.md +85 -0
  111. package/skills/ae-things/references/cli.md +103 -0
  112. package/skills/ae-things-progress/SKILL.md +43 -0
  113. package/skills/ae-things-review/SKILL.md +42 -0
  114. package/skills/ae-workspace/SKILL.md +43 -0
  115. package/skills/ae-workspace/references/commands.md +51 -0
  116. package/dist/chunk-E2JKXMVO.js +0 -29
  117. package/dist/context-M3HDGEIR.js +0 -46
  118. package/dist/memory-6APEB2JB.js +0 -892
  119. package/skills/ae-agent/references/create-automation.md +0 -125
  120. package/skills/ae-agent/references/list-automations.md +0 -56
  121. package/skills/ae-agent/references/update-automation.md +0 -107
@@ -2,19 +2,29 @@
2
2
 
3
3
  These rules guide Schema generation for project semantic knowledge bases built from source tree ZIP uploads.
4
4
 
5
- - Generate durable compile rules only. Do not copy platform working directory names, absolute filesystem paths, temporary extraction paths, source IDs, run IDs, or local machine paths into the generated rules.
6
- - Refer to the uploaded material as "the ZIP source tree" or "files under the source tree root". Use stable source-tree paths such as `sources/assets/reports/...` only when a rule must mention a category.
7
- - Treat `sources/README.md` and category `README.md` files as filing guides. They define where future material belongs; they are not business evidence.
8
- - CLI-managed asset facts live under `sources/assets/**`. Human project background, business rules, meetings, decisions, corrections, and references live outside `sources/assets/**`; asset refresh must not delete or override those human inputs.
9
- - Organize generated Wiki pages for knowledge consumption, not by mirroring every source file. Use project overview, business domains, reusable semantics, assets, decisions, and review pages.
5
+ - Generate durable rules. Never copy working directories, absolute or temporary paths, source IDs, run IDs, or machine paths. Refer to "the ZIP source tree" and use stable `sources/...` paths only when needed. README files are filing guides, not business evidence.
6
+ - `sources/project/pskb-p*-refresh-state.md` and its numbered `-part-*.md` files are CLI machine state. Do not read, decode, cite, or compile them as business evidence; use the authored project and asset sources instead.
7
+ - CLI-managed asset facts live under `sources/assets/**`. Adopted personal semantics exported by the asset package live under `sources/personal-semantics/**` and are also CLI-managed generated source. Human project background, business rules, meetings, decisions, corrections, and references live outside those generated directories; asset refresh must not delete or override those human inputs.
8
+ - Organize generated Wiki pages by scope and business use, not by mirroring every source file. The primary navigation has five ordered entries: project overview, project-wide shared knowledge, business domains, event and asset catalog, and governance with provenance. Within each domain, explain the business question, judgment path, relevant events, metrics, assets, and boundaries. Keep asset-type pages as secondary evidence and lookup indexes.
9
+ - Begin the root page with question-intent routing. Route business-state and current-value questions through the matching domain and recall card; route named assets and field/definition/dependency questions through the asset catalog and governance; route shared product background through project-wide knowledge. If no card matches, enter the relevant domain and filter assets by business object, answer grain, filters and time. If no suitable evidence remains, state the gap. Stop searching once the smallest authoritative evidence set matches object, grain, scope and time.
10
+ - Each recall card must identify its triggering questions, business object and answer grain, required parameters, conditional branches with direct asset targets, excluded or non-interchangeable assets, a stop condition, and a fallback for unavailable, conflicting or unmatched evidence. Preserve these as instructions in the compiled card; a fixed preferred-then-fallback sequence is insufficient when branches use different definitions.
11
+ - Public event groups are a project event lookup taxonomy. A group such as a product module is not automatically a business domain or an event definition. Only include events present in the exported asset scope, preserve their event-name identity, and link to the canonical event page. Never infer business meaning, certification, or domain membership from a group label alone. Personal event catalogs must not enter a shared project Wiki.
10
12
  - The project overview must start with the project-level business model: business positioning, audience roles, business objects, object relationships, decision chains, asset roles in those chains, and non-goals. Asset counts, snapshot hashes, source package metadata and build logs belong after that model. Do not reduce the overview to a directory, asset inventory, or build summary.
11
13
  - Do not infer business meaning from titles alone. Preserve unknowns, conflicts, source scope, certification state, and provenance when evidence is incomplete.
14
+ - Distinguish query counts, distinct report counts, company counts and people counts; never substitute one grain for another from similarly named fields. A scoped personal-semantic method for customer report usage belongs only on its verified scenario card and must link the actual search event, company field, report-name field, report asset and adoption record. Mark unverified field equivalence or name uniqueness as unknown.
15
+ - If the governed package contains neither certified entry assets nor adopted personal semantics, compile the uploaded empty skeleton as an explicit unknown-state Wiki with the normal five navigation layers. Do not invent business domains, decisions, metrics, or asset definitions. When later evidence replaces the skeleton, remove obsolete empty-state claims from the published Wiki.
12
16
  - Corrections and decisions override other material only when they state target, content, date, status, and applicable scope.
13
17
 
14
18
 
15
19
  ## Navigation and page naming
16
20
 
17
- The generated top-level `index.md` must be a decision-oriented navigation page. It may contain only module navigation, common-entry guidance, and a compact category/count overview. Do not append a complete page list, sitemap, individual asset files, metadata property pages, numeric report IDs, numeric dashboard IDs, source container IDs, or raw source filenames to the top-level page. Top-level navigation should link to project overview, business domains, recall cards, asset category indexes, decisions, and review pages. List individual assets only inside the matching asset index page, domain page, dashboard page, source-container page, or search result context. If the compiler wants to expose a full sitemap, put it under `wiki/index.md` or category index pages, never in root `index.md`.
21
+ The top-level `index.md` is decision-oriented. Keep its five primary entries; do not add a sitemap, individual assets or properties, numeric IDs, containers, or raw filenames. Put recall cards under domains, event groups under the event map, asset indexes under the catalog, and individual assets in category/domain/container pages. A full sitemap belongs in `wiki/index.md` or category indexes, never the root.
22
+
23
+ Both indexes use these primary targets: `wiki/project-overview.md`, `wiki/common/index.md`, `wiki/domains/index.md`, `wiki/asset-index/index.md`, `wiki/governance/index.md`. Put `wiki/event-map/index.md` below the catalog; keep asset, card, and adoption pages reachable below the five entries.
24
+
25
+ Adopted personal semantics are source provenance, not a top-level knowledge category. Integrate each accepted conclusion into its declared project, domain, recall-card, or asset target with its applicable boundary; retain a knowledge-adoption record under governance. A personal preference remains scoped and must not be turned into a project-wide rule merely because it was adopted. Use the label "project supplementary knowledge" for the integrated content and "knowledge adoption record" for its provenance.
26
+
27
+ Navigation taxonomy version 2.1 adds root intent routing and conditional recall-card execution. On a refresh from an older version, perform a full Wiki rebuild from current sources and regenerate the root index and cards; do not reuse obsolete published pages or retain an old top-level personal-semantic page. Preserve human source files and current canonical asset facts. After publication, verify routing, no-card fallback, card branches, stop conditions and all five navigation entries. A valid file count alone does not prove answer quality.
18
28
 
19
29
  When linking to an individual asset, use its business title as the visible link text and keep the numeric ID only as supporting identity beside the title, for example `示例报表(report 1001)`. Do not render bare numeric links such as `1001` as navigation items.
20
30
 
@@ -22,14 +32,17 @@ When linking to an individual asset, use its business title as the visible link
22
32
 
23
33
  CLI Agent has already performed the main semantic compilation for ALL assets: business meanings, SQL interpretation, calculation definitions, relationships, domains, recall boundaries, and conflict findings. These are editable source documents, not an imported final Wiki snapshot. Reuse their complete prose and calculation facts. Do not repeat project-wide extraction, SQL parsing, semantic planning, or per-field rewriting.
24
34
 
25
- Business-domain pages include a judgment model and an `Agent 使用摘要` for first-hop routing. Preserve them as the concise answer source for "what business state does this domain judge", "which objects/signals/decision path should the Agent follow", "which assets are primary/supporting/drilldown/excluded", "what does this domain solve", "which events/metrics are mainly used", and "which dimensions should the agent drill down by". Do not replace them with a raw report list or regenerate them from every report page unless newer scoped evidence explicitly changes the summary.
35
+ Consume adopted personal semantics by type: `background` informs context, `preference` informs routing and style, `experience` supplies proven cautions, and `asset_context` annotates an existing referenced asset. Retain person name, source/update time, source IDs, and scope; never expose `open_id`. Fuse similar conclusions without widening scope. They cannot override governed definitions, SQL facts, corrections, decisions, or live results.
36
+
37
+ Business-domain pages need a judgment model and `Agent 使用摘要` for first-hop routing: business state, objects, signals, decision path, primary/supporting/drilldown/excluded assets, events, metrics, and dimensions. Do not replace this with a report list or regenerate it without newer scoped evidence.
26
38
 
27
39
  Reject or repair any generated Wiki shape where the root, overview, or business-domain pages only list questions, events, metrics, dimensions, reports, dashboards or counts without explaining the business judgment chain that makes those assets useful. Examples in source instructions are illustrative only; never hardcode project-specific business domains, assets, metrics, event names or decision chains into the compiled Wiki.
28
40
 
29
- KB must compile and publish the final Wiki: preserve prepared asset sections, map source references to Wiki links, build concise titled navigation, and integrate project background, meetings, decisions and corrections. Batch independent page writes and link checks. Do not read each source repeatedly or regenerate unchanged pages.
41
+ Compile and publish the Wiki with prepared asset sections, linked references, concise titled navigation, and scoped human context. Batch independent writes and link checks; reuse unchanged pages.
30
42
 
31
- For SQL assets, preserve the complete Agent-authored purpose, parameters, output fields, grain, filters, limits, applicable/non-applicable questions and evidence locator in the report page's single `Agent 使用摘要`. Raw SQL/index JSON stays in the local evidence package; do not attempt to fetch it or infer omitted details. Missing facts remain unknown. Preserve certification, dependency-only status, conflicts and blocked recall; do not dismiss safety states as stale placeholders. Keep build-process markers out of visible Wiki prose: do not expose `CLI Agent`, `摘要来源`, `semantic_plan`, parser fallback labels, or builder/debug terminology as user-facing business content.
43
+ For SQL assets, keep Agent-authored purpose, parameters, output fields, grain, filters, limits, applicable/non-applicable questions, and evidence locator in one `Agent 使用摘要`. Raw SQL/index JSON stays local; do not infer missing details. Keep unknowns, certification, dependency-only status, conflicts, and blocked recall; do not dismiss safety states as stale placeholders. Never expose `CLI Agent`, `摘要来源`, `semantic_plan`, parser fallback, or builder/debug terms in Wiki prose.
44
+ - `dependency_only` is an answer-authority limit, not a blanket ban on retrieval. Known metadata may support definition, dependency or conflict explanations. It cannot independently prove business state or live numeric results. If platform permissions block direct metadata retrieval, follow its authorized referencing asset and say what remains unverified.
32
45
 
33
- For non-SQL report pages, preserve the `Agent 使用摘要` as a usable business routing summary, not a mechanical parser echo. The purpose and applicable-question bullets must not be identical. Metric, dimension and filter sections should preserve structured evidence when present and use source-confirmation wording for unavailable facts. Do not introduce broad placeholder phrases such as `未识别到稳定下钻维度` or `未发现明确过滤` when a safer "open the source report to confirm" boundary is already present.
46
+ For non-SQL reports, keep `Agent 使用摘要` useful for business routing, not a mechanical parser echo. Purpose and applicable questions must differ. Preserve evidenced metrics, dimensions, and filters; where unavailable, direct the reader to confirm in the source report instead of using broad `未识别/未发现` placeholders.
34
47
 
35
- During incremental compilation, inspect changed sources and their existing dependent pages. Update affected asset pages, indexes and cross-source conclusions; retain unrelated pages. Remove obsolete current assertions when a source changes or is removed. Integrate human evidence according to explicit scope, status and authority, never recency alone. Scope differences belong separately from same-scope contradictions. Preserve provenance to the actual uploaded source.
48
+ During incremental compilation, update changed sources and dependent pages, indexes, and cross-source conclusions; reuse unrelated pages and remove obsolete assertions. Adopted personal semantics share generated sources' paths, hashes, and add/replace/remove lifecycle. Integrate human evidence by scope, status, and authority rather than recency; distinguish scope differences from contradictions and preserve uploaded-source provenance.
@@ -0,0 +1,66 @@
1
+ # 项目语义知识库编译准则
2
+
3
+ 预置版本:1.1。导航分类版本:2.1。
4
+
5
+ ## 总体要求
6
+
7
+ 将项目语义源文件编译为有证据支撑的 Wiki,供分析 Agent 和知识维护者使用。保留项目层面的业务模型、资产定义、判断路径、问题路由、有明确适用范围的补充知识及其来源。当前数值、实体列表和趋势必须通过实时执行获取。项目名称、业务对象、事件名称、指标、资产身份和结论均以当前源文件为准,不得从本预置准则中推定。
8
+
9
+ 本文件是可持续复用的编译指令,不是要求再次生成编译准则的提示词。首次构建与增量编译使用同一套准则;全量重建也无需重新生成准则。只有经过明确审查的准则变更,才可升级预置版本。
10
+
11
+ ## 源文件职责与读取方式
12
+
13
+ - `raw/` 存放已上传的源文件,只允许读取;仅当前平台插件明确授权的 URL 更新除外。`wiki/` 存放编译后的页面。根目录 `index.md` 用于导航。
14
+ - 一个 ZIP 对应一个父源文件。使用本次执行提供的固定目录树,完整读取其中有效的业务文件,不得扫描目录树以外的内容。保留真实的父级 `sourceId` 和实际吸收的原始子文件精确路径;子文件不是独立源文件。URL 源文件同样保留平台提供的 `sourceId` 和实际吸收的子文件精确路径。不得编造身份标识或来源路径。
15
+ - README 是归档说明,不是业务证据。清单文件、刷新状态索引及其编号分片、哈希、日志和机器状态均不是业务证据,不得解码后编译为 Wiki 事实。应使用已编写的项目与资产源文档。
16
+ - CLI 管理的资产事实位于 `sources/assets/`;已采纳的个人语义位于 `sources/personal-semantics/`。人工补充的项目背景、会议、决策、纠正和参考资料属于独立证据。刷新时必须保留人工文件和未预期的手工修改。
17
+ - Markdown、文本、CSV、SVG 等文本格式优先使用 Read;长文本须分页读至末尾。获得完整、可用的语义后停止解析。读取失败时处理编码或格式问题,不得运行无关解析器。其他格式遵循当前插件的源文件解析约定,包括首选工具、有界回退、完整覆盖工作表或页面,以及失败退出方式。图片也优先使用 Read,只有语义提取失败或结果不可用时,才允许使用插件支持的回退方式。不得依据模型名称决定解析顺序,也不得在编译过程中安装解析器。
18
+ - 记录真实的失败情况,包括源文件身份、精确原始路径和失败原因。不得用笼统断言补齐缺失事实,也不得将解析失败表述为成功。
19
+
20
+ ## 上游已编写语义的复用约定
21
+
22
+ 上游 Agent 已编写业务含义、SQL 解释、计算事实、关系、业务域、召回边界和冲突说明;这些内容是可编辑的源文档。应复用其完整表述和证据,不得重复开展全项目提取、SQL 解析、语义规划或逐字段改写。只有发现具体矛盾或更新且适用范围明确的证据时,才重新审视已有解释;冲突无法解决时保留不确定性。
23
+
24
+ 项目概览首先呈现项目层面的业务模型:项目定位、受众角色、业务对象及其关系、业务判断链、资产作用和不覆盖的目标。源文件清单与数量是辅助信息。业务域应解释需要判断的状态、对象、信号、决策路径、主要资产、辅助资产、下钻资产、排除资产和适用边界,不得用报表列表代替。缺失的业务证据应明确标为未知;空的源文件骨架不得被扩写成虚构的业务模型。
25
+
26
+ 每项资产保留一份由 Agent 编写的业务摘要。SQL 报表摘要包含用途、参数、输出字段、统计粒度、筛选条件、限制、适用与不适用的问题,以及证据定位。非 SQL 报表应区分用途与可回答的问题,保留有证据的指标、维度、筛选条件和限制,不得机械复述解析器输出。定义细节缺失时,要求回到源报表确认。原始 SQL 和原始索引 JSON 保留在本地证据包中。
27
+
28
+ 不得将安全状态当作过时占位信息而忽略。保留认证状态、定义有效性、冲突、运行时策略和召回限制。已认证的资产仍可能存在定义未知的情况。仅作为依赖的资产可以解释定义、依赖关系和冲突,但不能独立证明业务状态或当前结果。遵守平台访问权限;权限不足时,不得改用其他项目或扩大范围来替代。
29
+
30
+ ## 权威性与补充知识
31
+
32
+ - 资产定义、治理事实和实时结果保留各自的权威性。决策或纠正仅在明确的目标、内容、日期、状态和适用范围内覆盖其他材料;时间较新本身不足以构成覆盖依据。
33
+ - 已采纳的背景知识用于补充上下文;偏好用于问题路由和呈现;经验用于提供注意事项;资产上下文用于注释已引用的既有资产。保留来源人员的显示名、来源或更新时间、采纳来源和适用范围,不得暴露后端私有用户标识。
34
+ - 将结论整合到其对应的项目、业务域、问答卡片或资产页面。采纳个人语义不意味着将个人偏好提升为全项目通用规则,也不意味着替换资产定义。在治理目录中保留知识采纳记录。目标已删除或失效时,必须明确按历史信息处理。
35
+ - 保留未知项和冲突。不得仅凭标题或分组标签推断业务含义、字段等价关系、唯一身份、事件定义或认证状态。合并事实前须核对业务对象、统计粒度、筛选条件、范围和时间。
36
+ - 公共事件分组是检索分类,不自动构成业务域或事件定义。仅纳入导出范围内有证据的事件,并保留实际名称。个人事件目录不得转为项目共享知识。
37
+
38
+ ## 导航与页面命名
39
+
40
+ 一级导航按以下五项顺序组织,让业务问题有入口,让资产目录提供进一步的证据:
41
+
42
+ 1. 项目概览:`wiki/project-overview.md`。
43
+ 2. 项目公共知识:`wiki/common/index.md`。
44
+ 3. 业务域:`wiki/domains/index.md`。
45
+ 4. 事件与资产目录:`wiki/asset-index/index.md`。
46
+ 5. 治理与来源追溯:`wiki/governance/index.md`。
47
+
48
+ 事件分组保留在 `wiki/event-map/index.md` 下,通过资产目录访问。问答卡片归入对应业务域;单项资产页面放入匹配的报表、看板、指标或元数据分类。新增页面应进入适当的既有分类。已采纳个人语义属于来源信息,不单独设为顶层知识分类。页面名使用基于标题的稳定短横线命名(kebab-case),ID 仅作为辅助身份信息。
49
+
50
+ 根目录 `index.md` 与存在时的 `wiki/index.md` 通过相同的五个一级入口导航。根目录 `index.md` 不含 YAML 元数据头、来源详情、格式化器(Formatter)内容、构建日志或页面元数据。一级模块最多八个,直接 Wiki 链接最多十二个。不得平铺单项资产,也不得使用裸数字作为链接文本。同类详情页面达到三个时使用分类索引;完整站点地图放在下级索引中。每个已发布页面都必须能从根目录沿链接逐级访问。
51
+
52
+ ## 问题路由与召回卡片
53
+
54
+ 根导航首先按问题意图提供入口。业务状态和当前数值问题进入匹配的业务域与卡片;指定资产、字段、定义和依赖问题进入资产目录与治理;共享产品背景进入公共知识。没有匹配卡片时,在相关业务域内按对象、回答粒度、筛选条件和时间筛选资产;证据不足时明确说明缺口。
55
+
56
+ 保留每张卡片的触发问题、适用范围、对象与回答粒度、必需参数、直接指向资产的条件分支、排除或不可互换的资产、停止条件,以及资产不可用、证据冲突或无匹配时的回退方式。不得将条件分支替换为统一的“首选资产、失败后备用资产”列表。缺失的路由或参数必须明确保留为缺口,不得编造。找到与对象、粒度、范围和时间匹配的最小权威证据集合后,停止扩展检索。当前数值、实体列表、趋势和明细结果必须通过已获授权的实时报表或查询执行获取。
57
+
58
+ ## 页面元数据与来源追溯
59
+
60
+ 每个 `wiki/**/*.md` 页面均以 YAML 元数据头开始,包含 `title`、`type`、`depth`、`related` 和 `source`;使用 ZIP 或 URL 证据时还须包含 `sourceId`。`type` 取值为 `overview`、`concept`、`guide`、`reference`、`synthesis` 或 `faq`;`depth` 取值为 `overview`、`detail` 或 `howto`;`summary` 为可选字段。使用真实的原始文件相对路径和真实父源文件身份,不得因存在来源字段就编造路径。正文中的来源引用使用纯文本路径,不使用指向原始文件的 Markdown 链接。Wiki 正文不得暴露凭据、宿主机路径或内部构建与调试标记。
61
+
62
+ ## 刷新与发布前检查
63
+
64
+ 增量编译应更新变化的源文件、依赖页面、索引和跨源结论,复用无关页面。源文件发生变化或消失时,移除过时的当前结论。已采纳个人语义同样遵循新增、替换、删除的生命周期。保留人工证据、手工内容和未变化的事实。导航迁移可以要求全量重建 Wiki,但不因此重新生成或替换正式准则。
65
+
66
+ 发布前检查元数据头、真实来源、链接、根目录可达性、全部五个一级入口、卡片分支和停止条件、权威性限制、冲突以及过时结论。相关链接与索引应一并修复。源文件和页面数量完整不等于回答质量合格。发布必须成功结束,且抽查召回页面应保留源文件定义、人工补充内容的适用边界和导航。发布失败时,不得将源文件已上传或编译已排队表述为已发布知识得到更新。
@@ -744,6 +744,7 @@ function domainReportEvents(report) {
744
744
  .map((item) => namedCodeFact(item.title, item.resource_key))
745
745
  .filter(isUsefulDomainEventFact);
746
746
  const occurrenceEvents = array(report.metric_occurrences)
747
+ .filter((item) => !isInternalMetricExpression(item.event_title))
747
748
  .map((item) => namedCodeFact(item.event_title, item.event_name))
748
749
  .filter(isUsefulDomainEventFact);
749
750
  return [...refs, ...occurrenceEvents];
@@ -4,6 +4,7 @@ import { normalizeSourceForHash } from './precompiled-source.mjs';
4
4
  import path from 'node:path';
5
5
  import { createRequire } from 'node:module';
6
6
  import { createHash, randomBytes } from 'node:crypto';
7
+ import { decodeRefreshState, encodeRefreshState, readRefreshStateIndex } from './refresh-state-parts.mjs';
7
8
 
8
9
  const args = parseArgs(process.argv.slice(2));
9
10
  const sourceDir = path.resolve(required('source-dir'));
@@ -30,6 +31,8 @@ const flatManifest = readManifest(path.join(sourceRoot, 'kb-upload-manifest.json
30
31
  const next = buildSourceTreeManifest(flatManifest, { requireLocalContent: true });
31
32
  const nextBaseline = shouldEmbedRefreshStateBaseline ? refreshStateBaseline(next) : null;
32
33
  const entries = buildSourceTreeEntries(next, nextBaseline);
34
+ const stateEntry = entries.find((entry) => entry.source_kind === 'refresh-state');
35
+ const stateParts = entries.filter((entry) => entry.source_kind === 'refresh-state-part');
33
36
 
34
37
  materializeTree(treeOutput, force, (staging) => {
35
38
  for (const entry of entries) writeText(path.join(staging, entry.path), entry.content);
@@ -41,20 +44,33 @@ const result = {
41
44
  namespace: next.namespace,
42
45
  root_path: next.root_path,
43
46
  source_count: next.source_count,
47
+ refresh_state_part_count: stateParts.length,
44
48
  tree_output: treeOutput,
45
49
  manifest: manifestOutput,
46
50
  };
47
51
 
48
52
  if (previousManifestPath || previousRefreshStatePath) {
49
53
  const previousState = previousManifestPath
50
- ? { manifest: readManifest(previousManifestPath, { allowFlat: true, allowTree: true }), baseline: null }
54
+ ? { manifest: readManifest(previousManifestPath, { allowFlat: true, allowTree: true }), baseline: null, index: null }
51
55
  : readRefreshStateBaseline(previousRefreshStatePath);
52
56
  const previous = buildSourceTreeManifest(previousState.manifest, { requireLocalContent: false });
57
+ if (previousManifestPath && previousTreeRoot) {
58
+ const previousRefresh = previous.sources.find((source) => source.source_kind === 'refresh-state');
59
+ if (previousRefresh) {
60
+ const file = path.join(previousTreeRoot, previousRefresh.file);
61
+ if (fs.existsSync(file)) {
62
+ const content = readText(file);
63
+ previousState.index = readRefreshStateIndex(content);
64
+ previousState.index_hash = previousState.index ? sha256(content) : null;
65
+ }
66
+ }
67
+ }
53
68
  if (previousTreeRoot) rehashPreviousTreeSources(previous, previousTreeRoot);
54
69
  if (previous.namespace !== next.namespace || previous.semantic_project_id !== next.semantic_project_id) {
55
70
  throw new Error('Wiki namespace/project changed; stop.');
56
71
  }
57
72
  const semanticPlanDiff = diffSemanticPlanFragments(previous.semantic_plan_state, next.semantic_plan_state);
73
+ const taxonomyChanged = previous.navigation_taxonomy_version !== next.navigation_taxonomy_version;
58
74
  const changedPlanFragments = new Set(semanticPlanDiff.changed_fragments);
59
75
  const before = new Map(previous.sources.map((source) => [source.file, source]));
60
76
  const after = new Map(next.sources.map((source) => [source.file, source]));
@@ -68,6 +84,36 @@ if (previousManifestPath || previousRefreshStatePath) {
68
84
  for (const source of previous.sources) {
69
85
  if (!after.has(source.file)) actions.push(actionForSource('remove', source, source, changedPlanFragments));
70
86
  }
87
+ if (stateEntry) {
88
+ const oldIndex = previousState.index;
89
+ const oldParts = new Map((oldIndex?.parts ?? []).map((part) => [part.file, part]));
90
+ const nextIndex = readRefreshStateIndex(stateEntry.content);
91
+ const oldState = before.get(stateEntry.path);
92
+ if (oldState && oldState.content_hash === after.get(stateEntry.path)?.content_hash
93
+ && (!oldIndex || previousState.index_hash !== sha256(stateEntry.content))) {
94
+ actions.push(actionForSource('replace', after.get(stateEntry.path), oldState, changedPlanFragments));
95
+ }
96
+ for (const part of stateParts) {
97
+ const old = oldParts.get(path.posix.basename(part.path));
98
+ if (old?.sha256 !== sha256(part.content)) actions.push(statePartAction(old ? 'replace' : 'add', part, old));
99
+ oldParts.delete(path.posix.basename(part.path));
100
+ }
101
+ for (const old of oldParts.values()) {
102
+ actions.push(statePartAction('remove', { path: path.posix.join(path.posix.dirname(stateEntry.path), old.file) }, old));
103
+ }
104
+ if (!nextIndex) throw new Error('Packaged refresh state index is missing.');
105
+ const stateAction = actions.find((action) => action.path === stateEntry.path);
106
+ if (stateAction) {
107
+ const unchangedPosition = unchanged.indexOf(stateEntry.path);
108
+ if (unchangedPosition !== -1) unchanged.splice(unchangedPosition, 1);
109
+ stateAction.content_hash = sha256(stateEntry.content);
110
+ if (oldIndex) stateAction.previous_content_hash = previousState.index_hash;
111
+ }
112
+ const ordinaryActions = actions.filter((action) => !['refresh-state', 'refresh-state-part'].includes(action.source_kind));
113
+ const partWrites = actions.filter((action) => action.source_kind === 'refresh-state-part' && action.action !== 'remove');
114
+ const partRemovals = actions.filter((action) => action.source_kind === 'refresh-state-part' && action.action === 'remove');
115
+ actions.splice(0, actions.length, ...ordinaryActions, ...partWrites, ...(stateAction ? [stateAction] : []), ...partRemovals);
116
+ }
71
117
  const affectedBySemanticPlan = next.sources
72
118
  .map((source) => ({
73
119
  path: source.file,
@@ -80,7 +126,14 @@ if (previousManifestPath || previousRefreshStatePath) {
80
126
  Object.assign(result, {
81
127
  actions,
82
128
  unchanged,
83
- compile_mode: actions.length ? 'incremental' : 'skip',
129
+ compile_mode: taxonomyChanged ? 'full' : actions.some((action) => !['refresh-state', 'refresh-state-part'].includes(action.source_kind)) ? 'incremental' : 'skip',
130
+ navigation_taxonomy_migration: taxonomyChanged ? {
131
+ from: previous.navigation_taxonomy_version ?? 'legacy',
132
+ to: next.navigation_taxonomy_version,
133
+ requires_schema_force: false,
134
+ requires_schema_review: true,
135
+ reason: 'navigation_taxonomy_changed',
136
+ } : null,
84
137
  semantic_plan_diff: semanticPlanDiff,
85
138
  affected_by_semantic_plan: affectedBySemanticPlan,
86
139
  previous_refresh_state_baseline: previousState.baseline,
@@ -89,9 +142,9 @@ if (previousManifestPath || previousRefreshStatePath) {
89
142
 
90
143
  if (outputArchive) {
91
144
  if (fs.existsSync(outputArchive) && !force) throw new Error('Archive already exists. Pass --force to replace it.');
92
- const archiveEntries = next.sources.map((source) => ({
93
- name: source.file,
94
- content: fs.readFileSync(path.join(treeOutput, source.file)),
145
+ const archiveEntries = entries.map((entry) => ({
146
+ name: entry.path,
147
+ content: fs.readFileSync(path.join(treeOutput, entry.path)),
95
148
  })).sort((a, b) => a.name.localeCompare(b.name, 'en'));
96
149
  await writeZip(outputArchive, archiveEntries);
97
150
  Object.assign(result, { archive: outputArchive, archive_bytes: fs.statSync(outputArchive).size });
@@ -142,6 +195,7 @@ function buildSourceTreeManifest(manifest, options) {
142
195
  return {
143
196
  schema_version: '1.1',
144
197
  manifest_kind: 'ae_project_semantic_kb_source_tree',
198
+ navigation_taxonomy_version: manifest.navigation_taxonomy_version ?? 'legacy',
145
199
  namespace: manifest.namespace,
146
200
  semantic_project_id: manifest.semantic_project_id,
147
201
  project_name: manifest.project_name ?? null,
@@ -161,25 +215,23 @@ function buildSourceTreeEntries(manifest, refreshStateBaseline = null) {
161
215
  .filter((item) => item.source_kind !== 'filing-guide')
162
216
  .map((item) => [item.display_name, item.file]));
163
217
  const guides = new Map(sourceDirectoryReadmes(manifest).map((entry) => [entry.path, entry.content]));
164
- return manifest.sources.map((source) => {
218
+ return manifest.sources.flatMap((source) => {
165
219
  if (source.source_kind === 'filing-guide') {
166
220
  const content = guides.get(source.file);
167
221
  if (!content) throw new Error(`Missing filing guide content for ${source.file}.`);
168
- return { path: source.file, content };
222
+ return [{ path: source.file, content, source_kind: source.source_kind }];
169
223
  }
170
- if (refreshStateBaseline && source.source_kind === 'refresh-state') {
171
- return {
172
- path: source.file,
173
- content: embedRefreshStateBaseline(
174
- rewriteLinksForTree(readText(path.join(sourceRoot, source.display_name)), source.display_name, flatToTree),
175
- refreshStateBaseline,
176
- ),
177
- };
224
+ if (source.source_kind === 'refresh-state') {
225
+ const content = rewriteLinksForTree(readText(path.join(sourceRoot, source.display_name)), source.display_name, flatToTree);
226
+ const state = encodeRefreshState(content, source.file, source.content_hash, refreshStateBaseline);
227
+ return [{ path: source.file, content: state.indexContent, source_kind: 'refresh-state' },
228
+ ...state.parts.map((part) => ({ ...part, source_kind: 'refresh-state-part' }))];
178
229
  }
179
- return {
230
+ return [{
180
231
  path: source.file,
181
232
  content: rewriteLinksForTree(readText(path.join(sourceRoot, source.display_name)), source.display_name, flatToTree),
182
- };
233
+ source_kind: source.source_kind,
234
+ }];
183
235
  });
184
236
  }
185
237
 
@@ -196,22 +248,9 @@ function refreshStateBaseline(manifest) {
196
248
  };
197
249
  }
198
250
 
199
- function embedRefreshStateBaseline(content, baseline) {
200
- const payload = parseRefreshStatePayload(content);
201
- const nextPayload = {
202
- ...payload,
203
- source_tree_baseline: {
204
- ...baseline,
205
- source_tree_manifest: baseline.source_tree_manifest,
206
- },
207
- };
208
- const encoded = Buffer.from(JSON.stringify(nextPayload, null, 2)).toString('base64url');
209
- return content.replace(/<!-- pskb_refresh_state_base64\n[\s\S]*?\n-->/,
210
- `<!-- pskb_refresh_state_base64\n${encoded}\n-->`);
211
- }
212
-
213
251
  function readRefreshStateBaseline(file) {
214
- const payload = parseRefreshStatePayload(readText(file));
252
+ const content = readText(file);
253
+ const { payload, index } = decodeRefreshState(content, file);
215
254
  const baseline = payload.source_tree_baseline ?? null;
216
255
  const manifest = baseline?.source_tree_manifest;
217
256
  if (!manifest) throw new Error('Refresh state does not contain source_tree_baseline.source_tree_manifest.');
@@ -220,8 +259,14 @@ function readRefreshStateBaseline(file) {
220
259
  if (expectedHash && expectedHash !== actualHash) {
221
260
  throw new Error('Refresh state source_tree_manifest hash does not match its manifest_hash.');
222
261
  }
262
+ const stateSource = manifest.sources.find((source) => source.source_kind === 'refresh-state');
263
+ if (index && stateSource?.content_hash !== index.normalized_content_hash) {
264
+ throw new Error('Refresh state normalized content hash does not match its source tree manifest.');
265
+ }
223
266
  return {
224
267
  manifest,
268
+ index,
269
+ index_hash: index ? sha256(content) : null,
225
270
  baseline: {
226
271
  schema_version: clean(baseline.schema_version) || null,
227
272
  source_id: clean(baseline.source_id) || null,
@@ -233,12 +278,6 @@ function readRefreshStateBaseline(file) {
233
278
  };
234
279
  }
235
280
 
236
- function parseRefreshStatePayload(content) {
237
- const match = String(content).match(/<!-- pskb_refresh_state_base64\n([A-Za-z0-9_=\-\s]+)\n-->/);
238
- if (!match) throw new Error('Refresh state base64 payload is missing.');
239
- return JSON.parse(Buffer.from(match[1].replace(/\s+/g, ''), 'base64url').toString('utf8'));
240
- }
241
-
242
281
  function sourceTreeManifestHash(manifest) {
243
282
  return sha256(stableJson(manifest));
244
283
  }
@@ -246,7 +285,7 @@ function sourceTreeManifestHash(manifest) {
246
285
  function sourceDirectoryReadmes(manifest) {
247
286
  const project = clean(manifest.project_name) || `Project ${manifest.semantic_project_id}`;
248
287
  const lines = [
249
- [`${rootPath}/README.md`, `# Source Directory\n\nThis directory is the writable source space for ${project} project knowledge. README files are filing guides only; they are not business evidence.\n\nPut source material by nature. CLI-managed asset facts live under assets/. Human context such as project background, meetings, decisions, corrections, and references must not be removed by an asset refresh.\n\nThe expected compiled Wiki should be organized for consumption: index.md, wiki/project/, wiki/domains/, wiki/semantics/, wiki/assets/, wiki/decisions/, and wiki/review/.\n`],
288
+ [`${rootPath}/README.md`, `# Source Directory\n\nThis directory is the writable source space for ${project} project knowledge. README files are filing guides only; they are not business evidence.\n\nPut source material by nature. CLI-managed asset facts live under assets/. Human context such as project background, meetings, decisions, corrections, and references must not be removed by an asset refresh.\n\nThe compiled Wiki navigation follows project overview, shared project knowledge, business domains, event and asset catalog, and governance with provenance. Event groups are lookup indexes, not business domains.\n`],
250
289
  [`${rootPath}/project/README.md`, '# Project Sources\n\nBackground, goals, business scope, terminology, and ownership notes. This guide is not evidence.\n'],
251
290
  [`${rootPath}/assets/README.md`, '# Asset Sources\n\nCLI-managed structured asset facts. Refresh may change files owned by the project semantic asset package only.\n'],
252
291
  [`${rootPath}/assets/dashboards/README.md`, '# Dashboard Asset Sources\n\nDashboard facts exported by CLI.\n'],
@@ -254,6 +293,11 @@ function sourceDirectoryReadmes(manifest) {
254
293
  [`${rootPath}/assets/metrics/README.md`, '# Metric Asset Sources\n\nReusable metric facts exported by CLI.\n'],
255
294
  [`${rootPath}/assets/metadata/README.md`, '# Metadata Asset Sources\n\nEvents, properties, tags, cohorts, and other selectable metadata facts exported by CLI.\n'],
256
295
  [`${rootPath}/business/README.md`, '# Business Sources\n\nBusiness rules, metric definitions, domain context, process notes, and user-facing question patterns.\n'],
296
+ [`${rootPath}/personal-semantics/README.md`, '# Adopted Personal Semantic Sources\n\nReviewed personal semantics promoted into project knowledge. Refresh may replace these CLI-managed files from adopted personal semantic records.\n'],
297
+ [`${rootPath}/personal-semantics/background/README.md`, '# Background Personal Semantics\n\nAdopted personal background context for project and domain interpretation.\n'],
298
+ [`${rootPath}/personal-semantics/preference/README.md`, '# Preference Personal Semantics\n\nAdopted personal preferences for routing, answer style, and source selection.\n'],
299
+ [`${rootPath}/personal-semantics/experience/README.md`, '# Experience Personal Semantics\n\nAdopted personal analysis experience, heuristics, and cautions.\n'],
300
+ [`${rootPath}/personal-semantics/asset-context/README.md`, '# Asset Context Personal Semantics\n\nAdopted personal context scoped to referenced project assets.\n'],
257
301
  [`${rootPath}/meetings/README.md`, '# Meeting Sources\n\nMeeting notes and discussion records. They can provide context, but do not override confirmed decisions by recency alone.\n'],
258
302
  [`${rootPath}/decisions/README.md`, '# Decision Sources\n\nConfirmed decisions must state confirmer, status, date, and applicable scope.\n'],
259
303
  [`${rootPath}/corrections/README.md`, '# Correction Sources\n\nCorrections must identify the target material, corrected content, reason, date, and applicable scope.\n'],
@@ -270,15 +314,27 @@ function sourceTreePath(source) {
270
314
  if (assetType === 'report') return `${rootPath}/assets/reports/${base}`;
271
315
  if (assetType === 'metric') return `${rootPath}/assets/metrics/${base}`;
272
316
  if (assetType === 'metadata' || kind === 'metadata') return `${rootPath}/assets/metadata/${base}`;
317
+ if (source.source_path.startsWith('wiki/event-map/')) return `${rootPath}/assets/metadata/${base}`;
273
318
  if (source.source_path === 'wiki/project-overview.md') return `${rootPath}/project/${base}`;
319
+ if (source.source_path.startsWith('wiki/common/') || source.source_path.startsWith('wiki/asset-index/')) return `${rootPath}/project/${base}`;
274
320
  if (kind === 'refresh-state') return `${rootPath}/project/${base}`;
275
321
  if (kind === 'domain' || kind === 'recall-card') return `${rootPath}/business/${base}`;
322
+ if (kind === 'personal-semantic') return personalSemanticSourceTreePath(source, base);
276
323
  if (kind === 'index' || kind === 'manifest') return `${rootPath}/project/${base}`;
277
324
  if (kind === 'governance') return `${rootPath}/references/governance/${base}`;
278
325
  if (kind === 'appendix' || kind === 'source-container') return `${rootPath}/references/${base}`;
279
326
  return `${rootPath}/references/${base}`;
280
327
  }
281
328
 
329
+ function personalSemanticSourceTreePath(source, base) {
330
+ const match = clean(source.source_path).match(/^wiki\/personal-semantics(?:\/([^/]+))?\//);
331
+ const folder = match?.[1];
332
+ if (['background', 'preference', 'experience', 'asset-context'].includes(folder)) {
333
+ return `${rootPath}/personal-semantics/${folder}/${base}`;
334
+ }
335
+ return `${rootPath}/personal-semantics/${base}`;
336
+ }
337
+
282
338
  function sourceTreeBaseName(source) {
283
339
  const original = path.posix.basename(source.file);
284
340
  if (!source.asset_key) return original;
@@ -426,10 +482,23 @@ function rehashPreviousTreeSources(manifest, root) {
426
482
  for (const source of manifest.sources) {
427
483
  const file = path.resolve(root, source.file);
428
484
  if (!file.startsWith(`${root}${path.sep}`) || !fs.existsSync(file)) continue;
429
- source.content_hash = sha256(normalizeContentForHash(readText(file)));
485
+ const content = readText(file);
486
+ source.content_hash = source.source_kind === 'refresh-state' && readRefreshStateIndex(content)
487
+ ? readRefreshStateIndex(content).normalized_content_hash
488
+ : sha256(normalizeContentForHash(content));
430
489
  }
431
490
  }
432
491
 
492
+ function statePartAction(action, part, previous) {
493
+ return {
494
+ action, path: part.path, ...(action === 'remove' ? {} : { file: path.join(treeOutput, part.path) }),
495
+ source_kind: 'refresh-state-part', asset_type: null, asset_key: null,
496
+ content_hash: action === 'remove' ? null : sha256(part.content),
497
+ previous_content_hash: previous?.sha256 ?? null,
498
+ semantic_plan_fragment_refs: [], changed_semantic_plan_fragments: [],
499
+ };
500
+ }
501
+
433
502
  function parseArgs(tokens) {
434
503
  const parsed = {};
435
504
  for (let i = 0; i < tokens.length; i += 1) {
@@ -212,7 +212,7 @@ function decideCompileMode({ actionCount, baselineCount, nextCount, fullThreshol
212
212
  mode: 'full',
213
213
  reason: forceReasons[0],
214
214
  changeRatio,
215
- requiresSchemaForce: true,
215
+ requiresSchemaForce: false,
216
216
  schemaForceReasons: forceReasons,
217
217
  };
218
218
  }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { runCompanyKb, invokeCompanyKbCli } from './company-kb.mjs';
7
+
8
+ const here = import.meta.dirname;
9
+ const missingCodes = new Set(['SCHEMA_NOT_FOUND', 'KB_SCHEMA_IMPORT_SCHEMA_NOT_FOUND']);
10
+
11
+ // Preparation never generates, validates with AI, applies rules, or compiles sources.
12
+ export async function prepareCompileSchema({ name, host, output, file, requestId }, invoke) {
13
+ if (!name || !host || !output) throw new Error('--name, --host and --output are required.');
14
+ if (fs.existsSync(output)) throw new Error('Output already exists; choose a new path.');
15
+ const call = (command, flags = []) => runCompanyKb(command, ['--name', name, '--host', host, ...flags], invoke);
16
+ const status = (await call('+status')).data;
17
+ if (status.activeRunId || status.schemaStatus === 'generating' || status.buildStatus === 'compiling'
18
+ || ['schema_generating', 'wiki_compiling', 'wiki_recovering', 'wiki_review_required'].includes(status.status)
19
+ || status.publicationReview) {
20
+ throw new Error(`Knowledge base is busy or requires publication review: ${status.status}`);
21
+ }
22
+ let absent = false;
23
+ try {
24
+ await call('+schema-download', ['--output', output]);
25
+ } catch (error) {
26
+ if (!missingCodes.has(error.code)) throw error;
27
+ absent = true;
28
+ }
29
+ if (!absent && status.schemaValidation?.valid === true) {
30
+ const bytes = fs.readFileSync(output);
31
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
32
+ if (bytes.length > 262144 || [...text.trim()].length < 20) throw new Error('Downloaded formal rules failed basic checks.');
33
+ return { schema_action: 'reuse', compile_ready: true, schema_file: path.resolve(output),
34
+ schema_hash: createHash('sha256').update(bytes).digest('hex') };
35
+ }
36
+ // New KBs contain a placeholder file even though no effective rules exist yet.
37
+ const uninitialized = status.schemaStatus === 'pending' && ['empty', 'schema_pending'].includes(status.status)
38
+ && (!status.schemaValidation || status.schemaValidation.reason === 'DEFAULT_TEMPLATE');
39
+ if (!uninitialized) throw new Error('Formal rules are invalid or their state is unknown; review explicitly before initialization.');
40
+ if (!requestId) throw new Error('--request-id is required to initialize rules; reuse it when resuming the same upload.');
41
+ const candidate = (await call('+schema-import', ['--file', file || path.join(here, 'default-compile-schema.md'),
42
+ '--request-id', requestId])).data;
43
+ return { schema_action: 'review_import', compile_ready: false, candidate,
44
+ ...(absent ? {} : { schema_file: path.resolve(output) }) };
45
+ }
46
+
47
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
48
+ try {
49
+ const args = process.argv.slice(2), values = {};
50
+ const allowed = new Set(['name', 'host', 'output', 'file', 'request-id']);
51
+ for (let i = 0; i < args.length; i += 2) {
52
+ const key = args[i].replace(/^--/, '');
53
+ if (!args[i].startsWith('--') || !allowed.has(key) || !args[i + 1] || args[i + 1].startsWith('--')) {
54
+ throw new Error(`Invalid argument: ${args[i]}`);
55
+ }
56
+ values[key] = args[i + 1];
57
+ }
58
+ const result = await prepareCompileSchema({ ...values, requestId: values['request-id'] }, invokeCompanyKbCli);
59
+ console.log(JSON.stringify(result, null, 2));
60
+ } catch (error) {
61
+ console.error(error.message);
62
+ process.exitCode = 1;
63
+ }
64
+ }
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { brotliCompressSync, brotliDecompressSync, constants } from 'node:zlib';
4
+ import { createHash } from 'node:crypto';
5
+
6
+ const PART_DATA_LENGTH = 15_000;
7
+ const MAX_PART_BYTES = 16 * 1024;
8
+ const MAX_PAYLOAD_BYTES = 64 * 1024 * 1024;
9
+ const INDEX_MARKER = /<!-- pskb_refresh_state_index\n([^\n]+)\n-->/;
10
+
11
+ const sha256 = (value) => createHash('sha256').update(value).digest('hex');
12
+
13
+ export function readLegacyRefreshState(content) {
14
+ const match = String(content).match(/<!-- pskb_refresh_state_base64\n([A-Za-z0-9_=\-\s]+)\n-->/);
15
+ if (!match) throw new Error('Refresh state payload is missing.');
16
+ return JSON.parse(Buffer.from(match[1].replace(/\s+/g, ''), 'base64url').toString('utf8'));
17
+ }
18
+
19
+ export function readRefreshStateIndex(content) {
20
+ const match = String(content).match(INDEX_MARKER);
21
+ if (!match) return null;
22
+ if (Buffer.byteLength(content) > MAX_PART_BYTES) throw new Error('Refresh state index exceeds 16 KiB.');
23
+ const index = JSON.parse(match[1]);
24
+ if (index.schema_version !== '2.0' || index.encoding !== 'brotli-base64url'
25
+ || !/^[a-f0-9]{64}$/.test(index.payload_sha256)
26
+ || !/^[a-f0-9]{64}$/.test(index.normalized_content_hash)
27
+ || !Number.isInteger(index.payload_bytes) || index.payload_bytes < 1 || index.payload_bytes > MAX_PAYLOAD_BYTES
28
+ || !Array.isArray(index.parts) || !index.parts.length) {
29
+ throw new Error('Invalid refresh state index.');
30
+ }
31
+ const seen = new Set();
32
+ for (const part of index.parts) {
33
+ if (!/^[-a-z0-9]+-refresh-state-part-\d{3}\.md$/.test(part.file)
34
+ || !/^[a-f0-9]{64}$/.test(part.sha256)
35
+ || !Number.isInteger(part.bytes) || part.bytes < 1 || part.bytes > MAX_PART_BYTES
36
+ || seen.has(part.file)) throw new Error('Invalid refresh state part metadata.');
37
+ seen.add(part.file);
38
+ }
39
+ return index;
40
+ }
41
+
42
+ export function encodeRefreshState(content, statePath, normalizedContentHash, baseline = null) {
43
+ if (!/^[a-f0-9]{64}$/.test(normalizedContentHash)) throw new Error('Invalid normalized refresh state hash.');
44
+ const payload = readLegacyRefreshState(content);
45
+ if (baseline) payload.source_tree_baseline = baseline;
46
+ const raw = Buffer.from(JSON.stringify(payload));
47
+ const encoded = brotliCompressSync(raw, { params: { [constants.BROTLI_PARAM_QUALITY]: 11 } }).toString('base64url');
48
+ const stem = path.posix.basename(statePath, '.md');
49
+ const parts = [];
50
+ for (let offset = 0; offset < encoded.length; offset += PART_DATA_LENGTH) {
51
+ const number = parts.length + 1;
52
+ const file = `${stem}-part-${String(number).padStart(3, '0')}.md`;
53
+ const data = encoded.slice(offset, offset + PART_DATA_LENGTH);
54
+ const partContent = `# Refresh State Part ${number}\n\nMachine state only. Do not compile as business evidence.\n\n<!-- pskb_refresh_state_part\n${data}\n-->\n`;
55
+ const bytes = Buffer.byteLength(partContent);
56
+ if (bytes > MAX_PART_BYTES) throw new Error('Refresh state part exceeds 16 KiB.');
57
+ parts.push({ file, path: path.posix.join(path.posix.dirname(statePath), file), content: partContent, sha256: sha256(partContent), bytes });
58
+ }
59
+ const index = {
60
+ schema_version: '2.0', encoding: 'brotli-base64url', payload_sha256: sha256(raw),
61
+ payload_bytes: raw.length, normalized_content_hash: normalizedContentHash,
62
+ parts: parts.map(({ file, sha256: hash, bytes }) => ({ file, sha256: hash, bytes })),
63
+ };
64
+ const indexContent = `# Refresh State\n\nMachine state index. Do not compile as business evidence. Read parts only with the CLI refresh-state reader.\n\n<!-- pskb_refresh_state_index\n${JSON.stringify(index)}\n-->\n`;
65
+ if (Buffer.byteLength(indexContent) > MAX_PART_BYTES) throw new Error('Refresh state index exceeds 16 KiB.');
66
+ return { indexContent, parts };
67
+ }
68
+
69
+ export function decodeRefreshState(content, indexFile) {
70
+ const index = readRefreshStateIndex(content);
71
+ if (!index) return { payload: readLegacyRefreshState(content), index: null };
72
+ const stem = path.basename(indexFile, '.md');
73
+ const chunks = index.parts.map((part, position) => {
74
+ if (part.file !== `${stem}-part-${String(position + 1).padStart(3, '0')}.md`) {
75
+ throw new Error(`Unexpected refresh state part name: ${part.file}.`);
76
+ }
77
+ const file = path.join(path.dirname(indexFile), part.file);
78
+ const data = fs.readFileSync(file);
79
+ if (data.length !== part.bytes || data.length > MAX_PART_BYTES || sha256(data) !== part.sha256) {
80
+ throw new Error(`Refresh state part checksum mismatch: ${part.file}.`);
81
+ }
82
+ const match = data.toString('utf8').match(/<!-- pskb_refresh_state_part\n([A-Za-z0-9_-]+)\n-->/);
83
+ if (!match) throw new Error(`Invalid refresh state part: ${part.file}.`);
84
+ return match[1];
85
+ });
86
+ const raw = brotliDecompressSync(Buffer.from(chunks.join(''), 'base64url'), { maxOutputLength: MAX_PAYLOAD_BYTES });
87
+ if (raw.length !== index.payload_bytes || sha256(raw) !== index.payload_sha256) {
88
+ throw new Error('Refresh state payload checksum mismatch.');
89
+ }
90
+ return { payload: JSON.parse(raw.toString('utf8')), index };
91
+ }