@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,102 @@
1
+ // src/session.ts — Pt Session 状态封装(pt-quality #2 / P2.6)
2
+ //
3
+ // 替代 index.ts 10 个模块级 let 变量——收拢到单 state 对象,便于维护和单元测试。
4
+ //
5
+ // 设计:
6
+ // - Map<sessionId, SessionState>(v12.x)—— key 用 pi SessionManager.getSessionId()
7
+ // 支持 pi-web 多 session 并发:不同 session 各自一份 SessionState。
8
+ // TUI 模式下只有一个 session,Map 只有一个 entry,等同 module-level 单例行为。
9
+ // - 所有 setter 都通过 state 字段赋值(不解构)
10
+ // - clearSessionById 暴露给 session_shutdown / 测试
11
+ //
12
+ // v10.x:session-state 升级为 session-scoped daemon 级——
13
+ // - sessionId:crypto 生成的 8-hex 短 id(多并发 `pi` 进程的日志隔离键)
14
+ // - logger :per-session 单例 PtLogger(替代 per-transpile 实例化)
15
+ //
16
+ // v10.x(issue pt-context-persist-lost 修复,现 /pt-profile):loadedFrom 记录当前 activeProfile 的来源,
17
+ // 用于 /pt status 可观测性 + 排查"为什么没选到我预期的 profile"。
18
+ //
19
+ // v12.x(issue pt-session-singleton-pi-web-pollution 修复):
20
+ // - 移除 module-level `session` 单例(设计假设 per-process = per-session 在 pi-web 下不成立)
21
+ // - state 容器改为 Map<sessionId, SessionState>,key 用 pi SessionManager.getSessionId()
22
+ // - 调用方通过 `getSessionById(ctx.sessionManager.getSessionId())` 取 state
23
+ // - 对应 `src/agent/registry.ts` 把 Adapter 注册表也改成 WeakMap<ExtensionAPI, ...>,
24
+ // 保证 Adapter 实例 per-pi,避免"单例 PiAdapter.this.segment 被其他 session 覆盖"。
25
+ // - `cachedManualProgress` 从 module-level let 搬到 SessionState 字段。
26
+ /** 默认空 SessionState。 */
27
+ export function createSessionState() {
28
+ return {
29
+ activeProfile: null,
30
+ cachedSegment: null,
31
+ cachedBundles: null,
32
+ cachedAgentContext: null,
33
+ cachedBlueprint: null,
34
+ cachedDomains: [],
35
+ cachedProfile: null,
36
+ lastCwd: "",
37
+ lastBuiltPrompt: null,
38
+ lastCacheHit: false,
39
+ activeAdapter: null,
40
+ sessionId: "",
41
+ logger: null,
42
+ loadedFrom: null,
43
+ injectionState: "idle",
44
+ injectionError: null,
45
+ activeManual: null,
46
+ cachedManualProgress: null,
47
+ assetHealthIssues: null,
48
+ packValidation: null,
49
+ projectPackDegraded: false,
50
+ globalPackGuideShown: false,
51
+ };
52
+ }
53
+ /** v12.x:Map<sessionId, SessionState> 容器,key = pi SessionManager.getSessionId()。
54
+ * 对外不导出(避免外部直接 mutate 绕过懒加载)。 */
55
+ const sessionMap = new Map();
56
+ /** 重置编译产物字段(catch 路径用)。
57
+ * v13.x(issue pt-no-agent-context-reset-session-state 修复):
58
+ * - transpile/session_start/switchProfile 三处 catch 调用本函数
59
+ * - 避免 stale cachedAgentContext/cachedBlueprint/cachedDomains/cachedProfile/activeAdapter
60
+ * 误导 /pt flows / /pt manual 返回旧 Profile 的手册列表(掩盖真实失败)
61
+ * - 不清 sessionId/logger/lastCwd/activeProfile/loadedFrom/activeManual(生命周期不同:
62
+ * session_id 标识当前会话、logger 持续写日志、lastCwd 是项目根、activeProfile 是用户意图、
63
+ * loadedFrom 是来源、activeManual 是手动追踪)
64
+ */
65
+ export function resetSessionState(s) {
66
+ s.cachedSegment = null;
67
+ s.cachedBundles = null;
68
+ s.cachedAgentContext = null;
69
+ s.cachedBlueprint = null;
70
+ s.cachedDomains = [];
71
+ s.cachedProfile = null;
72
+ s.activeAdapter?.resetInjection?.();
73
+ s.activeAdapter = null;
74
+ s.lastCacheHit = false;
75
+ s.injectionState = "idle";
76
+ s.injectionError = null;
77
+ }
78
+ /** 取指定 sessionId 的 state(lazy create)。TUI 模式下只有一个 entry。 */
79
+ export function getSessionById(sessionId) {
80
+ let s = sessionMap.get(sessionId);
81
+ if (!s) {
82
+ s = createSessionState();
83
+ sessionMap.set(sessionId, s);
84
+ }
85
+ return s;
86
+ }
87
+ /** 列表所有 session state(debug / 诊断用,如 /pt sessions 列表)。 */
88
+ export function listAllSessions() {
89
+ return [...sessionMap.values()];
90
+ }
91
+ /** 清空指定 sessionId 的 state(session_shutdown 调用)。 */
92
+ export function clearSessionById(sessionId) {
93
+ sessionMap.delete(sessionId);
94
+ }
95
+ /** 清空所有 session state(仅测试 / 重启进程等需要)。 */
96
+ export function clearAllSessions() {
97
+ sessionMap.clear();
98
+ }
99
+ /** 测试 / 诊断:Map 的当前大小。 */
100
+ export function sessionCount() {
101
+ return sessionMap.size;
102
+ }
package/dist/slog.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** session-scoped logger 快捷调用(sessionId 空时静默——session_start 之前不可用)。
2
+ * level 用 PtLogger 的方法名(debug/info/warn/error),不是 PtLogLevel 的字面量。 */
3
+ export declare function slog(sessionId: string, level: "debug" | "info" | "warn" | "error", msg: string, ctx?: Record<string, unknown>): void;
package/dist/slog.js ADDED
@@ -0,0 +1,27 @@
1
+ // src/slog.ts — session-scoped logger 快捷调用
2
+ //
3
+ // 设计动机:
4
+ // - 入口代码(src/index.ts)和 session 交互层(src/manual-session.ts)共享同一 slog 快捷
5
+ // - session.logger 为 null 时静默(session_start 之前不可用)
6
+ // - 不依赖 file IO(持久化走 src/log.ts 的 PtLogger 类),仅作调用快捷
7
+ //
8
+ // v11.x:从 src/index.ts 抽出(与 manual-session 同次提交),统一两个调用点的快捷。
9
+ //
10
+ // v12.x(issue pt-session-singleton-pi-web-pollution 修复):
11
+ // - 移除 module-level `session` 单例访问
12
+ // - `slog` 改接受 `sessionId` 参数,按 sessionId 取 per-session logger
13
+ // - sessionId 为空时静默(与原 "session.logger 为 null 时静默" 行为一致)
14
+ //
15
+ // 类型说明:PtLogLevel 用 "warning"(与 ctx.ui.notify 兼容),PtLogger 方法用 "warn"。
16
+ // 此处使用 PtLogger 的方法名(debug/info/warn/error)作为调用 key,与原 slog 行为一致。
17
+ import { getSessionById } from "./session.js";
18
+ /** session-scoped logger 快捷调用(sessionId 空时静默——session_start 之前不可用)。
19
+ * level 用 PtLogger 的方法名(debug/info/warn/error),不是 PtLogLevel 的字面量。 */
20
+ export function slog(sessionId, level, msg, ctx) {
21
+ if (!sessionId)
22
+ return;
23
+ const logger = getSessionById(sessionId).logger;
24
+ if (!logger)
25
+ return;
26
+ logger[level](msg, ctx);
27
+ }
@@ -0,0 +1,34 @@
1
+ import type { AgentContext, Blueprint, Domain, Profile, SchemaBundle, SourceAdapterContext } from "./schema.js";
2
+ export interface TranspileResult {
3
+ /** 注入 session 的字符串段(聚合所有 inject=session 的聚合组) */
4
+ segment: string;
5
+ /** 各 adapter 返回的 SchemaBundle(保留给 input handler 找 FlowTemplate / listManuals 用) */
6
+ bundles: SchemaBundle[];
7
+ /** 缓存命中信息 */
8
+ cacheHit: boolean;
9
+ /** v9 新增(给 AgentAdapter 用)。Phase term-P1:context → agentContext(IR 改名同步)。 */
10
+ agentContext: AgentContext;
11
+ blueprint: Blueprint;
12
+ domains: Domain[];
13
+ /** 当前激活的 Profile 名 */
14
+ activeProfile: string;
15
+ /** 当前激活的 Profile(listManuals 范围过滤用) */
16
+ profile: Profile;
17
+ /** v15.x PR6(fix pt-active-profile-fallback-mismatch):bundle.activeProfile 由来
18
+ * caller 层依据 origin 决定是否同步 s.activeProfile / loadedFrom / notify。 */
19
+ activeProfileOrigin: "exact" | "fallback";
20
+ /** origin="fallback" 时记录用户原始请求名(caller 用作 notify 文案 + log) */
21
+ originalProfileName?: string;
22
+ }
23
+ /** 三段式转译:parse → compile → cache → render。 */
24
+ export declare function loadAndTranspile(cwd: string, profileName: string, adapterCtx?: SourceAdapterContext): Promise<TranspileResult>;
25
+ /** 删除孤儿 cache 文件(Profile 已不存在的 cache)。
26
+ * v13.x(issue pt-no-agent-context-prune-orphan-caches):transpile 末尾自动清理,
27
+ * 避免 Profile 删改后旧 cache 文件残留误导排查。
28
+ *
29
+ * @param cwd 项目根目录
30
+ * @param cacheDir 缓存目录(从 Blueprint.compilation.cacheDir 读,默认 `.pt/cache/agent-contexts/`)
31
+ * @param validProfileNames 当前 bundle.profiles 合并后的全集(项目覆盖 builtin)
32
+ * @returns 被 unlink 的 cache 名列表(用于 trace)
33
+ */
34
+ export declare function pruneOrphanCaches(cwd: string, cacheDir: string, validProfileNames: Set<string>): Promise<string[]>;
@@ -0,0 +1,220 @@
1
+ // src/transpile.ts — v9 三段式链路:parse → compile → cache → render
2
+ //
3
+ // Phase 9.7:v9 链路——Profile 用户面 + Blueprint 结构层 + AgentAdapter 注入。
4
+ // parse(profile md) → IR (SchemaBundle with profiles)
5
+ // ↓
6
+ // findProfile → findBlueprint → compileAgentContext(profile, blueprint, domains) → AgentContext IR
7
+ // ↓
8
+ // cache.load? 命中 → 用缓存 : cache.save(AgentContext) → 重编译
9
+ // ↓
10
+ // render.systemPrompt(AgentContext, Blueprint) → 给 AgentAdapter 注入 before_agent_start
11
+ //
12
+ // Phase 11.x / P1:单源线性(YAGNI 多源合并)——取第一个成功的 adapter。
13
+ // 旧版多 bundle 循环 + 5 个 lastXxx 累积已删(P1.1)。bundles 仍返 [bundle]
14
+ // 保留 SchemaBundle[] 形态(index.ts:786 r.bundles[0] 依赖)。无成功 bundle
15
+ // 抛错(P1.2)由 index.ts:286 catch 兜底。
16
+ //
17
+ // Phase term-P1:Context IR → AgentContext 改名同步——
18
+ // - compileContext → compileAgentContext
19
+ // - saveContext/loadContext → saveAgentContext/loadAgentContext
20
+ // - TranspileResult.context → TranspileResult.agentContext(字段改名)
21
+ import { readdir, unlink } from "node:fs/promises";
22
+ import { join } from "node:path";
23
+ import { CACHE_DIR } from "./constants.js";
24
+ import { errMsg, reportError, reportWarn } from "./diagnostics.js";
25
+ import { mdAdapter } from "./parse/index.js";
26
+ import { compileAgentContext } from "./compile/agent-context.js";
27
+ import { saveAgentContext, loadAgentContext } from "./render/cache.js";
28
+ import { renderSessionInject } from "./render/session-inject.js";
29
+ import { findProfile } from "./schema.js";
30
+ import { parseRef, resolveBlueprint } from "./parse/ref-resolver.js";
31
+ import { expandProfile, UseTargetNotFound, UseChainCycle, BlueprintGroupOutOfScope, } from "./compile/resolve-use.js";
32
+ /** ============== Source Adapter 注册表(MVP 只有 MD) ==============
33
+ * P1.1:保留数组形式作为扩展点(未来加 yamlAdapter / dbAdapter),
34
+ * 但 loadAndTranspile 改为"取第一个成功 adapter"——多源合并未来设计(YAGNI)。 */
35
+ const sourceAdapters = [
36
+ mdAdapter,
37
+ // 未来:yamlAdapter, dbAdapter, ...
38
+ ];
39
+ /** 三段式转译:parse → compile → cache → render。 */
40
+ export async function loadAndTranspile(cwd, profileName, adapterCtx) {
41
+ adapterCtx?.log?.debug("transpile:start", { profileName, adapterCount: sourceAdapters.length });
42
+ // 1. parse:依次调所有 adapter,取第一个成功的 bundle
43
+ let bundle = null;
44
+ for (const a of sourceAdapters) {
45
+ try {
46
+ const b = await a.load(cwd, profileName, adapterCtx);
47
+ if (b) {
48
+ bundle = b;
49
+ break;
50
+ }
51
+ }
52
+ catch (e) {
53
+ reportError(adapterCtx, `adapter ${a.name} failed: ${errMsg(e)}`, { adapter: a.name });
54
+ }
55
+ }
56
+ if (!bundle) {
57
+ throw new Error(`transpile: no adapter succeeded for profile "${profileName}"`);
58
+ }
59
+ adapterCtx?.log?.info("transpile:parse done", {
60
+ profileName,
61
+ profileCount: bundle.profiles.length,
62
+ blueprintCount: bundle.blueprints.length,
63
+ domainCount: bundle.domains.length,
64
+ });
65
+ // 2. compile:profile → blueprint → context
66
+ const rawProfile = findProfile(bundle.profiles, bundle.activeProfile);
67
+ if (!rawProfile) {
68
+ throw new Error(`transpile: profile "${bundle.activeProfile}" not found in bundle (available: ${bundle.profiles.map((p) => p.name).join(", ") || "<none>"})`);
69
+ }
70
+ // v15.x PR5(§5.3.2 + §8.2):use 单继承展开
71
+ // - 在 findProfile 之后、findBlueprint 之前——展开后 blueprint 可能被 self 覆盖
72
+ // - 展开后 profile 进 compileAgentContext + computeSourceHash(§8.2 use 链自然进 hash)
73
+ // - profileByQualifiedName = workingSet.profiles 视图转换(key 已是 "pack/name")
74
+ // - blueprintByQualifiedName 直接复用 workingSet.blueprints({pack, asset} 视图)
75
+ // v15.x §4.4.2:profileByQualifiedName 从 workingSet.profiles.identity 迭代(key = pack.name/asset.name)
76
+ const profileByQualifiedName = new Map();
77
+ for (const [k, v] of bundle.workingSet.profiles.identity) {
78
+ profileByQualifiedName.set(k, v.asset);
79
+ }
80
+ // v15.x §4.4.2:blueprintByQualifiedName 从 workingSet.blueprints.identity 取(与原单索引语义一致)
81
+ const blueprintByQualifiedName = bundle.workingSet.blueprints.identity;
82
+ let profile;
83
+ try {
84
+ profile = expandProfile(rawProfile, profileByQualifiedName, blueprintByQualifiedName, bundle.packs, new Set(), adapterCtx);
85
+ }
86
+ catch (e) {
87
+ // use 链错误:reportWarn + rethrow——让上层报"transpile: failed"并阻断激活
88
+ if (e instanceof UseTargetNotFound ||
89
+ e instanceof UseChainCycle ||
90
+ e instanceof BlueprintGroupOutOfScope) {
91
+ reportWarn(adapterCtx, e.message, {
92
+ profileName: rawProfile.name,
93
+ errorType: e.name,
94
+ });
95
+ }
96
+ throw e;
97
+ }
98
+ const blueprintEntry = resolveBlueprint(profile, bundle.workingSet.blueprints, bundle.packs.map((p) => p.name));
99
+ const blueprint = blueprintEntry?.asset;
100
+ if (!blueprint) {
101
+ const resolved = (() => {
102
+ const { pack, name } = parseRef(profile.blueprint, profile.sourcePack ?? "");
103
+ return `${pack}/${name}`;
104
+ })();
105
+ reportWarn(adapterCtx, `Profile "${profile.name}" 引用未知 Blueprint "${profile.blueprint}"`, {
106
+ profileName: profile.name,
107
+ referencedBlueprint: profile.blueprint,
108
+ resolvedBlueprint: `@${resolved}`,
109
+ // v15.x §4.4.2:availableBlueprints 从 identity 索引读(manifest.name 视图,与原单索引语义一致)
110
+ availableBlueprints: [...bundle.workingSet.blueprints.identity.keys()],
111
+ });
112
+ throw new Error(`transpile: blueprint "${profile.blueprint}" not found for profile "${profile.name}"`);
113
+ }
114
+ // v15.x PR2(§8.1 + §8.3)+ PR3(§4.4.3 #2):compileAgentContext 加 packs + profilePack + workingSet
115
+ const ctx = compileAgentContext(profile, blueprint, bundle.domains, bundle.packs, bundle.activeProfilePack, bundle.workingSet);
116
+ adapterCtx?.log?.debug("transpile:compile done", {
117
+ profileName: profile.name,
118
+ sourceHashPrefix: ctx.sourceHash.slice(0, 8),
119
+ moduleCount: Object.keys(ctx.modules).length,
120
+ packName: ctx.packName,
121
+ });
122
+ // 3. cache:load 命中 → 用缓存(跳过写入),未命中 → save
123
+ // Phase term-P4.2:cacheDir 改用 constants.CACHE_DIR 常量,签名删 compilation 参数。
124
+ // v15.x PR2(§8.3):loadAgentContext 加 packName 参数,cache 文件名 <pack>__<profile>。
125
+ const cached = await loadAgentContext(cwd, ctx.packName, ctx.name, ctx.sourceHash);
126
+ let used;
127
+ let cacheHit = false;
128
+ if (cached) {
129
+ cacheHit = true;
130
+ adapterCtx?.log?.info("transpile:cache hit", { agentContextName: ctx.name });
131
+ used = cached;
132
+ }
133
+ else {
134
+ adapterCtx?.log?.info("transpile:cache miss → save", { agentContextName: ctx.name });
135
+ await saveAgentContext(cwd, ctx);
136
+ used = ctx;
137
+ }
138
+ // 4. render:剥 asset 分隔注释
139
+ // Phase term-P4.3:renderSystemPrompt → renderSessionInject(inject 语义值 session/turn)。
140
+ const segment = renderSessionInject(used, blueprint)
141
+ .replace(/<!-- =====[^\n]*-->\n?/g, "")
142
+ .trim();
143
+ // issue pt-status-no-injection-state:segmentLen === 0 几乎总是配置错误
144
+ // (profile 缺 ### Modules / blueprint groups 名错配 / domain 段名不匹配等)。
145
+ // cache hit 路径下特别危险——空 segment 被缓存复用,footer 永远 idle。
146
+ // 主动告警让用户首次切换就发现问题,而不是依赖 `injected: true` 误导
147
+ if (segment.length === 0) {
148
+ reportWarn(adapterCtx, `Profile「${profile.name}」编译产出空 segment(注入不会生效,footer 会一直 idle)`, {
149
+ profileName: profile.name,
150
+ cacheHit,
151
+ hint: "检查 Profile 的 H2 段下 `### Modules` 配置、Blueprint groups 名匹配、Domain H2 段名",
152
+ });
153
+ }
154
+ // 5. prune orphan caches(v13.x issue pt-no-agent-context-prune-orphan-caches 修复)
155
+ // Profile 删除/改名后,旧 cache 文件残留——transpile 末尾自动 unlink
156
+ // validProfileNames 来自当前 bundle(项目 + builtin 合并后的全集)
157
+ // Phase term-P4.2:Blueprint.competition 已移除,cacheDir 用 CACHE_DIR 常量
158
+ const pruned = await pruneOrphanCaches(cwd, CACHE_DIR, new Set(bundle.profiles.map((p) => p.name)));
159
+ if (pruned.length > 0) {
160
+ adapterCtx?.log?.info("transpile:prune orphan caches", { pruned });
161
+ }
162
+ adapterCtx?.log?.info("transpile:done", {
163
+ profileName: profile.name,
164
+ segmentLen: segment.length,
165
+ cacheHit,
166
+ prunedCount: pruned.length,
167
+ });
168
+ return {
169
+ segment,
170
+ bundles: [bundle],
171
+ cacheHit,
172
+ agentContext: used,
173
+ blueprint,
174
+ domains: bundle.domains,
175
+ activeProfile: profile.name,
176
+ profile,
177
+ // v15.x PR6(fix pt-active-profile-fallback-mismatch):透传 origin 给 caller
178
+ activeProfileOrigin: bundle.activeProfileOrigin,
179
+ originalProfileName: bundle.originalProfileName,
180
+ };
181
+ }
182
+ /** 删除孤儿 cache 文件(Profile 已不存在的 cache)。
183
+ * v13.x(issue pt-no-agent-context-prune-orphan-caches):transpile 末尾自动清理,
184
+ * 避免 Profile 删改后旧 cache 文件残留误导排查。
185
+ *
186
+ * @param cwd 项目根目录
187
+ * @param cacheDir 缓存目录(从 Blueprint.compilation.cacheDir 读,默认 `.pt/cache/agent-contexts/`)
188
+ * @param validProfileNames 当前 bundle.profiles 合并后的全集(项目覆盖 builtin)
189
+ * @returns 被 unlink 的 cache 名列表(用于 trace)
190
+ */
191
+ export async function pruneOrphanCaches(cwd, cacheDir, validProfileNames) {
192
+ const dir = join(cwd, cacheDir);
193
+ let files;
194
+ try {
195
+ files = await readdir(dir);
196
+ }
197
+ catch {
198
+ return []; // 目录不存在(首次加载)返空,不抛错
199
+ }
200
+ const pruned = [];
201
+ for (const f of files) {
202
+ if (!f.endsWith(".agent-context.md"))
203
+ continue;
204
+ // v15.x PR2(§8.3):cache 文件名 <pack>__<profile>.agent-context.md——split "__" 拿 profile name
205
+ // PR1 阶段:<profile>.agent-context.md——slice 去后缀。二者需向后兼容。
206
+ const stem = f.slice(0, -".agent-context.md".length);
207
+ const sepIdx = stem.lastIndexOf("__");
208
+ const name = sepIdx >= 0 ? stem.slice(sepIdx + 2) : stem;
209
+ if (validProfileNames.has(name))
210
+ continue;
211
+ try {
212
+ await unlink(join(dir, f));
213
+ pruned.push(name);
214
+ }
215
+ catch {
216
+ // unlink 失败(权限/不存在)跳过——不影响主流程
217
+ }
218
+ }
219
+ return pruned;
220
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProbeOutcome } from "../schema.js";
2
+ export declare function fileHash(cwd: string, params: Record<string, string>): Promise<ProbeOutcome>;
@@ -0,0 +1,30 @@
1
+ // src/verify/file-hash.ts — 验证文件 sha256 hash 匹配
2
+ import { createHash } from "node:crypto";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ export async function fileHash(cwd, params) {
6
+ const path = params.path;
7
+ if (!path)
8
+ return { outcome: "INCONCLUSIVE", message: "缺少参数: path" };
9
+ const expected = params.expected;
10
+ if (!expected)
11
+ return { outcome: "INCONCLUSIVE", message: "缺少参数: expected (sha256)" };
12
+ try {
13
+ const content = readFileSync(join(cwd, path));
14
+ const hash = createHash("sha256").update(content).digest("hex");
15
+ const match = hash === expected;
16
+ return {
17
+ outcome: match ? "COMPLETED" : "DEVIATED",
18
+ message: match
19
+ ? `hash 匹配: ${path}`
20
+ : `hash 不匹配: ${path}(期望 ${expected.slice(0, 8)}...,实际 ${hash.slice(0, 8)}...)`,
21
+ actual: hash,
22
+ };
23
+ }
24
+ catch (e) {
25
+ return {
26
+ outcome: "INCONCLUSIVE",
27
+ message: `无法读取 ${path}: ${e instanceof Error ? e.message : String(e)}`,
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProbeOutcome } from "../schema.js";
2
+ export declare function fsContentMatch(cwd: string, params: Record<string, string>): Promise<ProbeOutcome>;
@@ -0,0 +1,26 @@
1
+ // src/verify/fs-content-match.ts — 验证文件包含某文本
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ export async function fsContentMatch(cwd, params) {
5
+ const path = params.path;
6
+ if (!path)
7
+ return { outcome: "INCONCLUSIVE", message: "缺少参数: path" };
8
+ const pattern = params.pattern;
9
+ if (!pattern)
10
+ return { outcome: "INCONCLUSIVE", message: "缺少参数: pattern" };
11
+ try {
12
+ const content = readFileSync(join(cwd, path), "utf8");
13
+ const found = content.includes(pattern);
14
+ return {
15
+ outcome: found ? "COMPLETED" : "DEVIATED",
16
+ message: found ? `文件 ${path} 包含 "${pattern}"` : `文件 ${path} 不包含 "${pattern}"`,
17
+ actual: found ? "found" : "not-found",
18
+ };
19
+ }
20
+ catch (e) {
21
+ return {
22
+ outcome: "INCONCLUSIVE",
23
+ message: `无法读取 ${path}: ${e instanceof Error ? e.message : String(e)}`,
24
+ };
25
+ }
26
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProbeOutcome } from "../schema.js";
2
+ export declare function fsExists(cwd: string, params: Record<string, string>): Promise<ProbeOutcome>;
@@ -0,0 +1,13 @@
1
+ // src/verify/fs-exists.ts — 验证文件存在
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ export async function fsExists(cwd, params) {
5
+ const path = params.path;
6
+ if (!path)
7
+ return { outcome: "INCONCLUSIVE", message: "缺少参数: path" };
8
+ const exists = existsSync(join(cwd, path));
9
+ return {
10
+ outcome: exists ? "COMPLETED" : "DEVIATED",
11
+ message: exists ? `存在: ${path}` : `不存在: ${path}`,
12
+ };
13
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProbeOutcome } from "../schema.js";
2
+ export declare function fsNotExists(cwd: string, params: Record<string, string>): Promise<ProbeOutcome>;
@@ -0,0 +1,13 @@
1
+ // src/verify/fs-not-exists.ts — 验证文件不存在
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ export async function fsNotExists(cwd, params) {
5
+ const path = params.path;
6
+ if (!path)
7
+ return { outcome: "INCONCLUSIVE", message: "缺少参数: path" };
8
+ const exists = existsSync(join(cwd, path));
9
+ return {
10
+ outcome: !exists ? "COMPLETED" : "DEVIATED",
11
+ message: !exists ? `已删除: ${path}` : `仍存在: ${path}`,
12
+ };
13
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProbeOutcome } from "../schema.js";
2
+ export declare function gitStatusClean(cwd: string, _params: Record<string, string>): Promise<ProbeOutcome>;
@@ -0,0 +1,19 @@
1
+ // src/verify/git-status-clean.ts — 验证 git 工作树干净
2
+ import { execSync } from "node:child_process";
3
+ export async function gitStatusClean(cwd, _params) {
4
+ try {
5
+ const out = execSync("git status --porcelain", { cwd, encoding: "utf8", timeout: 10000 });
6
+ const clean = out.trim() === "";
7
+ return {
8
+ outcome: clean ? "COMPLETED" : "DEVIATED",
9
+ message: clean ? "git 工作树干净" : `git 工作树有变更:\n${out.trim()}`,
10
+ actual: clean ? "clean" : "dirty",
11
+ };
12
+ }
13
+ catch (e) {
14
+ return {
15
+ outcome: "INCONCLUSIVE",
16
+ message: `git 执行失败: ${e instanceof Error ? e.message : String(e)}`,
17
+ };
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ import type { ProbeOutcome } from "../schema.js";
2
+ /** verify 函数签名:纯函数,接收 cwd + 参数,返回 ProbeOutcome。 */
3
+ export type VerifyFunction = (cwd: string, params: Record<string, string>) => Promise<ProbeOutcome>;
4
+ /** 列出所有已注册的 probe 名(pt_verify tool 的错误提示用)。 */
5
+ export declare function listProbes(): string[];
6
+ /** 按名执行 verify。未注册的 probe 名返回 INCONCLUSIVE。 */
7
+ export declare function runVerify(cwd: string, name: string, params: Record<string, string>): Promise<ProbeOutcome>;
@@ -0,0 +1,46 @@
1
+ // src/verify/index.ts — verify 注册表 + runVerify 入口
2
+ //
3
+ // P1:observe 字段的实现库。纯函数 (cwd, params) => ProbeOutcome,不依赖 OXN 的 kernel/verdict 链。
4
+ // 加新 probe = 注册表加一行 + 实现文件一个。
5
+ import { fsContentMatch } from "./fs-content-match.js";
6
+ import { fsExists } from "./fs-exists.js";
7
+ import { fsNotExists } from "./fs-not-exists.js";
8
+ import { lintCheck } from "./lint-check.js";
9
+ import { tsCompiles } from "./ts-compiles.js";
10
+ import { testPass } from "./test-pass.js";
11
+ import { gitStatusClean } from "./git-status-clean.js";
12
+ import { fileHash } from "./file-hash.js";
13
+ /** 注册表:probe 名 → verify 函数。 */
14
+ const registry = {
15
+ "fs-content-match": fsContentMatch,
16
+ "fs-exists": fsExists,
17
+ "fs-not-exists": fsNotExists,
18
+ "lint-check": lintCheck,
19
+ "ts-compiles": tsCompiles,
20
+ "test-pass": testPass,
21
+ "git-status-clean": gitStatusClean,
22
+ "file-hash": fileHash,
23
+ };
24
+ /** 列出所有已注册的 probe 名(pt_verify tool 的错误提示用)。 */
25
+ export function listProbes() {
26
+ return Object.keys(registry);
27
+ }
28
+ /** 按名执行 verify。未注册的 probe 名返回 INCONCLUSIVE。 */
29
+ export async function runVerify(cwd, name, params) {
30
+ const fn = registry[name];
31
+ if (!fn) {
32
+ return {
33
+ outcome: "INCONCLUSIVE",
34
+ message: `未知 probe: ${name}(可用: ${listProbes().join(", ")})`,
35
+ };
36
+ }
37
+ try {
38
+ return await fn(cwd, params);
39
+ }
40
+ catch (e) {
41
+ return {
42
+ outcome: "INCONCLUSIVE",
43
+ message: `probe ${name} 执行异常: ${e instanceof Error ? e.message : String(e)}`,
44
+ };
45
+ }
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProbeOutcome } from "../schema.js";
2
+ export declare function lintCheck(cwd: string, params: Record<string, string>): Promise<ProbeOutcome>;
@@ -0,0 +1,17 @@
1
+ // src/verify/lint-check.ts — 验证 lint 通过
2
+ import { execSync } from "node:child_process";
3
+ import { readStderr } from "./read-stderr.js";
4
+ export async function lintCheck(cwd, params) {
5
+ const cmd = params.cmd ?? "npx biome check";
6
+ try {
7
+ execSync(cmd, { cwd, stdio: "pipe", timeout: 30000 });
8
+ return { outcome: "COMPLETED", message: `lint 通过: ${cmd}` };
9
+ }
10
+ catch (e) {
11
+ return {
12
+ outcome: "DEVIATED",
13
+ message: `lint 失败: ${cmd}`,
14
+ actual: readStderr(e).slice(0, 500),
15
+ };
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ /** 从 execSync 抛出的异常中提取 stderr 字符串。
2
+ * - e 非 Error 或无 stderr 字段 → 返 ""
3
+ * - stderr 为 Uint8Array → Buffer 转字符串
4
+ * - 其他类型 → String() 兜底 */
5
+ export declare function readStderr(e: unknown): string;
@@ -0,0 +1,20 @@
1
+ // src/verify/read-stderr.ts — execSync 异常 stderr 提取公共辅助(P3.5 抽出)
2
+ //
3
+ // execSync 抛异常时 stderr 是 Uint8Array,需要 Buffer.from 转换。
4
+ // lint-check.ts + test-pass.ts 都需要此逻辑——抽到此处避免重复。
5
+ /** 从 execSync 抛出的异常中提取 stderr 字符串。
6
+ * - e 非 Error 或无 stderr 字段 → 返 ""
7
+ * - stderr 为 Uint8Array → Buffer 转字符串
8
+ * - 其他类型 → String() 兜底 */
9
+ export function readStderr(e) {
10
+ if (e instanceof Error && "stderr" in e) {
11
+ const stderr = e.stderr;
12
+ if (stderr instanceof Uint8Array) {
13
+ return Buffer.from(stderr).toString();
14
+ }
15
+ if (typeof stderr === "string") {
16
+ return stderr;
17
+ }
18
+ }
19
+ return "";
20
+ }
@@ -0,0 +1,13 @@
1
+ import type { Blueprint, Domain, Profile } from "../schema.js";
2
+ export interface RefCheckResult {
3
+ ok: boolean;
4
+ errors: string[];
5
+ warnings: string[];
6
+ }
7
+ /** 校验单个 Profile 的引用完整性。
8
+ * 传入该 Profile 引用的 Blueprint(若存在)+ 全集 Domains。 */
9
+ export declare function checkProfileRefs(profile: Profile, blueprints: Blueprint[], domains: Domain[]): RefCheckResult;
10
+ /** 校验所有 Profile 的引用完整性(批量入口)。 */
11
+ export declare function checkAllRefs(profiles: Profile[], blueprints: Blueprint[], domains: Domain[]): RefCheckResult;
12
+ /** 把 RefCheckResult 格式化为人类可读文本(pt_check_refs tool 输出用)。 */
13
+ export declare function formatRefCheckResult(r: RefCheckResult): string;