kcode-pi 0.1.2 → 0.1.5

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,83 @@
1
+ import type { ActiveRun } from "./types.ts";
2
+ import { existsSync } from "node:fs";
3
+ import { isAbsolute, join, relative } from "node:path";
4
+
5
+ const SOURCE_EXTENSIONS = new Set([
6
+ ".java",
7
+ ".kt",
8
+ ".kts",
9
+ ".xml",
10
+ ".properties",
11
+ ".yml",
12
+ ".yaml",
13
+ ".sql",
14
+ ".ksql",
15
+ ".py",
16
+ ]);
17
+
18
+ export function flagshipWriteBlockReason(run: ActiveRun | undefined, path: string | undefined, cwd?: string): string | undefined {
19
+ if (run?.profile?.product !== "flagship") return undefined;
20
+ if (!path || !isSourceLikePath(path)) return undefined;
21
+
22
+ const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path);
23
+ if (normalized.startsWith(".pi/")) return undefined;
24
+ if (cwd && !hasWorkspaceCodeDir(cwd)) return undefined;
25
+ if (!normalized.startsWith("code/")) return `星空旗舰版代码必须跟随当前项目结构写入 code/ 下,不能写到 ${path}`;
26
+
27
+ return undefined;
28
+ }
29
+
30
+ export function planWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined, plan: string): string | undefined {
31
+ if (!run || run.phase !== "execute") return undefined;
32
+ if (!path || !isSourceLikePath(path)) return undefined;
33
+
34
+ const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path);
35
+ if (normalized.startsWith(".pi/")) return undefined;
36
+ if (planMentionsPath(plan, normalized)) return undefined;
37
+
38
+ return `PLAN.md 未批准写入 ${normalized}。请先回到 plan 阶段更新 PLAN.md,明确列出该目标文件后再执行。`;
39
+ }
40
+
41
+ export function flagshipPlanBlockReason(cwd: string, run: ActiveRun | undefined, plan: string): string | undefined {
42
+ if (run?.profile?.product !== "flagship") return undefined;
43
+
44
+ if (hasWorkspaceCodeDir(cwd)) {
45
+ if (/(?:^|[\s`"'(])code[\\/][^\s`"')]+/i.test(plan)) return undefined;
46
+ return "不能进入 execute:星空旗舰版 PLAN.md 必须先记录当前项目 code/ 下的实际目标路径;不要按固定模块规则猜路径。";
47
+ }
48
+
49
+ if (planMentionsDiscoveredSourcePath(plan)) return undefined;
50
+ return "不能进入 execute:PLAN.md 必须先记录已检查当前项目结构,并写明实际源码根或目标文件路径;当前项目没有 code/ 时更不能猜路径。";
51
+ }
52
+
53
+ function hasWorkspaceCodeDir(cwd: string): boolean {
54
+ return existsSync(join(cwd, "code"));
55
+ }
56
+
57
+ function planMentionsDiscoveredSourcePath(plan: string): boolean {
58
+ return /(?:^|[\s`"'(])(?:src[\\/]main[\\/]java|src[\\/]main[\\/]resources|[^`\n]*[\\/](?:src[\\/]main[\\/]java|src[\\/]main[\\/]resources)|[^`\n]*\.(?:java|xml|properties|yml|yaml|sql|ksql|py))(?:[\s`"')]|$)/i.test(
59
+ plan,
60
+ );
61
+ }
62
+
63
+ function planMentionsPath(plan: string, normalizedPath: string): boolean {
64
+ const normalizedPlan = normalizeRelativePath(plan);
65
+ const escaped = escapeRegExp(normalizedPath);
66
+ return new RegExp(`(?:^|[\\s\`"'(])${escaped}(?:[\\s\`"')]|$)`, "i").test(normalizedPlan);
67
+ }
68
+
69
+ function normalizeRelativePath(path: string): string {
70
+ return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
71
+ }
72
+
73
+ export function isSourceLikePath(path: string): boolean {
74
+ const normalized = normalizeRelativePath(path);
75
+ const lastSegment = normalized.split("/").at(-1) ?? normalized;
76
+ const dotIndex = lastSegment.lastIndexOf(".");
77
+ if (dotIndex < 0) return false;
78
+ return SOURCE_EXTENSIONS.has(lastSegment.slice(dotIndex).toLowerCase());
79
+ }
80
+
81
+ function escapeRegExp(value: string): string {
82
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83
+ }
@@ -0,0 +1,79 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { ActiveRun } from "./types.ts";
4
+ import { runRoot } from "./paths.ts";
5
+
6
+ export interface PlanStep {
7
+ id: string;
8
+ text: string;
9
+ }
10
+
11
+ const STEP_LINE_PATTERN = /^\s*[-*]\s*\[[ xX]\]\s*(STEP-\d{3,})\s*[::]\s*(.+)$/gim;
12
+ const EVIDENCE_PATTERN = /\bevidence[\\/][^\s`"')]+/gi;
13
+
14
+ export function parsePlanSteps(plan: string): PlanStep[] {
15
+ const steps: PlanStep[] = [];
16
+ for (const match of plan.matchAll(STEP_LINE_PATTERN)) {
17
+ steps.push({ id: match[1].toUpperCase(), text: match[2].trim() });
18
+ }
19
+ return steps;
20
+ }
21
+
22
+ export function planStepsBlockReason(plan: string): string | undefined {
23
+ if (!/##\s*Execution Steps/i.test(plan)) {
24
+ return "PLAN.md 缺少 ## Execution Steps。必须把计划拆成 STEP-001 这种可跟踪步骤。";
25
+ }
26
+
27
+ const steps = parsePlanSteps(plan);
28
+ if (steps.length === 0) {
29
+ return "PLAN.md 没有可执行步骤。请使用 `- [ ] STEP-001: ...` 列出步骤。";
30
+ }
31
+
32
+ const duplicate = firstDuplicate(steps.map((step) => step.id));
33
+ if (duplicate) return `PLAN.md 存在重复步骤编号:${duplicate}`;
34
+ return undefined;
35
+ }
36
+
37
+ export function executionStepsBlockReason(cwd: string, run: ActiveRun, plan: string, execution: string): string | undefined {
38
+ const steps = parsePlanSteps(plan);
39
+ if (steps.length === 0) return planStepsBlockReason(plan);
40
+
41
+ const missing: string[] = [];
42
+ const missingEvidence: string[] = [];
43
+
44
+ for (const step of steps) {
45
+ const line = findExecutionLine(execution, step.id);
46
+ if (!line) {
47
+ missing.push(step.id);
48
+ continue;
49
+ }
50
+
51
+ const evidencePaths = [...line.matchAll(EVIDENCE_PATTERN)].map((match) => normalizeEvidencePath(match[0]));
52
+ if (evidencePaths.length === 0 || !evidencePaths.some((path) => existsSync(join(runRoot(cwd, run), path)))) {
53
+ missingEvidence.push(step.id);
54
+ }
55
+ }
56
+
57
+ if (missing.length > 0) return `不能进入 verify:EXECUTION.md 未完成计划步骤 ${missing.join(", ")}。`;
58
+ if (missingEvidence.length > 0) return `不能进入 verify:步骤 ${missingEvidence.join(", ")} 缺少已落地的 evidence 文件。`;
59
+ return undefined;
60
+ }
61
+
62
+ function findExecutionLine(execution: string, stepId: string): string | undefined {
63
+ return execution
64
+ .split(/\r?\n/)
65
+ .find((line) => line.toUpperCase().includes(stepId) && /(\[[xX]\]|done|完成|completed)/i.test(line));
66
+ }
67
+
68
+ function firstDuplicate(values: string[]): string | undefined {
69
+ const seen = new Set<string>();
70
+ for (const value of values) {
71
+ if (seen.has(value)) return value;
72
+ seen.add(value);
73
+ }
74
+ return undefined;
75
+ }
76
+
77
+ function normalizeEvidencePath(path: string): string {
78
+ return path.replace(/\\/g, "/").replace(/^\/+/, "");
79
+ }
@@ -0,0 +1,62 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { isAbsolute, join, relative } from "node:path";
3
+ import type { ActiveRun } from "./types.ts";
4
+ import { runRoot } from "./paths.ts";
5
+ import { isSourceLikePath } from "./path-policy.ts";
6
+
7
+ export const TDD_RED_EVIDENCE = "evidence/tdd-red.md";
8
+ export const TDD_GREEN_EVIDENCE = "evidence/tdd-green.md";
9
+
10
+ export function tddPlanBlockReason(plan: string): string | undefined {
11
+ if (/##\s*TDD\s*\/\s*Red-Green Checks/i.test(plan)) return undefined;
12
+ return "PLAN.md 缺少 ## TDD / Red-Green Checks。必须声明红灯验证、绿灯验证和无法自动化时的产品验证替代方案。";
13
+ }
14
+
15
+ export function tddProductionWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined): string | undefined {
16
+ if (!run || run.phase !== "execute") return undefined;
17
+ if (!path || !isSourceLikePath(path)) return undefined;
18
+
19
+ const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path);
20
+ if (normalized.startsWith(".pi/")) return undefined;
21
+ if (isTestLikePath(normalized)) return undefined;
22
+ if (hasValidTddEvidence(cwd, run, "red")) return undefined;
23
+
24
+ return `不能写生产源码 ${normalized}:缺少红灯证据 ${TDD_RED_EVIDENCE}。请先记录失败的测试、API/基类/方法签名检查、元数据检查、编译检查或外部接口最小验证输出。`;
25
+ }
26
+
27
+ export function tddVerifyBlockReason(cwd: string, run: ActiveRun): string | undefined {
28
+ const missing: string[] = [];
29
+ if (!hasValidTddEvidence(cwd, run, "red")) missing.push(TDD_RED_EVIDENCE);
30
+ if (!hasValidTddEvidence(cwd, run, "green")) missing.push(TDD_GREEN_EVIDENCE);
31
+ if (missing.length === 0) return undefined;
32
+ return `不能进入 verify:缺少 TDD 红绿证据 ${missing.join(", ")}。`;
33
+ }
34
+
35
+ function hasValidTddEvidence(cwd: string, run: ActiveRun, kind: "red" | "green"): boolean {
36
+ const evidenceName = kind === "red" ? TDD_RED_EVIDENCE : TDD_GREEN_EVIDENCE;
37
+ const evidencePath = join(runRoot(cwd, run), evidenceName);
38
+ if (!existsSync(evidencePath)) return false;
39
+
40
+ const content = readFileSync(evidencePath, "utf8");
41
+ if (kind === "red") return /red|fail|failed|failure|error|失败|未通过|Exit:\s*[1-9]/i.test(content);
42
+ return /green|pass|passed|success|成功|通过|Exit:\s*0/i.test(content);
43
+ }
44
+
45
+ function isTestLikePath(path: string): boolean {
46
+ const normalized = normalizeRelativePath(path).toLowerCase();
47
+ const filename = normalized.split("/").at(-1) ?? normalized;
48
+ return (
49
+ normalized.includes("/src/test/") ||
50
+ normalized.includes("/test/") ||
51
+ normalized.includes("/tests/") ||
52
+ normalized.includes("/__tests__/") ||
53
+ /[._-](test|spec)\./.test(filename) ||
54
+ /test\.(java|kt|kts|cs)$/i.test(filename) ||
55
+ /tests\.(cs)$/i.test(filename) ||
56
+ /_test\.py$/i.test(filename)
57
+ );
58
+ }
59
+
60
+ function normalizeRelativePath(path: string): string {
61
+ return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
62
+ }
@@ -1,4 +1,4 @@
1
- export type KnowledgeScope = "enterprise" | "flagship" | "cosmic" | "xinghan" | "cangqiong";
1
+ export type KnowledgeScope = "enterprise" | "enterprise-python" | "flagship" | "cosmic" | "xinghan" | "cangqiong";
2
2
  export type Edition = Extract<KnowledgeScope, "enterprise" | "flagship">;
3
3
 
4
4
  export type KnowledgeFileType = "markdown" | "json";