kcode-pi 0.1.24 → 0.1.30

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.
@@ -100,7 +100,7 @@ export function addQuestion(
100
100
  run: ActiveRun,
101
101
  input: { question: string; reason?: string; choices?: string[]; blocking?: boolean },
102
102
  ): KdQuestion {
103
- const existing = run.questions ?? [];
103
+ const existing = Array.isArray(run.questions) ? run.questions : [];
104
104
  const question: KdQuestion = {
105
105
  id: createQuestionId(existing.length + 1),
106
106
  phase: run.phase,
@@ -118,7 +118,7 @@ export function addQuestion(
118
118
  }
119
119
 
120
120
  export function answerQuestion(cwd: string, run: ActiveRun, id: string, answer: string): KdQuestion | undefined {
121
- const question = (run.questions ?? []).find((item) => item.id === id);
121
+ const question = (Array.isArray(run.questions) ? run.questions : []).find((item) => item.id === id);
122
122
  if (!question) return undefined;
123
123
  question.status = "answered";
124
124
  question.answer = answer.trim();
@@ -129,7 +129,7 @@ export function answerQuestion(cwd: string, run: ActiveRun, id: string, answer:
129
129
  }
130
130
 
131
131
  export function openBlockingQuestions(run: ActiveRun): KdQuestion[] {
132
- return (run.questions ?? []).filter((question) => question.status === "open" && question.blocking);
132
+ return (Array.isArray(run.questions) ? run.questions : []).filter((question) => question.status === "open" && question.blocking);
133
133
  }
134
134
 
135
135
  export function updateProductProfile(cwd: string, run: ActiveRun, productInput: string, version?: string): ActiveRun {
@@ -189,6 +189,21 @@ export function advanceRun(cwd: string, run: ActiveRun, requestedPhase?: KdPhase
189
189
  return { run, message: `已推进 Kingdee run 到阶段:${target}` };
190
190
  }
191
191
 
192
+ export function advanceRunIfReady(cwd: string, run: ActiveRun): { run: ActiveRun; advanced: boolean; message: string } {
193
+ const current = readRun(cwd, run.id) ?? run;
194
+ const target = nextPhase(current.phase);
195
+ if (!target) {
196
+ return { run: current, advanced: false, message: `Run 已处于最终阶段:${current.phase}` };
197
+ }
198
+
199
+ const result = advanceRun(cwd, current, target);
200
+ return {
201
+ run: result.run,
202
+ advanced: result.run.phase === target,
203
+ message: result.message,
204
+ };
205
+ }
206
+
192
207
  export function refreshGate(cwd: string, run: ActiveRun): ActiveRun {
193
208
  run.gate = inspectGate(cwd, run);
194
209
  writeActiveRun(cwd, run);
@@ -202,19 +217,90 @@ function writeRunState(cwd: string, run: ActiveRun): void {
202
217
  }
203
218
 
204
219
  function hydrateRun(parsed: ActiveRun): ActiveRun | undefined {
205
- if (!parsed.id || !isKdPhase(parsed.phase)) return undefined;
206
- parsed.artifacts ??= {};
207
- parsed.questions ??= [];
208
- parsed.status ??= "active";
209
- parsed.createdAt ??= parsed.updatedAt;
210
- parsed.updatedAt ??= parsed.createdAt;
220
+ if (!parsed || typeof parsed !== "object") return undefined;
221
+ if (typeof parsed.id !== "string" || !parsed.id.trim()) return undefined;
222
+ if (typeof parsed.phase !== "string" || !isKdPhase(parsed.phase)) return undefined;
223
+ parsed.id = parsed.id.trim();
224
+ parsed.artifacts = isPlainObject(parsed.artifacts) ? sanitizeArtifacts(parsed.artifacts) : {};
225
+ parsed.questions = Array.isArray(parsed.questions) ? parsed.questions.map(sanitizeQuestion).filter((question): question is KdQuestion => Boolean(question)) : [];
226
+ parsed.status = parsed.status === "paused" || parsed.status === "done" ? parsed.status : "active";
227
+ parsed.goal = typeof parsed.goal === "string" ? parsed.goal : undefined;
228
+ parsed.version = typeof parsed.version === "string" ? parsed.version : undefined;
229
+ parsed.createdAt = typeof parsed.createdAt === "string" ? parsed.createdAt : typeof parsed.updatedAt === "string" ? parsed.updatedAt : undefined;
230
+ parsed.updatedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : parsed.createdAt;
211
231
  const legacyEdition = (parsed as ActiveRun & { edition?: string }).edition;
212
- parsed.profile = parsed.profile ?? profileForProduct(parsed.product ?? resolveProductProfile(legacyEdition).product);
232
+ const product = typeof parsed.profile?.product === "string" ? parsed.profile.product : typeof parsed.product === "string" ? parsed.product : resolveProductProfile(typeof legacyEdition === "string" ? legacyEdition : undefined).product;
233
+ parsed.profile = profileForProduct(product);
213
234
  parsed.product = parsed.profile.product;
214
- parsed.gate ??= { passed: false, checkedAt: new Date().toISOString() };
235
+ parsed.riskAssessment = sanitizeRiskAssessment(parsed.riskAssessment);
236
+ parsed.repair = sanitizeRepairState(parsed.repair);
237
+ parsed.gate = sanitizeGate(parsed.gate);
215
238
  return parsed;
216
239
  }
217
240
 
241
+ function sanitizeArtifacts(value: Record<string, unknown>): Record<string, string> {
242
+ return Object.fromEntries(Object.entries(value).filter((entry): entry is [string, string] => typeof entry[0] === "string" && typeof entry[1] === "string"));
243
+ }
244
+
245
+ function sanitizeQuestion(value: unknown): KdQuestion | undefined {
246
+ if (!isPlainObject(value)) return undefined;
247
+ const phase = typeof value.phase === "string" && isKdPhase(value.phase) ? value.phase : undefined;
248
+ const question = typeof value.question === "string" ? value.question.trim() : "";
249
+ if (!phase || !question) return undefined;
250
+ const id = typeof value.id === "string" && value.id.trim() ? value.id.trim() : createQuestionId(1);
251
+ return {
252
+ id,
253
+ phase,
254
+ question,
255
+ reason: typeof value.reason === "string" && value.reason.trim() ? value.reason.trim() : undefined,
256
+ choices: Array.isArray(value.choices) ? value.choices.filter((choice): choice is string => typeof choice === "string" && Boolean(choice.trim())).map((choice) => choice.trim()) : undefined,
257
+ blocking: value.blocking !== false,
258
+ status: value.status === "answered" ? "answered" : "open",
259
+ answer: typeof value.answer === "string" ? value.answer : undefined,
260
+ createdAt: typeof value.createdAt === "string" ? value.createdAt : new Date().toISOString(),
261
+ answeredAt: typeof value.answeredAt === "string" ? value.answeredAt : undefined,
262
+ };
263
+ }
264
+
265
+ function sanitizeRiskAssessment(value: unknown): ActiveRun["riskAssessment"] {
266
+ if (!isPlainObject(value)) return undefined;
267
+ const level = value.level;
268
+ if (level !== "low" && level !== "medium" && level !== "high") return undefined;
269
+ return {
270
+ level,
271
+ reason: typeof value.reason === "string" ? value.reason : "",
272
+ source: value.source === "verify" || value.source === "ship" ? value.source : "manual",
273
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : new Date().toISOString(),
274
+ };
275
+ }
276
+
277
+ function sanitizeRepairState(value: unknown): ActiveRun["repair"] {
278
+ if (!isPlainObject(value)) return undefined;
279
+ const attempts = typeof value.attempts === "number" && Number.isFinite(value.attempts) ? Math.max(0, Math.trunc(value.attempts)) : 0;
280
+ const maxAttempts = typeof value.maxAttempts === "number" && Number.isFinite(value.maxAttempts) ? Math.max(1, Math.trunc(value.maxAttempts)) : 3;
281
+ return {
282
+ attempts,
283
+ maxAttempts,
284
+ lastFailureEvidence: typeof value.lastFailureEvidence === "string" ? value.lastFailureEvidence : undefined,
285
+ lastFailureSignature: typeof value.lastFailureSignature === "string" ? value.lastFailureSignature : undefined,
286
+ status: value.status === "repairing" || value.status === "blocked" ? value.status : "idle",
287
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : new Date().toISOString(),
288
+ };
289
+ }
290
+
291
+ function sanitizeGate(value: unknown): ActiveRun["gate"] {
292
+ if (!isPlainObject(value)) return { passed: false, checkedAt: new Date().toISOString() };
293
+ return {
294
+ passed: value.passed === true,
295
+ reason: typeof value.reason === "string" ? value.reason : undefined,
296
+ checkedAt: typeof value.checkedAt === "string" ? value.checkedAt : new Date().toISOString(),
297
+ };
298
+ }
299
+
300
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
301
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
302
+ }
303
+
218
304
  function createRunId(goal: string): string {
219
305
  const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "").replace("T", "-");
220
306
  const slug = goal
@@ -18,6 +18,15 @@ export interface GateResult {
18
18
  checkedAt: string;
19
19
  }
20
20
 
21
+ export interface RepairState {
22
+ attempts: number;
23
+ maxAttempts: number;
24
+ lastFailureEvidence?: string;
25
+ lastFailureSignature?: string;
26
+ status: "idle" | "repairing" | "blocked";
27
+ updatedAt: string;
28
+ }
29
+
21
30
  export interface KdQuestion {
22
31
  id: string;
23
32
  phase: KdPhase;
@@ -43,6 +52,7 @@ export interface ActiveRun {
43
52
  version?: string;
44
53
  profile?: ProductProfile;
45
54
  riskAssessment?: RiskAssessment;
55
+ repair?: RepairState;
46
56
  artifacts: Record<string, string>;
47
57
  gate: GateResult;
48
58
  questions?: KdQuestion[];