kcode-pi 0.1.0 → 0.1.2

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.
@@ -0,0 +1,230 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, resolve } from "node:path";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ import { fileURLToPath } from "node:url";
6
+ import type { ProductProfile } from "../product/profile.ts";
7
+ import { readActiveRun } from "../harness/state.ts";
8
+ import { runArtifactPath } from "../harness/paths.ts";
9
+
10
+ const execFileAsync = promisify(execFile);
11
+
12
+ export type OfficialSkillKey = "ok-cosmic" | "ok-ksql";
13
+
14
+ export interface CommandSpec {
15
+ executable: string;
16
+ args: string[];
17
+ cwd: string;
18
+ display: string;
19
+ }
20
+
21
+ export interface CommandResult {
22
+ command: string;
23
+ exitCode: number;
24
+ stdout: string;
25
+ stderr: string;
26
+ }
27
+
28
+ export type OfficialEvidenceFile = "cosmic-config.txt" | "cosmic-metadata.json" | "cosmic-api.txt" | "ksql-lint.txt";
29
+
30
+ const SKILL_DIRS: Record<OfficialSkillKey, string> = {
31
+ "ok-cosmic": "ok-cosmic",
32
+ "ok-ksql": "ok-ksql",
33
+ };
34
+
35
+ const packageRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
36
+
37
+ export function isCosmicFamily(profile: ProductProfile): boolean {
38
+ return profile.platform === "cosmic";
39
+ }
40
+
41
+ export function officialSkillsSourceRoot(): string {
42
+ return officialSkillsSourceRoots()[0];
43
+ }
44
+
45
+ export function officialSkillsSourceRoots(): string[] {
46
+ return [
47
+ process.env.KCODE_KINGDEE_SKILLS_ROOT,
48
+ join(packageRoot, "vendor", "kingdee-skills"),
49
+ "E:\\projects\\kingdee\\skills",
50
+ ].filter((value): value is string => Boolean(value));
51
+ }
52
+
53
+ export function officialSkillsCacheRoot(cwd: string): string {
54
+ return join(cwd, ".pi", "kd", "official-skills");
55
+ }
56
+
57
+ export async function ensureOfficialSkillRoot(_cwd: string, skill: OfficialSkillKey): Promise<string> {
58
+ for (const sourceRoot of officialSkillsSourceRoots()) {
59
+ const expandedSource = join(sourceRoot, SKILL_DIRS[skill]);
60
+ if (existsSync(expandedSource)) return expandedSource;
61
+ }
62
+
63
+ throw new Error(`Official skill directory not found for ${skill}. Checked: ${officialSkillsSourceRoots().join(", ")}`);
64
+ }
65
+
66
+ export function resolveWorkspacePath(cwd: string, path: string): string {
67
+ return isAbsolute(path) ? path : resolve(cwd, path);
68
+ }
69
+
70
+ export function pythonExecutable(): string {
71
+ return process.env.KCODE_PYTHON ?? (process.platform === "win32" ? "python" : "python3");
72
+ }
73
+
74
+ export function buildPythonCommand(cwd: string, scriptPath: string, args: string[]): CommandSpec {
75
+ const executable = pythonExecutable();
76
+ const fullArgs = [scriptPath, ...args];
77
+ return {
78
+ executable,
79
+ args: fullArgs,
80
+ cwd,
81
+ display: formatCommand(executable, fullArgs),
82
+ };
83
+ }
84
+
85
+ export async function runCommand(command: CommandSpec, timeoutMs = 120_000): Promise<CommandResult> {
86
+ try {
87
+ const result = await execFileAsync(command.executable, command.args, {
88
+ cwd: command.cwd,
89
+ timeout: timeoutMs,
90
+ maxBuffer: 1024 * 1024 * 4,
91
+ });
92
+
93
+ return {
94
+ command: command.display,
95
+ exitCode: 0,
96
+ stdout: result.stdout ?? "",
97
+ stderr: result.stderr ?? "",
98
+ };
99
+ } catch (error) {
100
+ const err = error as {
101
+ code?: number | string;
102
+ stdout?: string;
103
+ stderr?: string;
104
+ message?: string;
105
+ };
106
+
107
+ return {
108
+ command: command.display,
109
+ exitCode: typeof err.code === "number" ? err.code : 1,
110
+ stdout: err.stdout ?? "",
111
+ stderr: err.stderr ?? err.message ?? "",
112
+ };
113
+ }
114
+ }
115
+
116
+ export async function cosmicConfigCommand(cwd: string, config?: string): Promise<CommandSpec> {
117
+ const root = await ensureOfficialSkillRoot(cwd, "ok-cosmic");
118
+ const script = join(root, "scripts", "cosmic-config-check.py");
119
+ const args = config ? ["--config", resolveWorkspacePath(cwd, config)] : [];
120
+ return buildPythonCommand(cwd, script, args);
121
+ }
122
+
123
+ export async function cosmicMetadataCommand(
124
+ cwd: string,
125
+ params: {
126
+ form: string;
127
+ config?: string;
128
+ fuzzy?: string;
129
+ typeFilter?: string;
130
+ sql?: boolean;
131
+ op?: boolean;
132
+ showDetail?: boolean;
133
+ },
134
+ ): Promise<CommandSpec> {
135
+ const root = await ensureOfficialSkillRoot(cwd, "ok-cosmic");
136
+ const script = join(root, "scripts", "cosmic-form-metadata.py");
137
+ const args = withConfig(["get", params.form], cwd, params.config);
138
+ if (params.sql) args.push("--sql");
139
+ if (params.op) args.push("--op");
140
+ if (params.showDetail) args.push("--show-detail");
141
+ if (params.fuzzy) args.push("--fuzzy", ...splitTerms(params.fuzzy));
142
+ if (params.typeFilter) args.push("--type", params.typeFilter);
143
+ return buildPythonCommand(cwd, script, args);
144
+ }
145
+
146
+ export async function cosmicApiCommand(
147
+ cwd: string,
148
+ params: {
149
+ mode: "search" | "search-method" | "detail";
150
+ query: string;
151
+ config?: string;
152
+ method?: string;
153
+ compact?: boolean;
154
+ },
155
+ ): Promise<CommandSpec> {
156
+ const root = await ensureOfficialSkillRoot(cwd, "ok-cosmic");
157
+ const script = join(root, "scripts", "cosmic-api-knowledge.py");
158
+ const args = withConfig([params.mode, params.query], cwd, params.config);
159
+ if (params.method) args.push("--method", params.method);
160
+ if (params.compact) args.push("--compact");
161
+ return buildPythonCommand(cwd, script, args);
162
+ }
163
+
164
+ export async function ksqlLintCommand(cwd: string, path: string): Promise<CommandSpec> {
165
+ const root = await ensureOfficialSkillRoot(cwd, "ok-ksql");
166
+ const script = join(root, "scripts", "ksql_lint.py");
167
+ return buildPythonCommand(cwd, script, [resolveWorkspacePath(cwd, path)]);
168
+ }
169
+
170
+ export function formatCommandResult(result: CommandResult): string {
171
+ return [
172
+ `Command: ${result.command}`,
173
+ `Exit: ${result.exitCode}`,
174
+ result.stdout.trim() ? `\nSTDOUT:\n${result.stdout.trim()}` : undefined,
175
+ result.stderr.trim() ? `\nSTDERR:\n${result.stderr.trim()}` : undefined,
176
+ ]
177
+ .filter(Boolean)
178
+ .join("\n");
179
+ }
180
+
181
+ export function writeOfficialEvidence(cwd: string, evidenceFile: OfficialEvidenceFile, result: CommandResult): string | undefined {
182
+ const run = readActiveRun(cwd);
183
+ if (!run) return undefined;
184
+
185
+ const path = runArtifactPath(cwd, run, join("evidence", evidenceFile));
186
+ mkdirSync(dirname(path), { recursive: true });
187
+
188
+ const content =
189
+ evidenceFile === "cosmic-metadata.json"
190
+ ? formatJsonEvidence(evidenceFile, result)
191
+ : `${formatCommandResult(result)}\nCaptured: ${new Date().toISOString()}\n`;
192
+ writeFileSync(path, content.endsWith("\n") ? content : `${content}\n`, "utf8");
193
+ return path;
194
+ }
195
+
196
+ function formatJsonEvidence(evidenceFile: OfficialEvidenceFile, result: CommandResult): string {
197
+ return `${JSON.stringify(
198
+ {
199
+ evidence: evidenceFile,
200
+ capturedAt: new Date().toISOString(),
201
+ command: result.command,
202
+ exitCode: result.exitCode,
203
+ stdout: result.stdout,
204
+ stderr: result.stderr,
205
+ },
206
+ null,
207
+ 2,
208
+ )}\n`;
209
+ }
210
+
211
+ function withConfig(args: string[], cwd: string, config?: string): string[] {
212
+ if (!config) return args;
213
+ return ["--config", resolveWorkspacePath(cwd, config), ...args];
214
+ }
215
+
216
+ function splitTerms(value: string): string[] {
217
+ return value
218
+ .split(/[,\s]+/)
219
+ .map((term) => term.trim())
220
+ .filter(Boolean);
221
+ }
222
+
223
+ function formatCommand(executable: string, args: string[]): string {
224
+ return [executable, ...args].map(quoteArg).join(" ");
225
+ }
226
+
227
+ function quoteArg(value: string): string {
228
+ if (!/[\s"'&|<>]/.test(value)) return value;
229
+ return `"${value.replace(/"/g, '\\"')}"`;
230
+ }
@@ -0,0 +1,115 @@
1
+ export type KdProduct = "unknown" | "flagship" | "cosmic" | "xinghan" | "cangqiong" | "enterprise";
2
+ export type KdPlatform = "unknown" | "cosmic" | "enterprise-csharp";
3
+ export type KdTechStack = "unknown" | "java-bos" | "java-cosmic" | "csharp-bos" | "ksql";
4
+ export type KdLanguage = "unknown" | "java" | "csharp" | "sql";
5
+ export type KnowledgeScope = "common" | "flagship" | "enterprise" | "cosmic" | "xinghan" | "cangqiong";
6
+
7
+ export interface ProductProfile {
8
+ product: KdProduct;
9
+ displayName: string;
10
+ platform: KdPlatform;
11
+ techStack: KdTechStack;
12
+ language: KdLanguage;
13
+ knowledgeScope: KnowledgeScope;
14
+ requiresMetadataVerification: boolean;
15
+ notes: string[];
16
+ }
17
+
18
+ const PROFILES: Record<KdProduct, ProductProfile> = {
19
+ unknown: {
20
+ product: "unknown",
21
+ displayName: "unknown",
22
+ platform: "unknown",
23
+ techStack: "unknown",
24
+ language: "unknown",
25
+ knowledgeScope: "common",
26
+ requiresMetadataVerification: true,
27
+ notes: ["Product is not selected; do not assume Java, C#, BOS, Cosmic, or KSQL rules."],
28
+ },
29
+ flagship: {
30
+ product: "flagship",
31
+ displayName: "Kingdee Xingkong Flagship",
32
+ platform: "cosmic",
33
+ techStack: "java-cosmic",
34
+ language: "java",
35
+ knowledgeScope: "flagship",
36
+ requiresMetadataVerification: true,
37
+ notes: ["Xingkong Flagship is Cosmic-family. Use Cosmic metadata, plugin lifecycle, SDK, and post-check constraints."],
38
+ },
39
+ cosmic: {
40
+ product: "cosmic",
41
+ displayName: "Kingdee Cosmic Platform",
42
+ platform: "cosmic",
43
+ techStack: "java-cosmic",
44
+ language: "java",
45
+ knowledgeScope: "cosmic",
46
+ requiresMetadataVerification: true,
47
+ notes: ["Cosmic is the shared platform base for Cangqiong, Xinghan, and Xingkong Flagship."],
48
+ },
49
+ xinghan: {
50
+ product: "xinghan",
51
+ displayName: "Kingdee Xinghan",
52
+ platform: "cosmic",
53
+ techStack: "java-cosmic",
54
+ language: "java",
55
+ knowledgeScope: "xinghan",
56
+ requiresMetadataVerification: true,
57
+ notes: ["Treat as Cosmic-family Java unless a Xinghan-specific rule overrides it."],
58
+ },
59
+ cangqiong: {
60
+ product: "cangqiong",
61
+ displayName: "Kingdee Cangqiong",
62
+ platform: "cosmic",
63
+ techStack: "java-cosmic",
64
+ language: "java",
65
+ knowledgeScope: "cangqiong",
66
+ requiresMetadataVerification: true,
67
+ notes: ["Use Cangqiong/Cosmic plugin, SDK, metadata, KSQL, and lifecycle rules."],
68
+ },
69
+ enterprise: {
70
+ product: "enterprise",
71
+ displayName: "Kingdee Enterprise",
72
+ platform: "enterprise-csharp",
73
+ techStack: "csharp-bos",
74
+ language: "csharp",
75
+ knowledgeScope: "enterprise",
76
+ requiresMetadataVerification: true,
77
+ notes: ["Use enterprise C# stack. Java plugin rules and Cosmic APIs are not applicable."],
78
+ },
79
+ };
80
+
81
+ const PRODUCT_ALIASES: Array<[RegExp, KdProduct]> = [
82
+ [/企业版|enterprise|csharp|c#|\.net/i, "enterprise"],
83
+ [/星瀚|xinghan/i, "xinghan"],
84
+ [/苍穹|cangqiong/i, "cangqiong"],
85
+ [/cosmic|云苍穹/i, "cosmic"],
86
+ [/星空旗舰版|星空旗舰|旗舰版|旗舰|flagship/i, "flagship"],
87
+ ];
88
+
89
+ export function profileForProduct(product: KdProduct | undefined): ProductProfile {
90
+ return PROFILES[product ?? "unknown"] ?? PROFILES.unknown;
91
+ }
92
+
93
+ export function resolveProductProfile(input: string | undefined): ProductProfile {
94
+ const text = input?.trim();
95
+ if (!text) return profileForProduct("unknown");
96
+
97
+ for (const [pattern, product] of PRODUCT_ALIASES) {
98
+ if (pattern.test(text)) return profileForProduct(product);
99
+ }
100
+
101
+ return profileForProduct("unknown");
102
+ }
103
+
104
+ export function normalizeProduct(value: string | undefined): KdProduct {
105
+ return resolveProductProfile(value).product;
106
+ }
107
+
108
+ export function isKnownProduct(product: KdProduct | undefined): boolean {
109
+ return Boolean(product && product !== "unknown");
110
+ }
111
+
112
+ export function formatProductProfile(profile: ProductProfile | undefined): string {
113
+ const resolved = profile ?? profileForProduct("unknown");
114
+ return `${resolved.product}/${resolved.platform}/${resolved.techStack}/${resolved.language}`;
115
+ }