@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.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/agent/api-bridge.d.ts +8 -0
- package/dist/agent/api-bridge.js +83 -0
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.js +3 -0
- package/dist/agent/pi-adapter.d.ts +35 -0
- package/dist/agent/pi-adapter.js +251 -0
- package/dist/agent/registry.d.ts +4 -0
- package/dist/agent/registry.js +40 -0
- package/dist/asset-health.d.ts +50 -0
- package/dist/asset-health.js +267 -0
- package/dist/asset-pack/loader.d.ts +47 -0
- package/dist/asset-pack/loader.js +121 -0
- package/dist/asset-pack/manifest.d.ts +23 -0
- package/dist/asset-pack/manifest.js +96 -0
- package/dist/asset-pack/md-file-pack.d.ts +41 -0
- package/dist/asset-pack/md-file-pack.js +153 -0
- package/dist/asset-pack/validate.d.ts +55 -0
- package/dist/asset-pack/validate.js +131 -0
- package/dist/builtin/assets/blueprints/dev-knowledge.blueprint.yaml +13 -0
- package/dist/builtin/assets/domains/agent-info.md +15 -0
- package/dist/builtin/assets/domains/authoring.md +201 -0
- package/dist/builtin/assets/domains/pack-repair.md +47 -0
- package/dist/builtin/assets/domains/project-analysis.md +52 -0
- package/dist/builtin/assets/domains/usage.md +81 -0
- package/dist/builtin/assets/domains/user-info.md +15 -0
- package/dist/builtin/assets/profiles/guide.profile.md +28 -0
- package/dist/commands.d.ts +69 -0
- package/dist/commands.js +344 -0
- package/dist/compile/agent-context.d.ts +48 -0
- package/dist/compile/agent-context.js +423 -0
- package/dist/compile/index.d.ts +1 -0
- package/dist/compile/index.js +5 -0
- package/dist/compile/resolve-use.d.ts +36 -0
- package/dist/compile/resolve-use.js +171 -0
- package/dist/compile/type-guards.d.ts +29 -0
- package/dist/compile/type-guards.js +107 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +133 -0
- package/dist/constants.d.ts +52 -0
- package/dist/constants.js +81 -0
- package/dist/diagnostics.d.ts +14 -0
- package/dist/diagnostics.js +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +838 -0
- package/dist/injection-status.d.ts +45 -0
- package/dist/injection-status.js +134 -0
- package/dist/log.d.ts +64 -0
- package/dist/log.js +139 -0
- package/dist/manual-session.d.ts +24 -0
- package/dist/manual-session.js +152 -0
- package/dist/manual-track.d.ts +35 -0
- package/dist/manual-track.js +107 -0
- package/dist/parse/blueprint.d.ts +5 -0
- package/dist/parse/blueprint.js +59 -0
- package/dist/parse/domain-renderers.d.ts +8 -0
- package/dist/parse/domain-renderers.js +115 -0
- package/dist/parse/domain.d.ts +7 -0
- package/dist/parse/domain.js +69 -0
- package/dist/parse/index.d.ts +9 -0
- package/dist/parse/index.js +223 -0
- package/dist/parse/profile.d.ts +18 -0
- package/dist/parse/profile.js +117 -0
- package/dist/parse/ref-resolver.d.ts +63 -0
- package/dist/parse/ref-resolver.js +174 -0
- package/dist/parse/shared.d.ts +76 -0
- package/dist/parse/shared.js +273 -0
- package/dist/profile-persist.d.ts +22 -0
- package/dist/profile-persist.js +53 -0
- package/dist/render/cache.d.ts +15 -0
- package/dist/render/cache.js +135 -0
- package/dist/render/index.d.ts +3 -0
- package/dist/render/index.js +14 -0
- package/dist/render/session-inject.d.ts +5 -0
- package/dist/render/session-inject.js +24 -0
- package/dist/render/turn-inject.d.ts +36 -0
- package/dist/render/turn-inject.js +248 -0
- package/dist/schema.d.ts +448 -0
- package/dist/schema.js +45 -0
- package/dist/session.d.ts +82 -0
- package/dist/session.js +102 -0
- package/dist/slog.d.ts +3 -0
- package/dist/slog.js +27 -0
- package/dist/transpile.d.ts +34 -0
- package/dist/transpile.js +220 -0
- package/dist/verify/file-hash.d.ts +2 -0
- package/dist/verify/file-hash.js +30 -0
- package/dist/verify/fs-content-match.d.ts +2 -0
- package/dist/verify/fs-content-match.js +26 -0
- package/dist/verify/fs-exists.d.ts +2 -0
- package/dist/verify/fs-exists.js +13 -0
- package/dist/verify/fs-not-exists.d.ts +2 -0
- package/dist/verify/fs-not-exists.js +13 -0
- package/dist/verify/git-status-clean.d.ts +2 -0
- package/dist/verify/git-status-clean.js +19 -0
- package/dist/verify/index.d.ts +7 -0
- package/dist/verify/index.js +46 -0
- package/dist/verify/lint-check.d.ts +2 -0
- package/dist/verify/lint-check.js +17 -0
- package/dist/verify/read-stderr.d.ts +5 -0
- package/dist/verify/read-stderr.js +20 -0
- package/dist/verify/ref-check.d.ts +13 -0
- package/dist/verify/ref-check.js +88 -0
- package/dist/verify/test-pass.d.ts +2 -0
- package/dist/verify/test-pass.js +17 -0
- package/dist/verify/ts-compiles.d.ts +2 -0
- package/dist/verify/ts-compiles.js +17 -0
- package/package.json +62 -0
- package/src/agent/api-bridge.ts +105 -0
- package/src/agent/index.ts +4 -0
- package/src/agent/pi-adapter.ts +326 -0
- package/src/agent/registry.ts +44 -0
- package/src/asset-health.ts +327 -0
- package/src/asset-pack/loader.ts +141 -0
- package/src/asset-pack/manifest.ts +118 -0
- package/src/asset-pack/md-file-pack.ts +202 -0
- package/src/asset-pack/validate.ts +186 -0
- package/src/builtin/assets/blueprints/dev-knowledge.blueprint.yaml +13 -0
- package/src/builtin/assets/domains/agent-info.md +15 -0
- package/src/builtin/assets/domains/authoring.md +201 -0
- package/src/builtin/assets/domains/pack-repair.md +47 -0
- package/src/builtin/assets/domains/project-analysis.md +52 -0
- package/src/builtin/assets/domains/usage.md +81 -0
- package/src/builtin/assets/domains/user-info.md +15 -0
- package/src/builtin/assets/profiles/guide.profile.md +28 -0
- package/src/commands.ts +405 -0
- package/src/compile/agent-context.ts +487 -0
- package/src/compile/index.ts +5 -0
- package/src/compile/resolve-use.ts +208 -0
- package/src/compile/type-guards.ts +132 -0
- package/src/config.ts +154 -0
- package/src/constants.ts +104 -0
- package/src/diagnostics.ts +36 -0
- package/src/index.ts +1028 -0
- package/src/injection-status.ts +155 -0
- package/src/log.ts +173 -0
- package/src/manual-session.ts +190 -0
- package/src/manual-track.ts +127 -0
- package/src/parse/blueprint.ts +82 -0
- package/src/parse/domain-renderers.ts +120 -0
- package/src/parse/domain.ts +78 -0
- package/src/parse/index.ts +266 -0
- package/src/parse/profile.ts +139 -0
- package/src/parse/ref-resolver.ts +198 -0
- package/src/parse/shared.ts +331 -0
- package/src/profile-persist.ts +60 -0
- package/src/render/cache.ts +150 -0
- package/src/render/index.ts +14 -0
- package/src/render/session-inject.ts +26 -0
- package/src/render/turn-inject.ts +278 -0
- package/src/schema.ts +542 -0
- package/src/session.ts +190 -0
- package/src/slog.ts +32 -0
- package/src/transpile.ts +302 -0
- package/src/verify/file-hash.ts +29 -0
- package/src/verify/fs-content-match.ts +28 -0
- package/src/verify/fs-exists.ts +14 -0
- package/src/verify/fs-not-exists.ts +17 -0
- package/src/verify/git-status-clean.ts +23 -0
- package/src/verify/index.ts +57 -0
- package/src/verify/lint-check.ts +21 -0
- package/src/verify/read-stderr.ts +24 -0
- package/src/verify/ref-check.ts +118 -0
- package/src/verify/test-pass.ts +18 -0
- package/src/verify/ts-compiles.ts +21 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
// src/compile/agent-context.ts — 中端:Profile + Blueprint + Domains → AgentContext IR
|
|
2
|
+
//
|
|
3
|
+
// Phase 9.4:v9 中端重写。
|
|
4
|
+
// - 输入:profile(v9 配置:blueprint + domains + groups)+ blueprint(v9 结构:groups)+ domains[]
|
|
5
|
+
// - 输出:AgentContext IR(v9 产物层)—— modules: Record<聚合组名, 聚合后 markdown>
|
|
6
|
+
//
|
|
7
|
+
// Phase term-P1:Context IR 改名 AgentContext(避免与 Pi 的 context_message 撞名,加入 Agent 概念族)。
|
|
8
|
+
// 文件同步改名 src/compile/context.ts → src/compile/agent-context.ts。
|
|
9
|
+
//
|
|
10
|
+
// v9 编译流程:
|
|
11
|
+
// 遍历 Blueprint.groups:
|
|
12
|
+
// 1. 找 Profile 对应 ProfileGroup(同名)—— 聚合组追加的 Domains
|
|
13
|
+
// 2. resolveDomains(profile, group, bpGroup, domainByName) — 合并全局 + 追加,按 Blueprint.modules 过滤
|
|
14
|
+
// 3. dispatchGroup — 遍历 bpGroup.modules,按 modName 注册表聚合
|
|
15
|
+
//
|
|
16
|
+
// v9 核心变化:
|
|
17
|
+
// - dispatchGroup 按 modName 驱动聚合(v8 按 target 硬编码)
|
|
18
|
+
// - moduleRenderers 注册表替代 domainSceneRenderers(v8 按 type 分发)
|
|
19
|
+
// - 加新聚合标题(Blueprint.modules 加项)= 改 Blueprint,不用改代码;generic fallback 自动处理
|
|
20
|
+
// - Trigger 段聚合在 session 聚合组,作为索引段
|
|
21
|
+
// - Profile domains 自动分发:YAML 全局 domains + 聚合组追加
|
|
22
|
+
//
|
|
23
|
+
// Tech Debt T6: 全用 type guard 收窄,不用 as 断言(pt-quality #1)
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
MOD_AGENT,
|
|
27
|
+
MOD_CHECKLISTS,
|
|
28
|
+
MOD_FLOWS,
|
|
29
|
+
MOD_PARTICIPANT,
|
|
30
|
+
MOD_RULES,
|
|
31
|
+
MOD_SCENE,
|
|
32
|
+
MOD_TRIGGER,
|
|
33
|
+
MOD_USER,
|
|
34
|
+
} from "../constants.js";
|
|
35
|
+
import { reportWarn } from "../diagnostics.js";
|
|
36
|
+
import { resolveAndDedupRefs } from "../parse/ref-resolver.js";
|
|
37
|
+
import type {
|
|
38
|
+
AgentContext,
|
|
39
|
+
AssetPack,
|
|
40
|
+
Blueprint,
|
|
41
|
+
BlueprintGroup,
|
|
42
|
+
Domain,
|
|
43
|
+
ModName,
|
|
44
|
+
Profile,
|
|
45
|
+
ProfileGroup,
|
|
46
|
+
SchemaBundle,
|
|
47
|
+
SourceAdapterContext,
|
|
48
|
+
StructureLayout,
|
|
49
|
+
Term,
|
|
50
|
+
} from "../schema.js";
|
|
51
|
+
import {
|
|
52
|
+
isChecklistArray,
|
|
53
|
+
isFlowTemplateArray,
|
|
54
|
+
isNamedItemArray,
|
|
55
|
+
isRecord,
|
|
56
|
+
isRuleArray,
|
|
57
|
+
isTermArray,
|
|
58
|
+
isTriggerItemArray,
|
|
59
|
+
} from "./type-guards.js";
|
|
60
|
+
|
|
61
|
+
// ==================== AgentContext 编译入口 ====================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 编译 Profile 为 AgentContext IR(v9)。
|
|
65
|
+
* - 遍历 Blueprint.groups
|
|
66
|
+
* - 每个聚合组找 Profile 同名 ProfileGroup
|
|
67
|
+
* - 按 modName 注册表聚合(dispatchGroup)—— modName 来源从 Blueprint.modules 改为 ProfileGroup.modules
|
|
68
|
+
*
|
|
69
|
+
* Phase term-P1:函数名 compileContext → compileAgentContext(IR 改名同步)。
|
|
70
|
+
* Phase term-P-modules-to-profile:
|
|
71
|
+
* - Blueprint 退化为插槽契约(无 modules 字段),modules 由 ProfileGroup.modules 提供
|
|
72
|
+
* - 加越权/缺填告警(log warn + notify / log debug only)—— 设计文档 §5
|
|
73
|
+
* - ctx 可选:测试 / CLI 直接调可不传;运行链路(transpile)传入同 adapterCtx 用 logger 留痕
|
|
74
|
+
*/
|
|
75
|
+
export function compileAgentContext(
|
|
76
|
+
profile: Profile,
|
|
77
|
+
blueprint: Blueprint,
|
|
78
|
+
domains: Domain[],
|
|
79
|
+
packs: AssetPack[],
|
|
80
|
+
profilePack: string,
|
|
81
|
+
workingSet: SchemaBundle["workingSet"],
|
|
82
|
+
ctx?: SourceAdapterContext
|
|
83
|
+
): AgentContext {
|
|
84
|
+
const loadedPackNames = packs.map((p) => p.name);
|
|
85
|
+
// 1. 按 Blueprint 的聚合组遍历
|
|
86
|
+
const modules: Record<string, string> = {};
|
|
87
|
+
for (const bpGroup of blueprint.groups) {
|
|
88
|
+
// 找 Profile 对应的聚合组实例化(同名)
|
|
89
|
+
const profileGroup = profile.groups.find((g) => g.name === bpGroup.name);
|
|
90
|
+
|
|
91
|
+
// 缺填告警:Blueprint 有此插槽但 Profile 没填 modules
|
|
92
|
+
// issue pt-status-no-injection-state:升级为 reportWarn——
|
|
93
|
+
// v9.1 modules-to-profile 迁移后,"未填 modules"几乎总是配置错误,
|
|
94
|
+
// 不再是 v9 的"故意留空"。三通道 fallback 让用户首次切换就看到告警,
|
|
95
|
+
// 避免切换后 footer 永远 idle 却 `injected: true` 误导
|
|
96
|
+
if (!profileGroup || profileGroup.modules.length === 0) {
|
|
97
|
+
reportWarn(
|
|
98
|
+
ctx,
|
|
99
|
+
`Profile「${profile.name}」插槽「${bpGroup.name}」未填 modules(segment 会为空)`,
|
|
100
|
+
{
|
|
101
|
+
profile: profile.name,
|
|
102
|
+
group: bpGroup.name,
|
|
103
|
+
hasGroup: !!profileGroup,
|
|
104
|
+
hint: "在 Profile 的 H2 段下加 `### Modules` 列出 modName(段名或 段.项 形态)",
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// v15.x PR3(§4.4.3 #2):resolveDomains 改用 resolveAndDedupRefs + workingSet
|
|
110
|
+
const refDomains = resolveDomains(profile, profileGroup, bpGroup, workingSet, loadedPackNames);
|
|
111
|
+
|
|
112
|
+
// 按 modName 驱动聚合(来源 = profileGroup.modules)
|
|
113
|
+
modules[bpGroup.name] = dispatchGroup(profileGroup, bpGroup, refDomains);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. 越权告警:Profile 有 Blueprint 未声明的 H2(遍历 profile.groups 找 bpGroup 没有的)
|
|
117
|
+
// 几乎总是错误 → log warn + notify(设计文档 §5)。reportWarn 内部已 log → notify → console 三通道 fallback。
|
|
118
|
+
const bpNames = new Set(blueprint.groups.map((g) => g.name));
|
|
119
|
+
for (const pg of profile.groups) {
|
|
120
|
+
if (!bpNames.has(pg.name)) {
|
|
121
|
+
// v15.x PR5(§5.5.1 S7):非 use 场景越权 warn——profile 有 blueprint 没有的 group
|
|
122
|
+
// use 场景的越权已在 expandProfile 报 error(§5.5.1);此处是普通 Profile 加载 warn
|
|
123
|
+
// back-compat:之前不检测,现加 warn 不破坏行为(只提示,不阻断)
|
|
124
|
+
reportWarn(
|
|
125
|
+
ctx,
|
|
126
|
+
`Profile「${profile.name}」的 H2「${pg.name}」不在 Blueprint 插槽中(越权,被忽略)`,
|
|
127
|
+
{
|
|
128
|
+
profile: profile.name,
|
|
129
|
+
group: pg.name,
|
|
130
|
+
hint: "use 场景此为 error(PR5 expandProfile 报 BlueprintGroupOutOfScope);非 use 场景此为 warn(group 被忽略,不阻断)",
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 4. 算 sourceHash(PR2 加 packs 维度)
|
|
137
|
+
const sourceHash = computeSourceHash(profile, blueprint, domains, packs);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
name: profile.name,
|
|
141
|
+
blueprint: profile.blueprint,
|
|
142
|
+
sourceHash,
|
|
143
|
+
modules,
|
|
144
|
+
// v15.x PR2(§8.3):packName 用于 cache 文件名 <pack>__<profile>
|
|
145
|
+
packName: profilePack,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** v9 Domains 分发:全局 domains + 聚合组追加(去重,保序),按 ProfileGroup.modules 过滤。
|
|
150
|
+
* 规则:Domain 有该聚合组 modules 列出的任一 H2 段 → 贡献;没有 → 跳过。
|
|
151
|
+
* v9.1(modules-to-profile 迁移):modules 来源从 Blueprint.modules 改为 ProfileGroup.modules
|
|
152
|
+
* —— Blueprint 退化为插槽契约,Profile 自带 modules 选择。ProfileGroup 不存在或 modules 为空
|
|
153
|
+
* 时返空数组(与 dispatchGroup 行为一致——产出空段由 render 跳过)。
|
|
154
|
+
*
|
|
155
|
+
* 这就是 v9 "Domain 同一份内容可贡献多聚合组" 的语义——Profile 引用的 Domain,
|
|
156
|
+
* 只有其 H2 段匹配 ProfileGroup.modules 时才进当前聚合组。 */
|
|
157
|
+
function resolveDomains(
|
|
158
|
+
profile: Profile,
|
|
159
|
+
profileGroup: ProfileGroup | undefined,
|
|
160
|
+
_bpGroup: BlueprintGroup,
|
|
161
|
+
workingSet: SchemaBundle["workingSet"],
|
|
162
|
+
loadedPackNames: string[]
|
|
163
|
+
): Domain[] {
|
|
164
|
+
// 合并:全局 domains + 聚合组追加(保序,resolveAndDedupRefs 内做 fp dedup)
|
|
165
|
+
const allRefs = [...profile.domains];
|
|
166
|
+
if (profileGroup) {
|
|
167
|
+
for (const dn of profileGroup.domains) {
|
|
168
|
+
if (!allRefs.includes(dn)) allRefs.push(dn);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// v15.x PR3(§4.4.2):resolveAndDedupRefs 解析 + fp dedup
|
|
173
|
+
// skipOnMissing=true:missing ref 静默跳过(与 dedupByNameN 旧行为一致)——空段由 empty-segment 检测单独报。
|
|
174
|
+
const resolved = resolveAndDedupRefs<Domain>(
|
|
175
|
+
allRefs,
|
|
176
|
+
profile,
|
|
177
|
+
workingSet.domains,
|
|
178
|
+
loadedPackNames,
|
|
179
|
+
{ skipOnMissing: true }
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// v9.1:从 ProfileGroup.modules 读过滤白名单(Blueprint.modules 已删除)
|
|
183
|
+
const mods = profileGroup?.modules ?? [];
|
|
184
|
+
return resolved
|
|
185
|
+
.map((r) => r.asset)
|
|
186
|
+
.filter((d) => mods.some((m) => d.modules[m.section] !== undefined));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ==================== §4.5.2:mergeSectionContent + mergeByName(PR3b) ====================
|
|
190
|
+
|
|
191
|
+
/** v15.x PR3b(§4.5.2):合并多份同 name asset 的同 H2 段内容(按 schema 分发 mixin)。
|
|
192
|
+
*
|
|
193
|
+
* 场景 D/E(不同 pack 同 name)→ resolveAndDedupRefs 保留多份 → dispatchGroup 遍历多份。
|
|
194
|
+
* 本函数在调 renderer 前合并内容,让多份 asset 的同 H2 段 union 后渲染一次(§4.5.2 期望)。
|
|
195
|
+
*
|
|
196
|
+
* 按 schema 分发:
|
|
197
|
+
* - Term[](Scene/User/Agent/Participant):Map by Term.name,后写覆盖前写
|
|
198
|
+
* - Trigger 项[]:Map by name,后写覆盖前写
|
|
199
|
+
* - Rule[] / FlowTemplate[] / Checklist[]:Map by name,后写覆盖前写
|
|
200
|
+
* - 未知段(fallback):保守取首份——避免未知 schema 误合并
|
|
201
|
+
*
|
|
202
|
+
* 单份 content(parts.length === 1)→ 原样返回(back-compat 零开销)。
|
|
203
|
+
* 所有 domain 都无该 section → undefined。 */
|
|
204
|
+
export function mergeSectionContent(domains: Domain[], section: string): unknown {
|
|
205
|
+
const parts: unknown[] = [];
|
|
206
|
+
for (const d of domains) {
|
|
207
|
+
const content = d.modules[section];
|
|
208
|
+
if (content !== undefined) parts.push(content);
|
|
209
|
+
}
|
|
210
|
+
if (parts.length === 0) return undefined;
|
|
211
|
+
if (parts.length === 1) return parts[0]; // 单份直接返——back-compat
|
|
212
|
+
|
|
213
|
+
// 按 schema 分发 union(用现有 type guards,不引新 guard)
|
|
214
|
+
// 内部断言 unknown[][]——type guard 已 narrow 元素形态(Rule / Term / 等)
|
|
215
|
+
const partsAsArrays = parts as unknown[][];
|
|
216
|
+
if (isTermArray(parts[0])) return mergeByName(partsAsArrays);
|
|
217
|
+
if (isTriggerItemArray(parts[0])) return mergeByName(partsAsArrays);
|
|
218
|
+
if (isRuleArray(parts[0])) return mergeByName(partsAsArrays);
|
|
219
|
+
if (isFlowTemplateArray(parts[0])) return mergeByName(partsAsArrays);
|
|
220
|
+
if (isChecklistArray(parts[0])) return mergeByName(partsAsArrays);
|
|
221
|
+
if (isNamedItemArray(parts[0])) return mergeByName(partsAsArrays);
|
|
222
|
+
// 未知 schema:保守策略——只取第一份(不 union,避免误合并)
|
|
223
|
+
return parts[0];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** 按 name 字段 union,后写覆盖前写(Map.set 语义)。
|
|
227
|
+
* 保留原顺序(Map.values 按首次插入)——dispatchGroup 遍历顺序 = 原列表首次出现顺序。
|
|
228
|
+
* 接受 unknown[][]——调用方 type guard 担保元素形态(Rule / Term / 等)。 */
|
|
229
|
+
function mergeByName(parts: unknown[][]): unknown[] {
|
|
230
|
+
const map = new Map<string, unknown>();
|
|
231
|
+
for (const arr of parts) {
|
|
232
|
+
for (const item of arr) {
|
|
233
|
+
if (item && typeof item === "object" && "name" in item && typeof item.name === "string") {
|
|
234
|
+
map.set(item.name, item);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return [...map.values()];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ==================== modName 驱动聚合(v9 核心) ====================
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* v9 dispatchGroup:按 modName 驱动聚合。
|
|
245
|
+
* - 遍历 profileGroup.modules(Profile 填的聚合标题列表)
|
|
246
|
+
* - 每个 modName 调对应 moduleRenderers[modName] 渲染
|
|
247
|
+
* - renderer 内部按段名 spec 取内容格式(Phase term-P9.3:不再按 d.type 分发)
|
|
248
|
+
* - inject 不在此判断——inject 决定注入位置,由 AgentAdapter 处理(compile 不感知 Agent)
|
|
249
|
+
*
|
|
250
|
+
* v9.1(modules-to-profile 迁移):modName 来源从 BlueprintGroup.modules 改为
|
|
251
|
+
* ProfileGroup.modules。Blueprint 退化为插槽契约(声明有哪些插槽 + inject + mode),
|
|
252
|
+
* Profile 通过 H2 `### Modules` 填聚合标题列表。
|
|
253
|
+
*/
|
|
254
|
+
export function dispatchGroup(
|
|
255
|
+
profileGroup: ProfileGroup | undefined,
|
|
256
|
+
bpGroup: BlueprintGroup,
|
|
257
|
+
refDomains: Domain[]
|
|
258
|
+
): string {
|
|
259
|
+
const parts: string[] = [];
|
|
260
|
+
|
|
261
|
+
// 遍历 ProfileGroup.modules 列出的 ModName(Blueprint.modules 已删除)
|
|
262
|
+
// v9.1+(modules-to-profile-complete):mod 是 { section, item? } 结构
|
|
263
|
+
// - 段粒度:mod.item 未定义 → 整段渲染
|
|
264
|
+
// - 段.项粒度:mod.item 已定义 → 单 H3 项渲染
|
|
265
|
+
// v15.x PR3b(§4.5.2):场景 D/E(多份同 name asset)→ 调 renderer 前先 mergeSectionContent
|
|
266
|
+
// 合并 H2 段内容(按 schema union by name);d0 = 首个有该 section 的 domain 做 ### 标题。
|
|
267
|
+
// 单份场景 back-compat:mergeSectionContent 原样返 content + d0 = 那份 domain。
|
|
268
|
+
const mods = profileGroup?.modules ?? [];
|
|
269
|
+
for (const mod of mods) {
|
|
270
|
+
const mergedContent = mergeSectionContent(refDomains, mod.section);
|
|
271
|
+
if (mergedContent === undefined) continue;
|
|
272
|
+
const d0 = refDomains.find((d) => d.modules[mod.section] !== undefined);
|
|
273
|
+
if (!d0) continue; // 防御——mergeSectionContent 已挡,这里兑底
|
|
274
|
+
|
|
275
|
+
const rendered = mod.item
|
|
276
|
+
? renderItemModule(d0, mergedContent, mod)
|
|
277
|
+
: (moduleRenderers[mod.section] ?? renderGenericModule)(d0, mergedContent, bpGroup.mode);
|
|
278
|
+
if (rendered) parts.push(rendered);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return parts.join("\n\n").trimEnd();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ==================== moduleRenderers 注册表(替代 v8 domainSceneRenderers) ====================
|
|
285
|
+
|
|
286
|
+
type ModuleRenderer = (d: Domain, content: unknown, mode?: StructureLayout["mode"]) => string;
|
|
287
|
+
|
|
288
|
+
/** modName → renderer。已注册:Scene/Trigger/Rules/Flows/Checklists/Participant。
|
|
289
|
+
* Phase term-P9.2:从 Manual 拆出 Rules/Flows/Checklists 三个 H2 段。
|
|
290
|
+
* Phase term-P8:加 Participant,复用 renderSceneModule(与 Scene 同构——都是 Term[])。
|
|
291
|
+
* 扩展:调 registerModuleRenderer("xxx", fn) 加一行 + 一个函数即可,不动主循环。
|
|
292
|
+
* 加新聚合标题(Blueprint.modules 加项)不注册 = 走 generic fallback(自动按 H3 + name/desc 输出)。 */
|
|
293
|
+
const moduleRenderers: Record<string, ModuleRenderer> = {
|
|
294
|
+
[MOD_SCENE]: renderSceneModule,
|
|
295
|
+
[MOD_TRIGGER]: renderTriggerModule,
|
|
296
|
+
[MOD_RULES]: renderRulesModule,
|
|
297
|
+
[MOD_FLOWS]: renderFlowsModule,
|
|
298
|
+
[MOD_CHECKLISTS]: renderChecklistsModule,
|
|
299
|
+
[MOD_PARTICIPANT]: renderSceneModule, // P8:复用 Scene renderer(Term[] 同构,带 ### domain 标题)
|
|
300
|
+
[MOD_USER]: renderSceneModule, // v9.1+:user-info 专用段(Term[] 同构)
|
|
301
|
+
[MOD_AGENT]: renderSceneModule, // v9.1+:agent-info 专用段(Term[] 同构)
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/** 扩展接口:加新 modName 只加一行 + 一个 renderer 函数。 */
|
|
305
|
+
export function registerModuleRenderer(modName: string, fn: ModuleRenderer): void {
|
|
306
|
+
moduleRenderers[modName] = fn;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ==================== Scene module renderer(Phase term-P9.3:去 type switch) ====================
|
|
310
|
+
|
|
311
|
+
/** Scene 段聚合:统一为 Term[] 列表(Phase term-P9.1:term + workflow Scene 合并)。
|
|
312
|
+
* - 无 type 区分——所有 Domain 的 Scene 段统一走 Term[] schema(path 可选)。
|
|
313
|
+
* - 渲染:有 path → `- name: path — desc`;无 path → `- name: desc`。
|
|
314
|
+
* - 附加 fields/note。
|
|
315
|
+
* hybrid mode 下 rule 也可从 Scene 抽——但 v9 规则在 Rules 段,Scene 段只承载场景元数据。 */
|
|
316
|
+
function renderSceneModule(d: Domain, content: unknown, _mode?: StructureLayout["mode"]): string {
|
|
317
|
+
if (!isTermArray(content)) return "";
|
|
318
|
+
const lines: string[] = [`### ${d.name}`];
|
|
319
|
+
for (const t of content) {
|
|
320
|
+
let line: string;
|
|
321
|
+
if (t.path && t.desc) line = `- ${t.name}: ${t.path} — ${t.desc}`;
|
|
322
|
+
else if (t.path) line = `- ${t.name}: ${t.path}`;
|
|
323
|
+
else if (t.desc) line = `- ${t.name}: ${t.desc}`;
|
|
324
|
+
else line = `- ${t.name}`;
|
|
325
|
+
// v9.2:附加 fields/note
|
|
326
|
+
if (t.fields && t.fields.length > 0) {
|
|
327
|
+
line += `(字段:${t.fields.join("/")})`;
|
|
328
|
+
}
|
|
329
|
+
if (t.note) {
|
|
330
|
+
line += ` — ${t.note}`;
|
|
331
|
+
}
|
|
332
|
+
lines.push(line);
|
|
333
|
+
}
|
|
334
|
+
return lines.join("\n").trimEnd();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ==================== H3 项粒度 renderer(v9.1+ modules-to-profile-complete) ====================
|
|
338
|
+
|
|
339
|
+
/** H3 项粒度渲染:输出 `### <domain>.<item>` 形式。
|
|
340
|
+
* 复用 Scene/Participant/User/Agent renderer 的输出风格:带 fields/note/path。
|
|
341
|
+
* 适用 mod:ModName.item 已定义。
|
|
342
|
+
* 跨段同名 H3 项不跨段匹配——"User.user-profile" 只匹配 User 段下 user-profile,
|
|
343
|
+
* 不匹配 Agent 段下同名 H3。设计约束:H3 项名跨段由段名命名空间避免。 */
|
|
344
|
+
function renderItemModule(d: Domain, content: unknown, mod: ModName): string {
|
|
345
|
+
if (!mod.item) return ""; // 防御:未到这里的 caller 已用段粒度路径
|
|
346
|
+
if (!isTermArray(content)) return "";
|
|
347
|
+
// 同段内 H3 重名后覆盖前——只取最后一个(parse 时同名 H3 后出现覆盖前)
|
|
348
|
+
const items = content as Array<Term & { name: string }>;
|
|
349
|
+
let found: Term | undefined;
|
|
350
|
+
for (const it of items) {
|
|
351
|
+
if (it.name === mod.item) found = it;
|
|
352
|
+
}
|
|
353
|
+
if (!found) return "";
|
|
354
|
+
const t = found as Term;
|
|
355
|
+
const lines: string[] = [`### ${d.name}.${t.name}`];
|
|
356
|
+
let line: string;
|
|
357
|
+
if (t.path && t.desc) line = `- ${t.name}: ${t.path} — ${t.desc}`;
|
|
358
|
+
else if (t.path) line = `- ${t.name}: ${t.path}`;
|
|
359
|
+
else if (t.desc) line = `- ${t.name}: ${t.desc}`;
|
|
360
|
+
else line = `- ${t.name}`;
|
|
361
|
+
if (t.fields && t.fields.length > 0) {
|
|
362
|
+
line += `(字段:${t.fields.join("/")})`;
|
|
363
|
+
}
|
|
364
|
+
if (t.note) {
|
|
365
|
+
line += ` — ${t.note}`;
|
|
366
|
+
}
|
|
367
|
+
lines.push(line);
|
|
368
|
+
return lines.join("\n").trimEnd();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ==================== Trigger module renderer(索引聚合,v9 新增) ====================
|
|
372
|
+
|
|
373
|
+
/** Trigger 段聚合:把所有 Domain 的 Trigger 项合并成索引。
|
|
374
|
+
* v9 关键:Trigger 是索引,告诉 LLM "有什么手册可查 + 何时查"。不加模块标题——索引段是平的。 */
|
|
375
|
+
function renderTriggerModule(_d: Domain, content: unknown): string {
|
|
376
|
+
if (!isTriggerItemArray(content)) return "";
|
|
377
|
+
if (content.length === 0) return "";
|
|
378
|
+
const lines: string[] = [];
|
|
379
|
+
for (const t of content) {
|
|
380
|
+
if (t.desc) lines.push(`- ${t.name}: ${t.desc}`);
|
|
381
|
+
else lines.push(`- ${t.name}`);
|
|
382
|
+
if (t.hint) lines.push(` ${t.hint}`);
|
|
383
|
+
}
|
|
384
|
+
return lines.join("\n");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ==================== Manual module renderer(聚合参考手册,P9.2 拆三段) ====================
|
|
388
|
+
|
|
389
|
+
/** Phase term-P9.2:从 renderManualModule 拆出 Rules/Flows/Checklists 三段。
|
|
390
|
+
* 每个 renderer 内部直接调对应 type guard(不再依赖 d.type——P9.3 后 type 字段删除)。 */
|
|
391
|
+
|
|
392
|
+
/** Rules 段:Rule[](term 形态,含 slot/type/check/items) */
|
|
393
|
+
function renderRulesModule(d: Domain, content: unknown): string {
|
|
394
|
+
if (!isRuleArray(content)) return "";
|
|
395
|
+
const lines: string[] = [];
|
|
396
|
+
for (const r of content) {
|
|
397
|
+
if (r.type === "invariant") {
|
|
398
|
+
if (r.check) lines.push(`- ${r.name}: ${r.check}`);
|
|
399
|
+
else lines.push(`- ${r.name}`);
|
|
400
|
+
} else if (r.type === "ban" && r.items && r.items.length > 0) {
|
|
401
|
+
if (r.check) lines.push(`- ${r.name}: ${r.check} (${r.items.join(" / ")})`);
|
|
402
|
+
else lines.push(`- ${r.name}: ${r.items.join(" / ")}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (lines.length === 0) return "";
|
|
406
|
+
return `### ${d.name}\n\n${lines.join("\n")}`.trimEnd();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Flows 段:FlowTemplate[](workflow 形态,含 argumentHint/intent/steps) */
|
|
410
|
+
function renderFlowsModule(d: Domain, content: unknown): string {
|
|
411
|
+
if (!isFlowTemplateArray(content)) return "";
|
|
412
|
+
const lines: string[] = [];
|
|
413
|
+
for (const t of content) {
|
|
414
|
+
const hint = t.argumentHint ? ` ${t.argumentHint}` : "";
|
|
415
|
+
lines.push(`- ${t.name}${hint}: ${t.intent}`);
|
|
416
|
+
}
|
|
417
|
+
if (lines.length === 0) return "";
|
|
418
|
+
return `### ${d.name}\n\n${lines.join("\n")}`.trimEnd();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Checklists 段:Checklist[](name + items[]) */
|
|
422
|
+
function renderChecklistsModule(d: Domain, content: unknown): string {
|
|
423
|
+
if (!isChecklistArray(content)) return "";
|
|
424
|
+
const sections: string[] = [];
|
|
425
|
+
for (const cl of content) {
|
|
426
|
+
const items = cl.items.map((i) => `- ${i}`).join("\n");
|
|
427
|
+
sections.push(`### ${cl.name}\n${items}`);
|
|
428
|
+
}
|
|
429
|
+
if (sections.length === 0) return "";
|
|
430
|
+
return `### ${d.name}\n\n${sections.join("\n\n")}`.trimEnd();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ==================== Generic fallback(扩展 modName 时自动适用) ====================
|
|
434
|
+
|
|
435
|
+
/** Generic fallback:原样输出 H3 标题 + 列表项,不解析内容格式。
|
|
436
|
+
* 加新聚合标题(如 ### Modules 加 ## Glossary)不用注册 renderer,自动用 fallback 聚合。 */
|
|
437
|
+
function renderGenericModule(_d: Domain, content: unknown): string {
|
|
438
|
+
if (!isNamedItemArray(content)) return "";
|
|
439
|
+
const lines: string[] = [];
|
|
440
|
+
for (const item of content) {
|
|
441
|
+
if (item.desc) lines.push(`- ${item.name}: ${item.desc}`);
|
|
442
|
+
else lines.push(`- ${item.name}`);
|
|
443
|
+
}
|
|
444
|
+
return lines.join("\n");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ==================== sourceHash ====================
|
|
448
|
+
|
|
449
|
+
/** v15.x PR2(§8.1):sourceHash 加 packs[].{name, rootDir}(不含 version/description)。
|
|
450
|
+
* pack 内容变化触发 cache 失效;pack version 变化不进 hash(M1:内容 hash 已覆盖)。
|
|
451
|
+
* pack 列表顺序敏感——dedupByNameN 后的 pack 顺序固定,添加 settings pack 会改 hash。 */
|
|
452
|
+
export function computeSourceHash(
|
|
453
|
+
profile: Profile,
|
|
454
|
+
blueprint: Blueprint,
|
|
455
|
+
domains: Domain[],
|
|
456
|
+
packs: AssetPack[]
|
|
457
|
+
): string {
|
|
458
|
+
const payload = JSON.stringify({
|
|
459
|
+
profile: stableStringify(profile),
|
|
460
|
+
blueprint: stableStringify(blueprint),
|
|
461
|
+
domains: domains.map((d) => stableStringify(d)),
|
|
462
|
+
packs: packs.map((p) => ({
|
|
463
|
+
name: p.name,
|
|
464
|
+
rootDir: p.rootDir,
|
|
465
|
+
// version 不进 hash(M1):内容 hash 已覆盖;version 进诊断即可
|
|
466
|
+
})),
|
|
467
|
+
});
|
|
468
|
+
return simpleHash(payload);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function stableStringify(obj: unknown): string {
|
|
472
|
+
if (obj === null || typeof obj !== "object") return JSON.stringify(obj);
|
|
473
|
+
if (Array.isArray(obj)) return `[${obj.map(stableStringify).join(",")}]`;
|
|
474
|
+
if (!isRecord(obj)) return JSON.stringify(obj);
|
|
475
|
+
const keys = Object.keys(obj).sort();
|
|
476
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** FNV-1a 32-bit hash,足够用于缓存标识。 */
|
|
480
|
+
function simpleHash(s: string): string {
|
|
481
|
+
let hash = 0x811c9dc5;
|
|
482
|
+
for (let i = 0; i < s.length; i++) {
|
|
483
|
+
hash ^= s.charCodeAt(i);
|
|
484
|
+
hash = Math.imul(hash, 0x01000193);
|
|
485
|
+
}
|
|
486
|
+
return `${(hash >>> 0).toString(16).padStart(8, "0")}-${s.length.toString(16).padStart(8, "0")}`;
|
|
487
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// src/compile/index.ts — Compile 中端入口
|
|
2
|
+
//
|
|
3
|
+
// Phase 9.4:v9 中端导出——compileAgentContext(profile, blueprint, domains) → AgentContext IR。
|
|
4
|
+
// Phase term-P1:compileContext → compileAgentContext(IR 改名同步)。
|
|
5
|
+
export { compileAgentContext, computeSourceHash, registerModuleRenderer } from "./agent-context.js";
|