@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,107 @@
1
+ // src/manual-track.ts — pt Manual 实例追踪(纯函数内核)
2
+ //
3
+ // 配套 .pt/docs/designs/pt-injection-status-manual-track.md §2.4 / §2.5:
4
+ // - parseManualProgress:读文件 → 抽 frontmatter → 正则数 checklist
5
+ // - renderManualWidgetLines:3 行 string[] 给 setWidget 用
6
+ // - renderManualFooterSuffix:footer 后缀("· manual: <procedure> 3/6")
7
+ // - isManualActive:文件存在 + status !== completed
8
+ //
9
+ // 边界纪律:
10
+ // - manual 实例文件不是 Pt 资产(不是 Domain/Blueprint/Profile)→ 不进 parse/ 层
11
+ // - 进度(stepDone/stepTotal)不存 session——文件是 single source of truth
12
+ // - 不依赖 ExtensionAPI,可单测
13
+ import { access, readFile } from "node:fs/promises";
14
+ /** 从 manual 实例文件 parse 进度。失败返回 null(容错,不抛)。 */
15
+ export async function parseManualProgress(filePath) {
16
+ try {
17
+ const content = await readFile(filePath, "utf8");
18
+ return parseManualProgressFromContent(content);
19
+ }
20
+ catch {
21
+ // 文件不存在 / 权限不足 / 读取失败
22
+ return null;
23
+ }
24
+ }
25
+ /** 内部:从字符串内容 parse(便于单测 & 未来内联输入)。
26
+ * 算法:
27
+ * 1. frontmatter:取首段 `---` ... `---` 之间的 YAML
28
+ * 2. procedure / status:从 frontmatter 行解析(`key: value` 形式,宽松)
29
+ * 3. checklist:全文正则 `/^- \[([ x])\]\s+(.+)$/gm` 数 done / total
30
+ * 4. nextStep:第一个未完成项的文本(去前缀) */
31
+ export function parseManualProgressFromContent(content) {
32
+ const fm = extractFrontmatter(content);
33
+ const procedure = fm?.procedure ?? "(unknown)";
34
+ const status = fm?.status ?? "unknown";
35
+ let stepDone = 0;
36
+ let stepTotal = 0;
37
+ let nextStep = null;
38
+ // 多行匹配(m 标志),匹配行首 "- [x]" 或 "- [ ]"
39
+ const re = /^- \[([ x])\]\s+(.+)$/gm;
40
+ for (const m of content.matchAll(re)) {
41
+ const checked = m[1] === "x";
42
+ const text = m[2]?.trim() ?? "";
43
+ stepTotal++;
44
+ if (checked) {
45
+ stepDone++;
46
+ }
47
+ else if (nextStep === null && text) {
48
+ nextStep = text;
49
+ }
50
+ }
51
+ return { procedure, stepDone, stepTotal, status, nextStep };
52
+ }
53
+ function extractFrontmatter(content) {
54
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
55
+ if (!m?.[1])
56
+ return null;
57
+ const out = {};
58
+ for (const line of m[1].split(/\r?\n/)) {
59
+ const kv = line.match(/^([a-zA-Z_-]+)\s*:\s*(.*)$/);
60
+ if (!kv?.[1])
61
+ continue;
62
+ const key = kv[1];
63
+ const value = (kv[2] ?? "").trim();
64
+ if (key === "procedure")
65
+ out.procedure = value;
66
+ else if (key === "status")
67
+ out.status = value;
68
+ }
69
+ return out;
70
+ }
71
+ /** widget 渲染:3 行 string[],给 `ui.setWidget("pt-manual", lines, { placement: "aboveEditor" })` 用。
72
+ * - 第 1 行:`pt ▶ <procedure> step <done>/<total> (<status>)`(completed 加 ` ✓ done`)
73
+ * - 第 2 行:` next: <nextStep 文本>`(无 nextStep 时该行省略)
74
+ * - 第 3 行:` file: <filePath>`
75
+ * status === completed 时仍显示,但 footer 后缀改 done;widget 撤掉由 caller 决定(见 index.ts refreshManualWidget)。 */
76
+ export function renderManualWidgetLines(filePath, p) {
77
+ const statusText = p.status === "completed" ? "✓ done" : `(${p.status})`;
78
+ const lines = [];
79
+ lines.push(`pt ▶ ${p.procedure} step ${p.stepDone}/${p.stepTotal} ${statusText}`);
80
+ if (p.nextStep && p.status !== "completed") {
81
+ lines.push(` next: ${p.nextStep}`);
82
+ }
83
+ lines.push(` file: ${filePath}`);
84
+ return lines;
85
+ }
86
+ /** footer 后缀:有 activeManual 时追加。
87
+ * - in-progress → `· manual: <procedure> <done>/<total>`
88
+ * - completed → `· manual: <procedure> done` */
89
+ export function renderManualFooterSuffix(p) {
90
+ if (p.status === "completed") {
91
+ return `· manual: ${p.procedure} done`;
92
+ }
93
+ return `· manual: ${p.procedure} ${p.stepDone}/${p.stepTotal}`;
94
+ }
95
+ /** 文件存在 + status !== completed。配合 widget/footer 决定要不要展示。
96
+ * - 用 fs.access 探测存在(轻量,不读全文)
97
+ * - 真的 parse status 用 parseManualProgress(一次 readFile 取齐) */
98
+ export async function isManualActive(filePath) {
99
+ try {
100
+ await access(filePath);
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ const p = await parseManualProgress(filePath);
106
+ return p !== null && p.status !== "completed";
107
+ }
@@ -0,0 +1,5 @@
1
+ import type { Blueprint } from "../schema.js";
2
+ /** 读 blueprints/<fileName>.blueprint.yaml → Blueprint { name, groups }
3
+ * v9.1(modules-to-profile 迁移):BlueprintGroup 不再带 modules——modules 由 ProfileGroup
4
+ * 通过 H2 `### Modules` 段提供。YAML 中 modules 行(若有)被静默忽略,迁移期兼容。 */
5
+ export declare function parseBlueprint(absDir: string, fileName: string): Promise<Blueprint>;
@@ -0,0 +1,59 @@
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
+ import { readFile } from "node:fs/promises";
23
+ import { join } from "node:path";
24
+ import { parse as parseYaml } from "yaml";
25
+ const VALID_MODES = ["byDomain", "byType", "hybrid"];
26
+ /** 读 blueprints/<fileName>.blueprint.yaml → Blueprint { name, groups }
27
+ * v9.1(modules-to-profile 迁移):BlueprintGroup 不再带 modules——modules 由 ProfileGroup
28
+ * 通过 H2 `### Modules` 段提供。YAML 中 modules 行(若有)被静默忽略,迁移期兼容。 */
29
+ export async function parseBlueprint(absDir, fileName) {
30
+ const filePath = join(absDir, fileName);
31
+ const raw = await readFile(filePath, "utf8");
32
+ const data = parseYaml(raw);
33
+ if (!data || typeof data.name !== "string") {
34
+ throw new Error(`parseBlueprint: ${fileName} 缺少顶层 name 字段`);
35
+ }
36
+ if (!Array.isArray(data.groups)) {
37
+ throw new Error(`parseBlueprint: ${fileName} 缺少 groups 数组`);
38
+ }
39
+ const groups = data.groups.map((ip) => {
40
+ if (typeof ip.name !== "string") {
41
+ throw new Error(`parseBlueprint: ${fileName} 聚合组缺 name 字段`);
42
+ }
43
+ if (typeof ip.inject !== "string") {
44
+ throw new Error(`parseBlueprint: ${fileName} 聚合组 ${ip.name} 缺 inject 字段`);
45
+ }
46
+ const config = {
47
+ name: ip.name,
48
+ inject: ip.inject,
49
+ };
50
+ if (ip.mode && isValidMode(ip.mode)) {
51
+ config.mode = ip.mode;
52
+ }
53
+ return config;
54
+ });
55
+ return { name: data.name, groups };
56
+ }
57
+ function isValidMode(x) {
58
+ return VALID_MODES.includes(x);
59
+ }
@@ -0,0 +1,8 @@
1
+ import { type Item } from "./shared.js";
2
+ /** 单个 H2 段内容解析器。 */
3
+ export type DomainSectionParser = (items: Item[], sectionRaw: string) => unknown;
4
+ /** 加新 H2 段名 = 调 registerDomainSectionRenderer 加一行注册 + 一个 parser 函数。
5
+ * Phase term-P9.3:删 type 参数。 */
6
+ export declare function registerDomainSectionRenderer(h2Name: string, parser: DomainSectionParser): void;
7
+ /** 取 H2段名 的 parser。未注册返 undefined(调用方走 fallback)。Phase term-P9.3:删 type 参数。 */
8
+ export declare function getDomainSectionParser(h2Name: string): DomainSectionParser | undefined;
@@ -0,0 +1,115 @@
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
+ import { s, sArr } from "./shared.js";
9
+ /** 注册表:H2段名 → parser。Phase term-P9.3:删 type 维度。 */
10
+ const domainSectionRenderers = {
11
+ // H2="Scene"(Phase term-P9.1:统一为 Term[])
12
+ Scene: (items) => items.map((it) => {
13
+ const desc = s(it.fields.desc) || s(it.fields.description) || s(it.fields.role) || "";
14
+ const path = s(it.fields.path);
15
+ const fields = sArr(it.fields.fields);
16
+ const note = s(it.fields.purpose) || s(it.fields.rule);
17
+ const term = {
18
+ name: it.name,
19
+ };
20
+ if (desc)
21
+ term.desc = desc;
22
+ if (path)
23
+ term.path = path;
24
+ if (fields.length > 0)
25
+ term.fields = fields;
26
+ if (note)
27
+ term.note = note;
28
+ return term;
29
+ }),
30
+ // H2="Rules"(Phase term-P9.2:从 Manual.term 拆出)—— Rule[]
31
+ Rules: (items) => items.map((it) => {
32
+ const itemsArr = sArr(it.fields.items);
33
+ const check = s(it.fields.check) ||
34
+ s(it.fields.desc) ||
35
+ s(it.fields.value) ||
36
+ s(it.fields.description) ||
37
+ "";
38
+ if (itemsArr.length > 0) {
39
+ return { name: it.name, slot: "global", type: "ban", check, items: itemsArr };
40
+ }
41
+ return { name: it.name, slot: "global", type: "invariant", check };
42
+ }),
43
+ // H2="Flows"(Phase term-P9.2:从 Manual.workflow 拆出)—— FlowTemplate[]
44
+ Flows: (items, sectionRaw) => items.map((item) => {
45
+ const tpl = {
46
+ name: item.name,
47
+ argumentHint: s(item.fields["argument-hint"]) || undefined,
48
+ intent: s(item.fields.intent),
49
+ steps: collectSteps(item.name, sectionRaw),
50
+ externals: [],
51
+ };
52
+ const vars = sArr(item.fields.vars);
53
+ if (vars.length > 0)
54
+ tpl._vars = vars;
55
+ return tpl;
56
+ }),
57
+ // H2="Checklists"(Phase term-P9.2:新增)—— Checklist[]
58
+ Checklists: (items) => items.map((it) => ({
59
+ name: it.name,
60
+ items: sArr(it.fields.items),
61
+ })),
62
+ };
63
+ /** 加新 H2 段名 = 调 registerDomainSectionRenderer 加一行注册 + 一个 parser 函数。
64
+ * Phase term-P9.3:删 type 参数。 */
65
+ export function registerDomainSectionRenderer(h2Name, parser) {
66
+ domainSectionRenderers[h2Name] = parser;
67
+ }
68
+ /** 取 H2段名 的 parser。未注册返 undefined(调用方走 fallback)。Phase term-P9.3:删 type 参数。 */
69
+ export function getDomainSectionParser(h2Name) {
70
+ return domainSectionRenderers[h2Name];
71
+ }
72
+ // ==================== shared 辅助 ====================
73
+ function collectSteps(itemName, sectionRaw) {
74
+ const lines = sectionRaw.split(/\r?\n/);
75
+ const steps = [];
76
+ let inItem = false;
77
+ let cur = null;
78
+ for (const line of lines) {
79
+ const h3 = line.match(/^###\s+(.+)$/);
80
+ if (h3) {
81
+ if (inItem)
82
+ break;
83
+ if (h3[1].trim() === itemName)
84
+ inItem = true;
85
+ continue;
86
+ }
87
+ if (!inItem)
88
+ continue;
89
+ const stepMatch = line.match(/^\s*-\s+step\s*:\s*(.+)$/);
90
+ if (stepMatch) {
91
+ if (cur)
92
+ steps.push(cur);
93
+ cur = { desc: stepMatch[1].trim() };
94
+ continue;
95
+ }
96
+ const observeMatch = line.match(/^\s*-\s+observe\s*:\s*(.+)$/);
97
+ if (observeMatch && cur) {
98
+ const val = observeMatch[1].trim();
99
+ // 支持 [a, b] 数组格式 和 单值格式
100
+ const arrMatch = val.match(/^\[(.*)\]$/);
101
+ if (arrMatch) {
102
+ cur.observe = arrMatch[1]
103
+ .split(",")
104
+ .map((s) => s.trim())
105
+ .filter((s) => s !== "");
106
+ }
107
+ else {
108
+ cur.observe = [val];
109
+ }
110
+ }
111
+ }
112
+ if (cur)
113
+ steps.push(cur);
114
+ return steps;
115
+ }
@@ -0,0 +1,7 @@
1
+ import type { Domain } from "../schema.js";
2
+ /** 读 domains/<relPath>.md → Domain { name, modules: Record<H2名, 内容> }
3
+ * Phase term-P9.3:Domain 不再有 type 字段——H2 段名直接决定 schema。
4
+ * v10.x:assetDir 让 fixtures 可指向 tests/fixtures/assets/(默认 .pt/assets)。
5
+ * v9.1+(modules-to-profile-complete):relPath 是 POSIX 相对路径(如 "user-info.md" /
6
+ * "workflow/dev-workflow.md"),用于支持多级目录。Domain.name 默认用 POSIX 相对路径去后缀。 */
7
+ export declare function parseDomain(absDir: string, relPath: string): Promise<Domain>;
@@ -0,0 +1,69 @@
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
+ import { join } from "node:path";
27
+ import { readAsset, s } from "./shared.js";
28
+ import { getDomainSectionParser } from "./domain-renderers.js";
29
+ /** 读 domains/<relPath>.md → Domain { name, modules: Record<H2名, 内容> }
30
+ * Phase term-P9.3:Domain 不再有 type 字段——H2 段名直接决定 schema。
31
+ * v10.x:assetDir 让 fixtures 可指向 tests/fixtures/assets/(默认 .pt/assets)。
32
+ * v9.1+(modules-to-profile-complete):relPath 是 POSIX 相对路径(如 "user-info.md" /
33
+ * "workflow/dev-workflow.md"),用于支持多级目录。Domain.name 默认用 POSIX 相对路径去后缀。 */
34
+ export async function parseDomain(absDir, relPath) {
35
+ const asset = await readAsset(join(absDir, relPath));
36
+ // H2 段名 → 段内容的解析:调注册表 parser,未注册走 fallback (Term[])
37
+ const modules = {};
38
+ for (const [h2Name, section] of Object.entries(asset.sections)) {
39
+ modules[h2Name] = parseDomainSection(h2Name, section.items, section.raw);
40
+ }
41
+ return {
42
+ name: typeof asset.frontmatter.name === "string"
43
+ ? asset.frontmatter.name
44
+ : normalizeDomainName(relPath),
45
+ modules,
46
+ };
47
+ }
48
+ /** 从 POSIX 相对路径生成 Domain.name(去 .md 后缀,'/' 保留以支持多级目录)。
49
+ * Node.js readdir({ recursive: true }) 跨平台统一返回 '/' 分隔路径——Windows 也用 '/'。 */
50
+ function normalizeDomainName(relPath) {
51
+ return relPath.split(/[\\/]/).join("/").replace(/\.md$/, "");
52
+ }
53
+ /** 调注册表 parser(h2Name)解析单个 H2 段。Phase term-P9.3:删 type 参数,二维注册表降一维。
54
+ * 未注册走 default fallback:term 形态(Term[])——新 H2 段名加 fallback 即可。 */
55
+ function parseDomainSection(h2Name, items, sectionRaw) {
56
+ const parser = getDomainSectionParser(h2Name);
57
+ if (parser)
58
+ return parser(items, sectionRaw);
59
+ // 未知 H2 段名:fallback 形态(term 的 Term[])
60
+ // ——新增 H2 段名(如 "Glossary")会自动走 fallback 输出 Term[],无需注册
61
+ return fallbackTerms(items);
62
+ }
63
+ /** Default fallback:把 H3 项数组转 Term[](用于未注册的 H2 段名,如 ## Term / ## Glossary)。 */
64
+ function fallbackTerms(items) {
65
+ return items.map((it) => ({
66
+ name: it.name,
67
+ desc: s(it.fields.desc) || s(it.fields.description),
68
+ }));
69
+ }
@@ -0,0 +1,9 @@
1
+ import type { SourceAdapter } from "../schema.js";
2
+ import { parseBlueprint } from "./blueprint.js";
3
+ import { parseDomain } from "./domain.js";
4
+ import { parseProfile } from "./profile.js";
5
+ /** MD adapter:按目录位置分发到 domain/blueprint/profile adapter,组装 SchemaBundle。
6
+ * v9 命名约定:适配的是 MD 文件格式(不再叫 OXN——OXN 是历史名)。
7
+ * v15.x PR1(§3.1):内部构造 4 类 AssetPack → loadXxx → N 元 dedupByNameN。 */
8
+ export declare const mdAdapter: SourceAdapter;
9
+ export { parseBlueprint, parseDomain, parseProfile };
@@ -0,0 +1,223 @@
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
+ import { loadBuiltinPack, loadGlobalPack, loadProjectPack, loadSettingsPacks, } from "../asset-pack/loader.js";
15
+ import { reportWarn } from "../diagnostics.js";
16
+ import { parseRef, resolveBlueprint } from "./ref-resolver.js";
17
+ import { parseBlueprint } from "./blueprint.js";
18
+ import { parseDomain } from "./domain.js";
19
+ import { parseProfile } from "./profile.js";
20
+ /** MD adapter:按目录位置分发到 domain/blueprint/profile adapter,组装 SchemaBundle。
21
+ * v9 命名约定:适配的是 MD 文件格式(不再叫 OXN——OXN 是历史名)。
22
+ * v15.x PR1(§3.1):内部构造 4 类 AssetPack → loadXxx → N 元 dedupByNameN。 */
23
+ export const mdAdapter = {
24
+ name: "md",
25
+ async load(cwd, profileName, adapterCtx) {
26
+ // 1. 构造 4 类 pack(§3.1 顺序:project → settings → global → builtin)
27
+ const projectPack = await loadProjectPack(cwd, adapterCtx);
28
+ const settingsPacks = await loadSettingsPacks(cwd); // PR4 接通:读 .pi/settings.json pt.asset-packs
29
+ const globalPack = await loadGlobalPack(adapterCtx);
30
+ const builtinPack = await loadBuiltinPack(adapterCtx);
31
+ // v15.x PR4(§3.3.1):settings 倒序——后者赢(npm 风格)
32
+ // packs 顺序 = [project, ...settings.reverse(), global, builtin]
33
+ // findActiveProfile 不限定 ref 按此顺序前者赢 → project > 后声明 settings > 先声明 settings > global > builtin
34
+ const packs = [
35
+ projectPack,
36
+ ...settingsPacks.slice().reverse(),
37
+ globalPack,
38
+ builtinPack,
39
+ ];
40
+ // v15.x PR4(§3.4):pack name 全局唯一性校验——settings pack 之间同名报错
41
+ checkPackNameConflicts(packs, adapterCtx);
42
+ // 2. v15.x §4.4.2(双层语义):构建双索引 working set——location(位置 alias)+ identity(manifest.name)
43
+ // reserved pack(project/global/builtin)才进 location 索引;settings pack 不进 location(无位置别名)
44
+ // 所有 pack 都进 identity 索引
45
+ // back-compat(缺口 1-b):reserved pack 无 manifest 时 pack.name=位置别名,identity 与 location 索引 key 重合——双入口命中同一 asset
46
+ const LOC_ALIAS = new Map([
47
+ ["project", "prj"],
48
+ ["global", "gbl"],
49
+ ["builtin", "pt"],
50
+ ]);
51
+ const domainLocWS = new Map();
52
+ const domainIdWS = new Map();
53
+ const blueprintLocWS = new Map();
54
+ const blueprintIdWS = new Map();
55
+ const profileLocWS = new Map();
56
+ const profileIdWS = new Map();
57
+ const packProfiles = [];
58
+ const allDomains = [];
59
+ const allBlueprints = [];
60
+ const allProfiles = [];
61
+ for (const pack of packs) {
62
+ const locAlias = LOC_ALIAS.get(pack.source); // settings → undefined
63
+ const packDoms = await pack.loadDomains();
64
+ for (const d of packDoms) {
65
+ domainIdWS.set(`${pack.name}/${d.name}`, { pack, asset: d });
66
+ if (locAlias)
67
+ domainLocWS.set(`${locAlias}/${d.name}`, { pack, asset: d });
68
+ allDomains.push(d);
69
+ }
70
+ const packBps = await pack.loadBlueprints();
71
+ for (const b of packBps) {
72
+ blueprintIdWS.set(`${pack.name}/${b.name}`, { pack, asset: b });
73
+ if (locAlias)
74
+ blueprintLocWS.set(`${locAlias}/${b.name}`, { pack, asset: b });
75
+ allBlueprints.push(b);
76
+ }
77
+ const packProfs = await pack.loadProfiles();
78
+ for (const p of packProfs) {
79
+ profileIdWS.set(`${pack.name}/${p.name}`, { pack, asset: p });
80
+ if (locAlias)
81
+ profileLocWS.set(`${locAlias}/${p.name}`, { pack, asset: p });
82
+ allProfiles.push(p);
83
+ }
84
+ packProfiles.push(packProfs);
85
+ }
86
+ // 3. 找 active profile(PR3:支持 "foo" 和 "@pack/foo" 两种)
87
+ let active;
88
+ let activeProfileOrigin = "exact";
89
+ let originalProfileName;
90
+ try {
91
+ active = findActiveProfile(packs, packProfiles, profileName);
92
+ }
93
+ catch (e) {
94
+ // active 没找到时 fallback 到第一个 profile(与今天行为一致)
95
+ const fallback = allProfiles[0];
96
+ if (!fallback) {
97
+ throw e; // 真的没 profile——抛错
98
+ }
99
+ active = fallback;
100
+ // v15.x PR6(fix pt-active-profile-fallback-mismatch):记录 fallback 由来
101
+ // 让 caller(transpile.ts + index.ts)能感知并同步 s.activeProfile / loadedFrom / notify
102
+ activeProfileOrigin = "fallback";
103
+ originalProfileName = profileName;
104
+ }
105
+ // 4. 校验 active 引用的 Blueprint 存在(用 working set)
106
+ // v15.x PR6(fix pt-parse-blueprint-warn-misleading):改用 resolveBlueprint helper
107
+ // 与 compile 阶段(transpile.ts:145)共用 fallback 逻辑——避免 parse 层单方面
108
+ // 报 "unknown Blueprint" warn 但 compile 层能 fallback 成功的 false positive。
109
+ // 行为:
110
+ // - 不限定 ref + 跨 pack fallback 命中 → 静默(compile 阶段会复用同一 fallback)
111
+ // - 限定 ref 找不到 / 不限定 ref fallback 全部 miss → warn(compile 阶段会 throw)
112
+ // v15.x §4.4.2:resolveBlueprint 接双索引 WorkingSet,按 parseRef 的 kind 分发查 location/identity
113
+ const blueprintWS = {
114
+ location: blueprintLocWS,
115
+ identity: blueprintIdWS,
116
+ };
117
+ const bpResolved = resolveBlueprint(active, blueprintWS, packs.map((p) => p.name));
118
+ if (!bpResolved && active.blueprint) {
119
+ const { pack: bpPack, name: bpName } = parseRef(active.blueprint, active.sourcePack ?? "");
120
+ reportWarn(adapterCtx, `Profile "${active.name}" 引用了未知 Blueprint "${active.blueprint}"`, {
121
+ profileName: active.name,
122
+ referencedBlueprint: active.blueprint,
123
+ resolvedBlueprint: `@${bpPack}/${bpName}`,
124
+ availableBlueprints: [...blueprintIdWS.keys()],
125
+ });
126
+ }
127
+ return {
128
+ domains: allDomains,
129
+ blueprints: allBlueprints,
130
+ profiles: allProfiles,
131
+ activeProfile: active.name,
132
+ // v15.x PR6(fix pt-active-profile-fallback-mismatch):暴露 fallback 由来
133
+ activeProfileOrigin,
134
+ originalProfileName,
135
+ packs,
136
+ activeProfilePack: findProfilePack(packs, packProfiles, active.name),
137
+ // v15.x §4.4.2:working set 双索引
138
+ workingSet: {
139
+ domains: { location: domainLocWS, identity: domainIdWS },
140
+ blueprints: blueprintWS,
141
+ profiles: { location: profileLocWS, identity: profileIdWS },
142
+ },
143
+ };
144
+ },
145
+ };
146
+ /** v15.x PR3:找 active profile——支持 "foo" 和 "@pack/foo" 两种。
147
+ * 限定 ref 直接查目标 pack;不限定按 packs 顺序前者赢(project > settings 倒序 > global > builtin)。
148
+ * v15.x PR4:settings 倒序逻辑由 mdAdapter.load 构造 packs 时已处理,findActiveProfile 不变。 */
149
+ function findActiveProfile(packs, packProfiles, profileRef) {
150
+ if (profileRef.startsWith("@")) {
151
+ const { pack, name } = parseRef(profileRef, ""); // 限定 ref 不需要 selfPack
152
+ const packIdx = packs.findIndex((p) => p.name === pack);
153
+ if (packIdx < 0) {
154
+ throw new Error(`active profile "@${pack}/${name}" references unknown pack "${pack}"`);
155
+ }
156
+ const found = packProfiles[packIdx]?.find((p) => p.name === name);
157
+ if (!found) {
158
+ throw new Error(`active profile "@${pack}/${name}" not found in pack "${pack}"`);
159
+ }
160
+ return found;
161
+ }
162
+ for (let i = 0; i < packs.length; i++) {
163
+ const found = packProfiles[i]?.find((p) => p.name === profileRef);
164
+ if (found)
165
+ return found;
166
+ }
167
+ throw new Error(`active profile "${profileRef}" not found in any pack`);
168
+ }
169
+ /** v15.x PR2(§8.3):找 profile 所属 pack——按 dedup 前顺序,
170
+ * 第一个含该 profile name 的 pack 赢(PR3 后 working set 取代 dedup,但 cache 文件名仍用 pack name)。
171
+ * fallback "prj"(不应到达——active profile 必来自某 pack)。 */
172
+ function findProfilePack(packs, packProfiles, profileName) {
173
+ for (let i = 0; i < packs.length; i++) {
174
+ if (packProfiles[i]?.some((p) => p.name === profileName)) {
175
+ return packs[i].name;
176
+ }
177
+ }
178
+ return "prj";
179
+ }
180
+ /** v15.x PR4(§3.4)+ §3.4 缺口 2(升级 throw):pack name 全局唯一性校验。
181
+ * reserved pack 读 manifest 后,project + settings 同 manifest.name 可能撞名。
182
+ * 3 个场景区分:
183
+ * - 场景 A(路径重叠):settings pack rootDir 与 project pack 一致 → throw + "路径重叠"
184
+ * - 场景 B(不同路径同名):多个 settings pack 同 manifest.name 但 rootDir 不同 → throw + "不同路径同 name"
185
+ * - back-compat:project/settings 覆盖 global/builtin 是合理优先级 → 不 throw,静默(§3.4 表)
186
+ * 错误从 session_start catch 透出到 ctx.ui.notify("error")——用户能直接看到场景区分。
187
+ *
188
+ * reserved 名(prj/gbl/pt)只 1 份,不会冲突。settings pack manifest.name 不能是 reserved 名
189
+ * (PR2 manifest 校验已挡),所以不会与 reserved 重名。 */
190
+ function checkPackNameConflicts(packs, adapterCtx) {
191
+ const seen = new Map();
192
+ for (const pack of packs) {
193
+ const arr = seen.get(pack.name) ?? [];
194
+ arr.push(pack);
195
+ seen.set(pack.name, arr);
196
+ }
197
+ for (const [name, group] of seen) {
198
+ if (group.length <= 1)
199
+ continue;
200
+ // 同 name 多份——按是否含 settings source 分类处理
201
+ const hasSettings = group.some((p) => p.source === "settings");
202
+ if (!hasSettings) {
203
+ // 没有 settings 来源:project+global/builtin 同名 = 合理优先级(§3.4 表),静默
204
+ // 不可能发生(manifest 校验挡 reserved name);保留分支以防未来加新 source
205
+ continue;
206
+ }
207
+ // 有 settings 来源——按 rootDir 是否一致区分场景 A/B
208
+ const rootDirs = new Set(group.map((p) => p.rootDir));
209
+ const sources = group.map((p) => `${p.source}(${p.rootDir})`).join(" + ");
210
+ if (rootDirs.size === 1) {
211
+ // 场景 A:路径重叠(settings pack 与 project pack 指向同一目录)—— 重复加载同一份
212
+ throw new Error(`§3.4 路径重叠:settings pack "@${name}" 与 project pack 指向同一目录 ${[...rootDirs][0]}。` +
213
+ `修复:从 pt.asset-packs 移除重复 path,或从 pt.project-pack-dir / assetDir 切到别处。`);
214
+ }
215
+ // 场景 B:不同路径同名——多份 manifest.name 相同但指向不同目录
216
+ const pathsList = group.map((p) => ` - ${p.source}: ${p.rootDir}`).join("\n");
217
+ throw new Error(`§3.4 不同路径同 name:pack "${name}" 在多个不同路径被声明为同一身份:\n${pathsList}\n` +
218
+ `修复:删掉重复的 pt.asset-packs 条目,或给同名 pack 加唯一 manifest.name 区分。`);
219
+ }
220
+ }
221
+ // 保留 parseX 函数的导出(其他模块 / 测试可能 import)。PR1 阶段 MdFilePack 已复用,
222
+ // 但 parseX 仍作为底层 parser 公开——加新 adapter 类型时可直接 import。
223
+ export { parseBlueprint, parseDomain, parseProfile };
@@ -0,0 +1,18 @@
1
+ import type { ModName, Profile } from "../schema.js";
2
+ import type { SourceAdapterContext } from "../schema.js";
3
+ /** 已知 H2 段名(段名 = 命名空间)。modName 解析时左段必须是段名。
4
+ * 扩展新段名 = 加这一行 + registerModuleRenderer 加一行 + 一致性测试。
5
+ * v9.1+(modules-to-profile-complete):加 "User" / "Agent" 专用段(user-info / agent-info 重命名)。
6
+ * 公开导出(src/asset-health.ts 复用做 unknown-modname 检测——避免重复常量)。 */
7
+ export declare const KNOWN_SECTION_NAMES: ReadonlySet<string>;
8
+ /** 解析 modName 字符串 → { section, item? }。
9
+ * 形态 1: "Scene" → { section: "Scene" }(整段)
10
+ * 形态 2: "User.user-profile" → { section: "User", item: "user-profile" }(单 H3 项)
11
+ * 解析失败(不含 '.' 但段名未知 / 含 '.' 但段名未知 / 段名已知但项名为空)→ 返 null。
12
+ * 不跨段匹配 item("User.user-profile" 不匹配 "Agent" 段下同名 H3)—— 跨段同名由段名命名空间避免。 */
13
+ export declare function parseModName(raw: string): ModName | null;
14
+ /** 读 profiles/<fileName>.md → Profile { name, blueprint, domains, groups }
15
+ * v10.x:assetDir 让 fixtures 可指向 tests/fixtures/assets/(默认 .pt/assets)。
16
+ * v9.1(modules-to-profile 迁移):每个 H2 段加读 `### Modules` 填 ProfileGroup.modules。
17
+ * v9.1+(modules-to-profile-complete):modName 字符串解析为 ModName 对象(2 形态)。 */
18
+ export declare function parseProfile(absDir: string, fileName: string, adapterCtx?: SourceAdapterContext): Promise<Profile>;