@istuen/pt 0.1.0

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 (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/dist/agent/api-bridge.d.ts +8 -0
  4. package/dist/agent/api-bridge.js +83 -0
  5. package/dist/agent/index.d.ts +2 -0
  6. package/dist/agent/index.js +3 -0
  7. package/dist/agent/pi-adapter.d.ts +35 -0
  8. package/dist/agent/pi-adapter.js +251 -0
  9. package/dist/agent/registry.d.ts +4 -0
  10. package/dist/agent/registry.js +40 -0
  11. package/dist/asset-health.d.ts +50 -0
  12. package/dist/asset-health.js +267 -0
  13. package/dist/asset-pack/loader.d.ts +47 -0
  14. package/dist/asset-pack/loader.js +121 -0
  15. package/dist/asset-pack/manifest.d.ts +23 -0
  16. package/dist/asset-pack/manifest.js +96 -0
  17. package/dist/asset-pack/md-file-pack.d.ts +41 -0
  18. package/dist/asset-pack/md-file-pack.js +153 -0
  19. package/dist/asset-pack/validate.d.ts +55 -0
  20. package/dist/asset-pack/validate.js +131 -0
  21. package/dist/builtin/assets/blueprints/dev-knowledge.blueprint.yaml +13 -0
  22. package/dist/builtin/assets/domains/agent-info.md +15 -0
  23. package/dist/builtin/assets/domains/authoring.md +201 -0
  24. package/dist/builtin/assets/domains/pack-repair.md +47 -0
  25. package/dist/builtin/assets/domains/project-analysis.md +52 -0
  26. package/dist/builtin/assets/domains/usage.md +81 -0
  27. package/dist/builtin/assets/domains/user-info.md +15 -0
  28. package/dist/builtin/assets/profiles/guide.profile.md +28 -0
  29. package/dist/commands.d.ts +69 -0
  30. package/dist/commands.js +344 -0
  31. package/dist/compile/agent-context.d.ts +48 -0
  32. package/dist/compile/agent-context.js +423 -0
  33. package/dist/compile/index.d.ts +1 -0
  34. package/dist/compile/index.js +5 -0
  35. package/dist/compile/resolve-use.d.ts +36 -0
  36. package/dist/compile/resolve-use.js +171 -0
  37. package/dist/compile/type-guards.d.ts +29 -0
  38. package/dist/compile/type-guards.js +107 -0
  39. package/dist/config.d.ts +31 -0
  40. package/dist/config.js +133 -0
  41. package/dist/constants.d.ts +52 -0
  42. package/dist/constants.js +81 -0
  43. package/dist/diagnostics.d.ts +14 -0
  44. package/dist/diagnostics.js +29 -0
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.js +838 -0
  47. package/dist/injection-status.d.ts +45 -0
  48. package/dist/injection-status.js +134 -0
  49. package/dist/log.d.ts +64 -0
  50. package/dist/log.js +139 -0
  51. package/dist/manual-session.d.ts +24 -0
  52. package/dist/manual-session.js +152 -0
  53. package/dist/manual-track.d.ts +35 -0
  54. package/dist/manual-track.js +107 -0
  55. package/dist/parse/blueprint.d.ts +5 -0
  56. package/dist/parse/blueprint.js +59 -0
  57. package/dist/parse/domain-renderers.d.ts +8 -0
  58. package/dist/parse/domain-renderers.js +115 -0
  59. package/dist/parse/domain.d.ts +7 -0
  60. package/dist/parse/domain.js +69 -0
  61. package/dist/parse/index.d.ts +9 -0
  62. package/dist/parse/index.js +223 -0
  63. package/dist/parse/profile.d.ts +18 -0
  64. package/dist/parse/profile.js +117 -0
  65. package/dist/parse/ref-resolver.d.ts +63 -0
  66. package/dist/parse/ref-resolver.js +174 -0
  67. package/dist/parse/shared.d.ts +76 -0
  68. package/dist/parse/shared.js +273 -0
  69. package/dist/profile-persist.d.ts +22 -0
  70. package/dist/profile-persist.js +53 -0
  71. package/dist/render/cache.d.ts +15 -0
  72. package/dist/render/cache.js +135 -0
  73. package/dist/render/index.d.ts +3 -0
  74. package/dist/render/index.js +14 -0
  75. package/dist/render/session-inject.d.ts +5 -0
  76. package/dist/render/session-inject.js +24 -0
  77. package/dist/render/turn-inject.d.ts +36 -0
  78. package/dist/render/turn-inject.js +248 -0
  79. package/dist/schema.d.ts +448 -0
  80. package/dist/schema.js +45 -0
  81. package/dist/session.d.ts +82 -0
  82. package/dist/session.js +102 -0
  83. package/dist/slog.d.ts +3 -0
  84. package/dist/slog.js +27 -0
  85. package/dist/transpile.d.ts +34 -0
  86. package/dist/transpile.js +220 -0
  87. package/dist/verify/file-hash.d.ts +2 -0
  88. package/dist/verify/file-hash.js +30 -0
  89. package/dist/verify/fs-content-match.d.ts +2 -0
  90. package/dist/verify/fs-content-match.js +26 -0
  91. package/dist/verify/fs-exists.d.ts +2 -0
  92. package/dist/verify/fs-exists.js +13 -0
  93. package/dist/verify/fs-not-exists.d.ts +2 -0
  94. package/dist/verify/fs-not-exists.js +13 -0
  95. package/dist/verify/git-status-clean.d.ts +2 -0
  96. package/dist/verify/git-status-clean.js +19 -0
  97. package/dist/verify/index.d.ts +7 -0
  98. package/dist/verify/index.js +46 -0
  99. package/dist/verify/lint-check.d.ts +2 -0
  100. package/dist/verify/lint-check.js +17 -0
  101. package/dist/verify/read-stderr.d.ts +5 -0
  102. package/dist/verify/read-stderr.js +20 -0
  103. package/dist/verify/ref-check.d.ts +13 -0
  104. package/dist/verify/ref-check.js +88 -0
  105. package/dist/verify/test-pass.d.ts +2 -0
  106. package/dist/verify/test-pass.js +17 -0
  107. package/dist/verify/ts-compiles.d.ts +2 -0
  108. package/dist/verify/ts-compiles.js +17 -0
  109. package/package.json +62 -0
  110. package/src/agent/api-bridge.ts +105 -0
  111. package/src/agent/index.ts +4 -0
  112. package/src/agent/pi-adapter.ts +326 -0
  113. package/src/agent/registry.ts +44 -0
  114. package/src/asset-health.ts +327 -0
  115. package/src/asset-pack/loader.ts +141 -0
  116. package/src/asset-pack/manifest.ts +118 -0
  117. package/src/asset-pack/md-file-pack.ts +202 -0
  118. package/src/asset-pack/validate.ts +186 -0
  119. package/src/builtin/assets/blueprints/dev-knowledge.blueprint.yaml +13 -0
  120. package/src/builtin/assets/domains/agent-info.md +15 -0
  121. package/src/builtin/assets/domains/authoring.md +201 -0
  122. package/src/builtin/assets/domains/pack-repair.md +47 -0
  123. package/src/builtin/assets/domains/project-analysis.md +52 -0
  124. package/src/builtin/assets/domains/usage.md +81 -0
  125. package/src/builtin/assets/domains/user-info.md +15 -0
  126. package/src/builtin/assets/profiles/guide.profile.md +28 -0
  127. package/src/commands.ts +405 -0
  128. package/src/compile/agent-context.ts +487 -0
  129. package/src/compile/index.ts +5 -0
  130. package/src/compile/resolve-use.ts +208 -0
  131. package/src/compile/type-guards.ts +132 -0
  132. package/src/config.ts +154 -0
  133. package/src/constants.ts +104 -0
  134. package/src/diagnostics.ts +36 -0
  135. package/src/index.ts +1028 -0
  136. package/src/injection-status.ts +155 -0
  137. package/src/log.ts +173 -0
  138. package/src/manual-session.ts +190 -0
  139. package/src/manual-track.ts +127 -0
  140. package/src/parse/blueprint.ts +82 -0
  141. package/src/parse/domain-renderers.ts +120 -0
  142. package/src/parse/domain.ts +78 -0
  143. package/src/parse/index.ts +266 -0
  144. package/src/parse/profile.ts +139 -0
  145. package/src/parse/ref-resolver.ts +198 -0
  146. package/src/parse/shared.ts +331 -0
  147. package/src/profile-persist.ts +60 -0
  148. package/src/render/cache.ts +150 -0
  149. package/src/render/index.ts +14 -0
  150. package/src/render/session-inject.ts +26 -0
  151. package/src/render/turn-inject.ts +278 -0
  152. package/src/schema.ts +542 -0
  153. package/src/session.ts +190 -0
  154. package/src/slog.ts +32 -0
  155. package/src/transpile.ts +302 -0
  156. package/src/verify/file-hash.ts +29 -0
  157. package/src/verify/fs-content-match.ts +28 -0
  158. package/src/verify/fs-exists.ts +14 -0
  159. package/src/verify/fs-not-exists.ts +17 -0
  160. package/src/verify/git-status-clean.ts +23 -0
  161. package/src/verify/index.ts +57 -0
  162. package/src/verify/lint-check.ts +21 -0
  163. package/src/verify/read-stderr.ts +24 -0
  164. package/src/verify/ref-check.ts +118 -0
  165. package/src/verify/test-pass.ts +18 -0
  166. package/src/verify/ts-compiles.ts +21 -0
@@ -0,0 +1,82 @@
1
+ // src/parse/blueprint.ts — blueprints/*.blueprint.yaml → Blueprint IR
2
+ //
3
+ // Phase term-P4.5:载体从 MD 转 YAML。
4
+ // Blueprint 是纯结构化无叙事(target + mode + modules),MD 的 H2/H3 是用叙事格式装非叙事数据。
5
+ // YAML 直接表达嵌套结构,parser 简化为 yaml.load() + 校验,与 frontmatter 同构。
6
+ //
7
+ // Blueprint asset 格式(v9.5):
8
+ // name: <blueprint-name>
9
+ // groups:
10
+ // - name: 会话背景 ← 聚合组(人类自定义语义名)
11
+ // inject: session ← 注入位置(session / turn,Agent-agnostic)
12
+ // mode: hybrid ← 可选
13
+ // modules: [Scene, Participant] ← 聚合点(Domain Schema Name 列表)
14
+ // - name: 触发索引
15
+ // inject: session
16
+ // modules: [Trigger]
17
+ // - name: 参考手册
18
+ // inject: turn
19
+ // modules: [Rules, Flows, Checklists]
20
+ //
21
+ // Tech Debt T6: 用 constants + type guard(pt-quality #1/#4/#5)
22
+
23
+ import { readFile } from "node:fs/promises";
24
+ import { join } from "node:path";
25
+ import { parse as parseYaml } from "yaml";
26
+ import type { Blueprint, BlueprintGroup, InjectTarget, StructureLayout } from "../schema.js";
27
+
28
+ const VALID_MODES: ReadonlyArray<StructureLayout["mode"]> = ["byDomain", "byType", "hybrid"];
29
+
30
+ interface RawBlueprintGroup {
31
+ name: string;
32
+ inject: string;
33
+ mode?: string;
34
+ // v9.1(modules-to-profile 迁移):modules 已迁到 ProfileGroup,本接口字段保留注释说明。
35
+ // 旧 YAML 中 modules: [..] 行 parse 后被忽略——迁移期内联文件用。
36
+ modules?: string[];
37
+ }
38
+
39
+ interface RawBlueprint {
40
+ name: string;
41
+ groups: RawBlueprintGroup[];
42
+ }
43
+
44
+ /** 读 blueprints/<fileName>.blueprint.yaml → Blueprint { name, groups }
45
+ * v9.1(modules-to-profile 迁移):BlueprintGroup 不再带 modules——modules 由 ProfileGroup
46
+ * 通过 H2 `### Modules` 段提供。YAML 中 modules 行(若有)被静默忽略,迁移期兼容。 */
47
+ export async function parseBlueprint(absDir: string, fileName: string): Promise<Blueprint> {
48
+ const filePath = join(absDir, fileName);
49
+ const raw = await readFile(filePath, "utf8");
50
+ const data = parseYaml(raw) as RawBlueprint;
51
+
52
+ if (!data || typeof data.name !== "string") {
53
+ throw new Error(`parseBlueprint: ${fileName} 缺少顶层 name 字段`);
54
+ }
55
+ if (!Array.isArray(data.groups)) {
56
+ throw new Error(`parseBlueprint: ${fileName} 缺少 groups 数组`);
57
+ }
58
+
59
+ const groups: BlueprintGroup[] = data.groups.map((ip) => {
60
+ if (typeof ip.name !== "string") {
61
+ throw new Error(`parseBlueprint: ${fileName} 聚合组缺 name 字段`);
62
+ }
63
+ if (typeof ip.inject !== "string") {
64
+ throw new Error(`parseBlueprint: ${fileName} 聚合组 ${ip.name} 缺 inject 字段`);
65
+ }
66
+
67
+ const config: BlueprintGroup = {
68
+ name: ip.name,
69
+ inject: ip.inject as InjectTarget,
70
+ };
71
+ if (ip.mode && isValidMode(ip.mode)) {
72
+ config.mode = ip.mode;
73
+ }
74
+ return config;
75
+ });
76
+
77
+ return { name: data.name, groups };
78
+ }
79
+
80
+ function isValidMode(x: string): x is StructureLayout["mode"] {
81
+ return (VALID_MODES as readonly string[]).includes(x);
82
+ }
@@ -0,0 +1,120 @@
1
+ // src/parse/domain-renderers.ts — Domain H2 段解析注册表(pt-quality #3)
2
+ //
3
+ // Phase term-P9.3:注册表从二维(h2Name × type)降为一维(h2Name)。
4
+ // - Type 字段已删除——H2 段名 = schema 选择器(一个 H2 段一个 schema)
5
+ // - 加新 H2 段名 = 调 registerDomainSectionRenderer 加一行 + parser 函数
6
+ //
7
+ // 替换 parse/domain.ts 中的 switch-case 主逻辑。switch-case 仅留兜底 default fallback。
8
+
9
+ import { s, sArr, type Item } from "./shared.js";
10
+ import type { FlowStep } from "../schema.js";
11
+
12
+ /** 单个 H2 段内容解析器。 */
13
+ export type DomainSectionParser = (items: Item[], sectionRaw: string) => unknown;
14
+
15
+ /** 注册表:H2段名 → parser。Phase term-P9.3:删 type 维度。 */
16
+ const domainSectionRenderers: Record<string, DomainSectionParser> = {
17
+ // H2="Scene"(Phase term-P9.1:统一为 Term[])
18
+ Scene: (items) =>
19
+ items.map((it) => {
20
+ const desc = s(it.fields.desc) || s(it.fields.description) || s(it.fields.role) || "";
21
+ const path = s(it.fields.path);
22
+ const fields = sArr(it.fields.fields);
23
+ const note = s(it.fields.purpose) || s(it.fields.rule);
24
+ const term: { name: string; desc?: string; fields?: string[]; note?: string; path?: string } =
25
+ {
26
+ name: it.name,
27
+ };
28
+ if (desc) term.desc = desc;
29
+ if (path) term.path = path;
30
+ if (fields.length > 0) term.fields = fields;
31
+ if (note) term.note = note;
32
+ return term;
33
+ }),
34
+ // H2="Rules"(Phase term-P9.2:从 Manual.term 拆出)—— Rule[]
35
+ Rules: (items) =>
36
+ items.map((it) => {
37
+ const itemsArr = sArr(it.fields.items);
38
+ const check =
39
+ s(it.fields.check) ||
40
+ s(it.fields.desc) ||
41
+ s(it.fields.value) ||
42
+ s(it.fields.description) ||
43
+ "";
44
+ if (itemsArr.length > 0) {
45
+ return { name: it.name, slot: "global", type: "ban", check, items: itemsArr };
46
+ }
47
+ return { name: it.name, slot: "global", type: "invariant", check };
48
+ }),
49
+ // H2="Flows"(Phase term-P9.2:从 Manual.workflow 拆出)—— FlowTemplate[]
50
+ Flows: (items, sectionRaw) =>
51
+ items.map((item) => {
52
+ const tpl: Record<string, unknown> = {
53
+ name: item.name,
54
+ argumentHint: s(item.fields["argument-hint"]) || undefined,
55
+ intent: s(item.fields.intent),
56
+ steps: collectSteps(item.name, sectionRaw),
57
+ externals: [],
58
+ };
59
+ const vars = sArr(item.fields.vars);
60
+ if (vars.length > 0) tpl._vars = vars;
61
+ return tpl;
62
+ }),
63
+ // H2="Checklists"(Phase term-P9.2:新增)—— Checklist[]
64
+ Checklists: (items) =>
65
+ items.map((it) => ({
66
+ name: it.name,
67
+ items: sArr(it.fields.items),
68
+ })),
69
+ };
70
+
71
+ /** 加新 H2 段名 = 调 registerDomainSectionRenderer 加一行注册 + 一个 parser 函数。
72
+ * Phase term-P9.3:删 type 参数。 */
73
+ export function registerDomainSectionRenderer(h2Name: string, parser: DomainSectionParser): void {
74
+ domainSectionRenderers[h2Name] = parser;
75
+ }
76
+
77
+ /** 取 H2段名 的 parser。未注册返 undefined(调用方走 fallback)。Phase term-P9.3:删 type 参数。 */
78
+ export function getDomainSectionParser(h2Name: string): DomainSectionParser | undefined {
79
+ return domainSectionRenderers[h2Name];
80
+ }
81
+
82
+ // ==================== shared 辅助 ====================
83
+
84
+ function collectSteps(itemName: string, sectionRaw: string): FlowStep[] {
85
+ const lines = sectionRaw.split(/\r?\n/);
86
+ const steps: FlowStep[] = [];
87
+ let inItem = false;
88
+ let cur: FlowStep | null = null;
89
+ for (const line of lines) {
90
+ const h3 = line.match(/^###\s+(.+)$/);
91
+ if (h3) {
92
+ if (inItem) break;
93
+ if (h3[1].trim() === itemName) inItem = true;
94
+ continue;
95
+ }
96
+ if (!inItem) continue;
97
+ const stepMatch = line.match(/^\s*-\s+step\s*:\s*(.+)$/);
98
+ if (stepMatch) {
99
+ if (cur) steps.push(cur);
100
+ cur = { desc: stepMatch[1].trim() };
101
+ continue;
102
+ }
103
+ const observeMatch = line.match(/^\s*-\s+observe\s*:\s*(.+)$/);
104
+ if (observeMatch && cur) {
105
+ const val = observeMatch[1].trim();
106
+ // 支持 [a, b] 数组格式 和 单值格式
107
+ const arrMatch = val.match(/^\[(.*)\]$/);
108
+ if (arrMatch) {
109
+ cur.observe = arrMatch[1]
110
+ .split(",")
111
+ .map((s) => s.trim())
112
+ .filter((s) => s !== "");
113
+ } else {
114
+ cur.observe = [val];
115
+ }
116
+ }
117
+ }
118
+ if (cur) steps.push(cur);
119
+ return steps;
120
+ }
@@ -0,0 +1,78 @@
1
+ // src/parse/domain.ts — domains/*.md → Domain IR
2
+ //
3
+ // Phase term-P9.3:删除 type 字段(Phase term-P9.2 时已先保留 type 键做过渡)。
4
+ // parser 注册表从二维(h2Name × type)降为一维(h2Name)。
5
+ // 加新 H2 段名 = 调 registerDomainSectionRenderer 加一行注册 + parser 函数。
6
+ //
7
+ // Domain asset 格式(v9.3):
8
+ // ---
9
+ // name: <domain-name>
10
+ // ---
11
+ //
12
+ // ## Scene ← H2 段名 = 上下文模块类型(schema 由段名决定)
13
+ // ...(Term[] 形态,含 path/fields/note/desc)
14
+ //
15
+ // ## Rules ← Rule[](pt-quality)
16
+ // ...
17
+ //
18
+ // ## Flows ← FlowTemplate[](dev-workflow 等)
19
+ // ...
20
+ //
21
+ // ## Checklists ← Checklist[](pt-collab)
22
+ // ...
23
+ //
24
+ // ## Trigger ← 索引段(H3 + desc/hint)
25
+ // ...
26
+
27
+ import { join } from "node:path";
28
+ import type { Domain, Term } from "../schema.js";
29
+ import { readAsset, s, type Item } from "./shared.js";
30
+ import { getDomainSectionParser } from "./domain-renderers.js";
31
+
32
+ /** 读 domains/<relPath>.md → Domain { name, modules: Record<H2名, 内容> }
33
+ * Phase term-P9.3:Domain 不再有 type 字段——H2 段名直接决定 schema。
34
+ * v10.x:assetDir 让 fixtures 可指向 tests/fixtures/assets/(默认 .pt/assets)。
35
+ * v9.1+(modules-to-profile-complete):relPath 是 POSIX 相对路径(如 "user-info.md" /
36
+ * "workflow/dev-workflow.md"),用于支持多级目录。Domain.name 默认用 POSIX 相对路径去后缀。 */
37
+ export async function parseDomain(absDir: string, relPath: string): Promise<Domain> {
38
+ const asset = await readAsset(join(absDir, relPath));
39
+
40
+ // H2 段名 → 段内容的解析:调注册表 parser,未注册走 fallback (Term[])
41
+ const modules: Record<string, unknown> = {};
42
+ for (const [h2Name, section] of Object.entries(asset.sections)) {
43
+ modules[h2Name] = parseDomainSection(h2Name, section.items, section.raw);
44
+ }
45
+
46
+ return {
47
+ name:
48
+ typeof asset.frontmatter.name === "string"
49
+ ? asset.frontmatter.name
50
+ : normalizeDomainName(relPath),
51
+ modules,
52
+ };
53
+ }
54
+
55
+ /** 从 POSIX 相对路径生成 Domain.name(去 .md 后缀,'/' 保留以支持多级目录)。
56
+ * Node.js readdir({ recursive: true }) 跨平台统一返回 '/' 分隔路径——Windows 也用 '/'。 */
57
+ function normalizeDomainName(relPath: string): string {
58
+ return relPath.split(/[\\/]/).join("/").replace(/\.md$/, "");
59
+ }
60
+
61
+ /** 调注册表 parser(h2Name)解析单个 H2 段。Phase term-P9.3:删 type 参数,二维注册表降一维。
62
+ * 未注册走 default fallback:term 形态(Term[])——新 H2 段名加 fallback 即可。 */
63
+ function parseDomainSection(h2Name: string, items: Item[], sectionRaw: string): unknown {
64
+ const parser = getDomainSectionParser(h2Name);
65
+ if (parser) return parser(items, sectionRaw);
66
+
67
+ // 未知 H2 段名:fallback 形态(term 的 Term[])
68
+ // ——新增 H2 段名(如 "Glossary")会自动走 fallback 输出 Term[],无需注册
69
+ return fallbackTerms(items);
70
+ }
71
+
72
+ /** Default fallback:把 H3 项数组转 Term[](用于未注册的 H2 段名,如 ## Term / ## Glossary)。 */
73
+ function fallbackTerms(items: Item[]): Term[] {
74
+ return items.map((it) => ({
75
+ name: it.name,
76
+ desc: s(it.fields.desc) || s(it.fields.description),
77
+ }));
78
+ }
@@ -0,0 +1,266 @@
1
+ // src/parse/index.ts — Parse 前端入口
2
+ //
3
+ // Phase 9.3:v9 适配 — 枚举 profiles/(用户面是 Profile,不是 Blueprint);
4
+ // channels/ 删除(v9 Channel 留作未来 Connector,不实现)。
5
+ // 按目录位置分发载体(domains/ → domain adapter / blueprints/ → blueprint adapter / profiles/ → profile adapter)。
6
+ //
7
+ // v15.x PR1(§3.1):mdAdapter.load 改为构造 AssetPack[] → 加载 → N 元 dedupByNameN。
8
+ // - 加载顺序:project → settings → global → builtin(settings PR1 stub 返空)
9
+ // - settings 数组内部 reverse(§3.3.1 后者赢)
10
+ // - 删除 loadAllDomains/loadAllBlueprints/loadAllProfiles/loadAllBuiltin* 旧函数
11
+ // ——它们的逻辑已迁入 src/asset-pack/md-file-pack.ts
12
+ // - back-compat:settingsPacks=[] 时,dedupByNameN([project, global, builtin])
13
+ // 退化等价于今天的 dedupByName(project, builtin)(project 前者赢,builtin 补充)。
14
+
15
+ import type {
16
+ AssetPack,
17
+ Blueprint,
18
+ Domain,
19
+ Profile,
20
+ SchemaBundle,
21
+ SourceAdapter,
22
+ SourceAdapterContext,
23
+ WorkingSet,
24
+ } from "../schema.js";
25
+ import {
26
+ loadBuiltinPack,
27
+ loadGlobalPack,
28
+ loadProjectPack,
29
+ loadSettingsPacks,
30
+ } from "../asset-pack/loader.js";
31
+ import { reportWarn } from "../diagnostics.js";
32
+ import { parseRef, resolveBlueprint } from "./ref-resolver.js";
33
+ import { parseBlueprint } from "./blueprint.js";
34
+ import { parseDomain } from "./domain.js";
35
+ import { parseProfile } from "./profile.js";
36
+
37
+ /** MD adapter:按目录位置分发到 domain/blueprint/profile adapter,组装 SchemaBundle。
38
+ * v9 命名约定:适配的是 MD 文件格式(不再叫 OXN——OXN 是历史名)。
39
+ * v15.x PR1(§3.1):内部构造 4 类 AssetPack → loadXxx → N 元 dedupByNameN。 */
40
+ export const mdAdapter: SourceAdapter = {
41
+ name: "md",
42
+
43
+ async load(cwd, profileName, adapterCtx): Promise<SchemaBundle> {
44
+ // 1. 构造 4 类 pack(§3.1 顺序:project → settings → global → builtin)
45
+ const projectPack = await loadProjectPack(cwd, adapterCtx);
46
+ const settingsPacks = await loadSettingsPacks(cwd); // PR4 接通:读 .pi/settings.json pt.asset-packs
47
+ const globalPack = await loadGlobalPack(adapterCtx);
48
+ const builtinPack = await loadBuiltinPack(adapterCtx);
49
+
50
+ // v15.x PR4(§3.3.1):settings 倒序——后者赢(npm 风格)
51
+ // packs 顺序 = [project, ...settings.reverse(), global, builtin]
52
+ // findActiveProfile 不限定 ref 按此顺序前者赢 → project > 后声明 settings > 先声明 settings > global > builtin
53
+ const packs: AssetPack[] = [
54
+ projectPack,
55
+ ...settingsPacks.slice().reverse(),
56
+ globalPack,
57
+ builtinPack,
58
+ ];
59
+
60
+ // v15.x PR4(§3.4):pack name 全局唯一性校验——settings pack 之间同名报错
61
+ checkPackNameConflicts(packs, adapterCtx);
62
+
63
+ // 2. v15.x §4.4.2(双层语义):构建双索引 working set——location(位置 alias)+ identity(manifest.name)
64
+ // reserved pack(project/global/builtin)才进 location 索引;settings pack 不进 location(无位置别名)
65
+ // 所有 pack 都进 identity 索引
66
+ // back-compat(缺口 1-b):reserved pack 无 manifest 时 pack.name=位置别名,identity 与 location 索引 key 重合——双入口命中同一 asset
67
+ const LOC_ALIAS: ReadonlyMap<string, string> = new Map([
68
+ ["project", "prj"],
69
+ ["global", "gbl"],
70
+ ["builtin", "pt"],
71
+ ]);
72
+ const domainLocWS = new Map<string, { pack: AssetPack; asset: Domain }>();
73
+ const domainIdWS = new Map<string, { pack: AssetPack; asset: Domain }>();
74
+ const blueprintLocWS = new Map<string, { pack: AssetPack; asset: Blueprint }>();
75
+ const blueprintIdWS = new Map<string, { pack: AssetPack; asset: Blueprint }>();
76
+ const profileLocWS = new Map<string, { pack: AssetPack; asset: Profile }>();
77
+ const profileIdWS = new Map<string, { pack: AssetPack; asset: Profile }>();
78
+ const packProfiles: Profile[][] = [];
79
+ const allDomains: Domain[] = [];
80
+ const allBlueprints: Blueprint[] = [];
81
+ const allProfiles: Profile[] = [];
82
+
83
+ for (const pack of packs) {
84
+ const locAlias = LOC_ALIAS.get(pack.source); // settings → undefined
85
+ const packDoms = await pack.loadDomains();
86
+ for (const d of packDoms) {
87
+ domainIdWS.set(`${pack.name}/${d.name}`, { pack, asset: d });
88
+ if (locAlias) domainLocWS.set(`${locAlias}/${d.name}`, { pack, asset: d });
89
+ allDomains.push(d);
90
+ }
91
+ const packBps = await pack.loadBlueprints();
92
+ for (const b of packBps) {
93
+ blueprintIdWS.set(`${pack.name}/${b.name}`, { pack, asset: b });
94
+ if (locAlias) blueprintLocWS.set(`${locAlias}/${b.name}`, { pack, asset: b });
95
+ allBlueprints.push(b);
96
+ }
97
+ const packProfs = await pack.loadProfiles();
98
+ for (const p of packProfs) {
99
+ profileIdWS.set(`${pack.name}/${p.name}`, { pack, asset: p });
100
+ if (locAlias) profileLocWS.set(`${locAlias}/${p.name}`, { pack, asset: p });
101
+ allProfiles.push(p);
102
+ }
103
+ packProfiles.push(packProfs);
104
+ }
105
+
106
+ // 3. 找 active profile(PR3:支持 "foo" 和 "@pack/foo" 两种)
107
+ let active: Profile;
108
+ let activeProfileOrigin: "exact" | "fallback" = "exact";
109
+ let originalProfileName: string | undefined;
110
+ try {
111
+ active = findActiveProfile(packs, packProfiles, profileName);
112
+ } catch (e) {
113
+ // active 没找到时 fallback 到第一个 profile(与今天行为一致)
114
+ const fallback = allProfiles[0];
115
+ if (!fallback) {
116
+ throw e; // 真的没 profile——抛错
117
+ }
118
+ active = fallback;
119
+ // v15.x PR6(fix pt-active-profile-fallback-mismatch):记录 fallback 由来
120
+ // 让 caller(transpile.ts + index.ts)能感知并同步 s.activeProfile / loadedFrom / notify
121
+ activeProfileOrigin = "fallback";
122
+ originalProfileName = profileName;
123
+ }
124
+
125
+ // 4. 校验 active 引用的 Blueprint 存在(用 working set)
126
+ // v15.x PR6(fix pt-parse-blueprint-warn-misleading):改用 resolveBlueprint helper
127
+ // 与 compile 阶段(transpile.ts:145)共用 fallback 逻辑——避免 parse 层单方面
128
+ // 报 "unknown Blueprint" warn 但 compile 层能 fallback 成功的 false positive。
129
+ // 行为:
130
+ // - 不限定 ref + 跨 pack fallback 命中 → 静默(compile 阶段会复用同一 fallback)
131
+ // - 限定 ref 找不到 / 不限定 ref fallback 全部 miss → warn(compile 阶段会 throw)
132
+ // v15.x §4.4.2:resolveBlueprint 接双索引 WorkingSet,按 parseRef 的 kind 分发查 location/identity
133
+ const blueprintWS = {
134
+ location: blueprintLocWS,
135
+ identity: blueprintIdWS,
136
+ };
137
+ const bpResolved = resolveBlueprint(
138
+ active,
139
+ blueprintWS,
140
+ packs.map((p) => p.name)
141
+ );
142
+ if (!bpResolved && active.blueprint) {
143
+ const { pack: bpPack, name: bpName } = parseRef(active.blueprint, active.sourcePack ?? "");
144
+ reportWarn(
145
+ adapterCtx,
146
+ `Profile "${active.name}" 引用了未知 Blueprint "${active.blueprint}"`,
147
+ {
148
+ profileName: active.name,
149
+ referencedBlueprint: active.blueprint,
150
+ resolvedBlueprint: `@${bpPack}/${bpName}`,
151
+ availableBlueprints: [...blueprintIdWS.keys()],
152
+ }
153
+ );
154
+ }
155
+
156
+ return {
157
+ domains: allDomains,
158
+ blueprints: allBlueprints,
159
+ profiles: allProfiles,
160
+ activeProfile: active.name,
161
+ // v15.x PR6(fix pt-active-profile-fallback-mismatch):暴露 fallback 由来
162
+ activeProfileOrigin,
163
+ originalProfileName,
164
+ packs,
165
+ activeProfilePack: findProfilePack(packs, packProfiles, active.name),
166
+ // v15.x §4.4.2:working set 双索引
167
+ workingSet: {
168
+ domains: { location: domainLocWS, identity: domainIdWS },
169
+ blueprints: blueprintWS,
170
+ profiles: { location: profileLocWS, identity: profileIdWS },
171
+ },
172
+ };
173
+ },
174
+ };
175
+
176
+ /** v15.x PR3:找 active profile——支持 "foo" 和 "@pack/foo" 两种。
177
+ * 限定 ref 直接查目标 pack;不限定按 packs 顺序前者赢(project > settings 倒序 > global > builtin)。
178
+ * v15.x PR4:settings 倒序逻辑由 mdAdapter.load 构造 packs 时已处理,findActiveProfile 不变。 */
179
+ function findActiveProfile(
180
+ packs: AssetPack[],
181
+ packProfiles: Profile[][],
182
+ profileRef: string
183
+ ): Profile {
184
+ if (profileRef.startsWith("@")) {
185
+ const { pack, name } = parseRef(profileRef, ""); // 限定 ref 不需要 selfPack
186
+ const packIdx = packs.findIndex((p) => p.name === pack);
187
+ if (packIdx < 0) {
188
+ throw new Error(`active profile "@${pack}/${name}" references unknown pack "${pack}"`);
189
+ }
190
+ const found = packProfiles[packIdx]?.find((p) => p.name === name);
191
+ if (!found) {
192
+ throw new Error(`active profile "@${pack}/${name}" not found in pack "${pack}"`);
193
+ }
194
+ return found;
195
+ }
196
+ for (let i = 0; i < packs.length; i++) {
197
+ const found = packProfiles[i]?.find((p) => p.name === profileRef);
198
+ if (found) return found;
199
+ }
200
+ throw new Error(`active profile "${profileRef}" not found in any pack`);
201
+ }
202
+
203
+ /** v15.x PR2(§8.3):找 profile 所属 pack——按 dedup 前顺序,
204
+ * 第一个含该 profile name 的 pack 赢(PR3 后 working set 取代 dedup,但 cache 文件名仍用 pack name)。
205
+ * fallback "prj"(不应到达——active profile 必来自某 pack)。 */
206
+ function findProfilePack(
207
+ packs: AssetPack[],
208
+ packProfiles: Profile[][],
209
+ profileName: string
210
+ ): string {
211
+ for (let i = 0; i < packs.length; i++) {
212
+ if (packProfiles[i]?.some((p) => p.name === profileName)) {
213
+ return packs[i].name;
214
+ }
215
+ }
216
+ return "prj";
217
+ }
218
+
219
+ /** v15.x PR4(§3.4)+ §3.4 缺口 2(升级 throw):pack name 全局唯一性校验。
220
+ * reserved pack 读 manifest 后,project + settings 同 manifest.name 可能撞名。
221
+ * 3 个场景区分:
222
+ * - 场景 A(路径重叠):settings pack rootDir 与 project pack 一致 → throw + "路径重叠"
223
+ * - 场景 B(不同路径同名):多个 settings pack 同 manifest.name 但 rootDir 不同 → throw + "不同路径同 name"
224
+ * - back-compat:project/settings 覆盖 global/builtin 是合理优先级 → 不 throw,静默(§3.4 表)
225
+ * 错误从 session_start catch 透出到 ctx.ui.notify("error")——用户能直接看到场景区分。
226
+ *
227
+ * reserved 名(prj/gbl/pt)只 1 份,不会冲突。settings pack manifest.name 不能是 reserved 名
228
+ * (PR2 manifest 校验已挡),所以不会与 reserved 重名。 */
229
+ function checkPackNameConflicts(packs: AssetPack[], adapterCtx?: SourceAdapterContext): void {
230
+ const seen = new Map<string, AssetPack[]>();
231
+ for (const pack of packs) {
232
+ const arr = seen.get(pack.name) ?? [];
233
+ arr.push(pack);
234
+ seen.set(pack.name, arr);
235
+ }
236
+ for (const [name, group] of seen) {
237
+ if (group.length <= 1) continue;
238
+ // 同 name 多份——按是否含 settings source 分类处理
239
+ const hasSettings = group.some((p) => p.source === "settings");
240
+ if (!hasSettings) {
241
+ // 没有 settings 来源:project+global/builtin 同名 = 合理优先级(§3.4 表),静默
242
+ // 不可能发生(manifest 校验挡 reserved name);保留分支以防未来加新 source
243
+ continue;
244
+ }
245
+ // 有 settings 来源——按 rootDir 是否一致区分场景 A/B
246
+ const rootDirs = new Set(group.map((p) => p.rootDir));
247
+ const sources = group.map((p) => `${p.source}(${p.rootDir})`).join(" + ");
248
+ if (rootDirs.size === 1) {
249
+ // 场景 A:路径重叠(settings pack 与 project pack 指向同一目录)—— 重复加载同一份
250
+ throw new Error(
251
+ `§3.4 路径重叠:settings pack "@${name}" 与 project pack 指向同一目录 ${[...rootDirs][0]}。` +
252
+ `修复:从 pt.asset-packs 移除重复 path,或从 pt.project-pack-dir / assetDir 切到别处。`
253
+ );
254
+ }
255
+ // 场景 B:不同路径同名——多份 manifest.name 相同但指向不同目录
256
+ const pathsList = group.map((p) => ` - ${p.source}: ${p.rootDir}`).join("\n");
257
+ throw new Error(
258
+ `§3.4 不同路径同 name:pack "${name}" 在多个不同路径被声明为同一身份:\n${pathsList}\n` +
259
+ `修复:删掉重复的 pt.asset-packs 条目,或给同名 pack 加唯一 manifest.name 区分。`
260
+ );
261
+ }
262
+ }
263
+
264
+ // 保留 parseX 函数的导出(其他模块 / 测试可能 import)。PR1 阶段 MdFilePack 已复用,
265
+ // 但 parseX 仍作为底层 parser 公开——加新 adapter 类型时可直接 import。
266
+ export { parseBlueprint, parseDomain, parseProfile };