@epoint-testtech/ep-stage-skill 0.0.3-alpha.1
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/SKILL.md +27 -0
- package/codex-skill/ep-stage/create-project/SKILL.md +59 -0
- package/codex-skill/ep-stage/glue-test/SKILL.md +258 -0
- package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +139 -0
- package/codex-skill/ep-stage/glue-test/references/gap-review-protocol.md +43 -0
- package/codex-skill/ep-stage/glue-test/references/harness-principles.md +46 -0
- package/codex-skill/ep-stage/glue-test/scripts/generate-crud-spec.mjs +149 -0
- package/codex-skill/ep-stage/glue-testcase/SKILL.md +31 -0
- package/codex-skill/ep-stage/glue-testcase/examples/observable-testcase.json +40 -0
- package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +67 -0
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +27 -0
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +73 -0
- package/dist/src/capability/coverage-diff.d.ts +34 -0
- package/dist/src/capability/coverage-diff.d.ts.map +1 -0
- package/dist/src/capability/coverage-diff.js +91 -0
- package/dist/src/capability/page-structure.d.ts +31 -0
- package/dist/src/capability/page-structure.d.ts.map +1 -0
- package/dist/src/capability/page-structure.js +50 -0
- package/dist/src/capability/scenario-inference.d.ts +36 -0
- package/dist/src/capability/scenario-inference.d.ts.map +1 -0
- package/dist/src/capability/scenario-inference.js +114 -0
- package/dist/src/cli/generate-crud-contract.d.ts +2 -0
- package/dist/src/cli/generate-crud-contract.d.ts.map +1 -0
- package/dist/src/cli/generate-crud-contract.js +77 -0
- package/dist/src/cli/generate-playwright-tests.d.ts +30 -0
- package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -0
- package/dist/src/cli/generate-playwright-tests.js +81 -0
- package/dist/src/cli/run-gap-pipeline.d.ts +256 -0
- package/dist/src/cli/run-gap-pipeline.d.ts.map +1 -0
- package/dist/src/cli/run-gap-pipeline.js +1468 -0
- package/dist/src/context/stage-context.d.ts +63 -0
- package/dist/src/context/stage-context.d.ts.map +1 -0
- package/dist/src/context/stage-context.js +297 -0
- package/dist/src/contracts/crud-business-module.d.ts +645 -0
- package/dist/src/contracts/crud-business-module.d.ts.map +1 -0
- package/dist/src/contracts/crud-business-module.js +1 -0
- package/dist/src/contracts/gap-inference.d.ts +213 -0
- package/dist/src/contracts/gap-inference.d.ts.map +1 -0
- package/dist/src/contracts/gap-inference.js +11 -0
- package/dist/src/contracts/observable-chain.d.ts +250 -0
- package/dist/src/contracts/observable-chain.d.ts.map +1 -0
- package/dist/src/contracts/observable-chain.js +1 -0
- package/dist/src/extractors/code-list.d.ts +40 -0
- package/dist/src/extractors/code-list.d.ts.map +1 -0
- package/dist/src/extractors/code-list.js +225 -0
- package/dist/src/extractors/html-page.d.ts +67 -0
- package/dist/src/extractors/html-page.d.ts.map +1 -0
- package/dist/src/extractors/html-page.js +195 -0
- package/dist/src/extractors/java-action.d.ts +8 -0
- package/dist/src/extractors/java-action.d.ts.map +1 -0
- package/dist/src/extractors/java-action.js +53 -0
- package/dist/src/extractors/spec-yaml.d.ts +28 -0
- package/dist/src/extractors/spec-yaml.d.ts.map +1 -0
- package/dist/src/extractors/spec-yaml.js +29 -0
- package/dist/src/gap-planner/action-candidates.d.ts +9 -0
- package/dist/src/gap-planner/action-candidates.d.ts.map +1 -0
- package/dist/src/gap-planner/action-candidates.js +66 -0
- package/dist/src/gap-planner/list-gap-workflows.d.ts +17 -0
- package/dist/src/gap-planner/list-gap-workflows.d.ts.map +1 -0
- package/dist/src/gap-planner/list-gap-workflows.js +47 -0
- package/dist/src/gap-planner/plan-agent-workflows.d.ts +26 -0
- package/dist/src/gap-planner/plan-agent-workflows.d.ts.map +1 -0
- package/dist/src/gap-planner/plan-agent-workflows.js +116 -0
- package/dist/src/gap-planner/skeleton-coverage.d.ts +9 -0
- package/dist/src/gap-planner/skeleton-coverage.d.ts.map +1 -0
- package/dist/src/gap-planner/skeleton-coverage.js +41 -0
- package/dist/src/gap-planner/stable-id.d.ts +16 -0
- package/dist/src/gap-planner/stable-id.d.ts.map +1 -0
- package/dist/src/gap-planner/stable-id.js +19 -0
- package/dist/src/generalization/generalization-eval.d.ts +71 -0
- package/dist/src/generalization/generalization-eval.d.ts.map +1 -0
- package/dist/src/generalization/generalization-eval.js +53 -0
- package/dist/src/generators/agent-inferred-workflow-script.d.ts +22 -0
- package/dist/src/generators/agent-inferred-workflow-script.d.ts.map +1 -0
- package/dist/src/generators/agent-inferred-workflow-script.js +230 -0
- package/dist/src/generators/stage-skeleton-script.d.ts +107 -0
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -0
- package/dist/src/generators/stage-skeleton-script.js +607 -0
- package/dist/src/index.d.ts +52 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +26 -0
- package/dist/src/material/material-inventory.d.ts +92 -0
- package/dist/src/material/material-inventory.d.ts.map +1 -0
- package/dist/src/material/material-inventory.js +191 -0
- package/dist/src/normalizers/crud-contract.d.ts +107 -0
- package/dist/src/normalizers/crud-contract.d.ts.map +1 -0
- package/dist/src/normalizers/crud-contract.js +1068 -0
- package/dist/src/testcase/testcase-generator.d.ts +43 -0
- package/dist/src/testcase/testcase-generator.d.ts.map +1 -0
- package/dist/src/testcase/testcase-generator.js +152 -0
- package/dist/src/testcase/testcase-skeleton.d.ts +91 -0
- package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -0
- package/dist/src/testcase/testcase-skeleton.js +121 -0
- package/dist/src/testcase/testcase-spec-assembly.d.ts +11 -0
- package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -0
- package/dist/src/testcase/testcase-spec-assembly.js +24 -0
- package/dist/src/trace/review-summary.d.ts +17 -0
- package/dist/src/trace/review-summary.d.ts.map +1 -0
- package/dist/src/trace/review-summary.js +34 -0
- package/dist/src/trace/trace-writer.d.ts +17 -0
- package/dist/src/trace/trace-writer.d.ts.map +1 -0
- package/dist/src/trace/trace-writer.js +81 -0
- package/dist/test/crud-contract.test.d.ts +2 -0
- package/dist/test/crud-contract.test.d.ts.map +1 -0
- package/dist/test/crud-contract.test.js +819 -0
- package/dist/test/gap-inference.test.d.ts +2 -0
- package/dist/test/gap-inference.test.d.ts.map +1 -0
- package/dist/test/gap-inference.test.js +597 -0
- package/dist/test/generalization.test.d.ts +2 -0
- package/dist/test/generalization.test.d.ts.map +1 -0
- package/dist/test/generalization.test.js +73 -0
- package/dist/test/material-inventory.test.d.ts +2 -0
- package/dist/test/material-inventory.test.d.ts.map +1 -0
- package/dist/test/material-inventory.test.js +141 -0
- package/dist/test/observable-chain.test.d.ts +2 -0
- package/dist/test/observable-chain.test.d.ts.map +1 -0
- package/dist/test/observable-chain.test.js +123 -0
- package/dist/test/observable-pipeline.test.d.ts +2 -0
- package/dist/test/observable-pipeline.test.d.ts.map +1 -0
- package/dist/test/observable-pipeline.test.js +461 -0
- package/dist/test/page-structure.test.d.ts +2 -0
- package/dist/test/page-structure.test.d.ts.map +1 -0
- package/dist/test/page-structure.test.js +45 -0
- package/dist/test/scenario-inference.test.d.ts +2 -0
- package/dist/test/scenario-inference.test.d.ts.map +1 -0
- package/dist/test/scenario-inference.test.js +73 -0
- package/dist/test/stage-context.test.d.ts +2 -0
- package/dist/test/stage-context.test.d.ts.map +1 -0
- package/dist/test/stage-context.test.js +263 -0
- package/dist/test/testcase-generator.test.d.ts +2 -0
- package/dist/test/testcase-generator.test.d.ts.map +1 -0
- package/dist/test/testcase-generator.test.js +276 -0
- package/dist/test/testcase-skeleton.test.d.ts +2 -0
- package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
- package/dist/test/testcase-skeleton.test.js +185 -0
- package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
- package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
- package/dist/test/testcase-spec-assembly.test.js +105 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +7 -0
- package/docs/README.md +134 -0
- package/docs/mvp-usage-guide.md +298 -0
- package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -0
- package/examples/schemeresource.module-hints.json +38 -0
- package/examples/schemeresource.observable.code_list.md +37 -0
- package/examples/zwplace-observable-docs/zwplace.context.md +16 -0
- package/examples/zwplace-placecategory-validation.json +29 -0
- package/examples/zwplace.module-hints.json +69 -0
- package/examples/zwplace.observable.code_list.md +37 -0
- package/package.json +38 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ObservableStagePayload } from '../contracts/observable-chain.js';
|
|
2
|
+
export type StageContextMode = 'default' | 'glue';
|
|
3
|
+
export type StageContext = {
|
|
4
|
+
projectName: string;
|
|
5
|
+
projectDir: string;
|
|
6
|
+
mode: StageContextMode;
|
|
7
|
+
envPath: string;
|
|
8
|
+
envFilePath: string;
|
|
9
|
+
knowledgeRoot?: string;
|
|
10
|
+
codeListPaths: string[];
|
|
11
|
+
stageContextPath?: string;
|
|
12
|
+
};
|
|
13
|
+
export type ProjectIndexEntry = {
|
|
14
|
+
projectName: string;
|
|
15
|
+
projectDir: string;
|
|
16
|
+
mode: StageContextMode;
|
|
17
|
+
envPath?: string;
|
|
18
|
+
knowledgeRoot?: string;
|
|
19
|
+
codeListPaths?: string[];
|
|
20
|
+
stageContextPath?: string;
|
|
21
|
+
};
|
|
22
|
+
export type ProjectIndex = {
|
|
23
|
+
version?: 1;
|
|
24
|
+
projects: ProjectIndexEntry[];
|
|
25
|
+
};
|
|
26
|
+
export type ResolveStageContextInput = {
|
|
27
|
+
stageContextPath?: string;
|
|
28
|
+
projectDir?: string;
|
|
29
|
+
cwd?: string;
|
|
30
|
+
codeListPath?: string;
|
|
31
|
+
homeDir?: string;
|
|
32
|
+
};
|
|
33
|
+
export type ResolveStageContextResult = {
|
|
34
|
+
context: StageContext;
|
|
35
|
+
trace: ObservableStagePayload;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 读取项目级 stage-context.md,并将相对路径归一到项目目录。
|
|
39
|
+
*
|
|
40
|
+
* @param stageContextPath - stage-context.md 文件路径。
|
|
41
|
+
* @returns 归一化后的项目上下文。
|
|
42
|
+
* @throws 当 frontmatter 缺少必要字段或格式不合法时抛出错误。
|
|
43
|
+
*/
|
|
44
|
+
export declare function readStageContext(stageContextPath: string): StageContext;
|
|
45
|
+
/**
|
|
46
|
+
* 写入用户级 ep-stage 项目索引。
|
|
47
|
+
*
|
|
48
|
+
* @param input - HOME 目录和项目列表。
|
|
49
|
+
* @returns 写入后的索引文件路径。
|
|
50
|
+
*/
|
|
51
|
+
export declare function writeProjectIndex(input: {
|
|
52
|
+
homeDir?: string;
|
|
53
|
+
projects: ProjectIndexEntry[];
|
|
54
|
+
}): string;
|
|
55
|
+
/**
|
|
56
|
+
* 按固定优先级解析 ep-stage 项目上下文,并返回可审阅 trace。
|
|
57
|
+
*
|
|
58
|
+
* @param input - 显式路径、当前目录、code_list 路径和可选 HOME。
|
|
59
|
+
* @returns 解析出的项目上下文与 context-resolution trace。
|
|
60
|
+
* @throws 当无法定位任何上下文来源时抛出错误。
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveStageContext(input: ResolveStageContextInput): ResolveStageContextResult;
|
|
63
|
+
//# sourceMappingURL=stage-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-context.d.ts","sourceRoot":"","sources":["../../../src/context/stage-context.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAE/E,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,sBAAsB,CAAC;CAC/B,CAAC;AAWF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,GAAG,YAAY,CAqBvE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,GAAG,MAAM,CAST;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,yBAAyB,CA6D9F"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import JSON5 from 'json5';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
/**
|
|
7
|
+
* 读取项目级 stage-context.md,并将相对路径归一到项目目录。
|
|
8
|
+
*
|
|
9
|
+
* @param stageContextPath - stage-context.md 文件路径。
|
|
10
|
+
* @returns 归一化后的项目上下文。
|
|
11
|
+
* @throws 当 frontmatter 缺少必要字段或格式不合法时抛出错误。
|
|
12
|
+
*/
|
|
13
|
+
export function readStageContext(stageContextPath) {
|
|
14
|
+
const resolvedStageContextPath = path.resolve(stageContextPath);
|
|
15
|
+
const baseDir = path.dirname(resolvedStageContextPath);
|
|
16
|
+
const raw = parseStageContextFrontmatter(readFileSync(resolvedStageContextPath, 'utf8'));
|
|
17
|
+
const projectDir = resolvePath(baseDir, raw.projectDir ?? '.');
|
|
18
|
+
const envPath = raw.envPath ?? '.env';
|
|
19
|
+
if (!raw.projectName) {
|
|
20
|
+
throw new Error(`stage-context.md missing projectName: ${resolvedStageContextPath}`);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
projectName: raw.projectName,
|
|
24
|
+
projectDir,
|
|
25
|
+
mode: raw.mode ?? 'glue',
|
|
26
|
+
envPath,
|
|
27
|
+
envFilePath: resolvePath(projectDir, envPath),
|
|
28
|
+
knowledgeRoot: raw.knowledgeRoot ? resolvePath(projectDir, raw.knowledgeRoot) : undefined,
|
|
29
|
+
codeListPaths: (raw.codeListPaths ?? []).map((codeListPath) => resolvePath(projectDir, codeListPath)),
|
|
30
|
+
stageContextPath: resolvedStageContextPath,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 写入用户级 ep-stage 项目索引。
|
|
35
|
+
*
|
|
36
|
+
* @param input - HOME 目录和项目列表。
|
|
37
|
+
* @returns 写入后的索引文件路径。
|
|
38
|
+
*/
|
|
39
|
+
export function writeProjectIndex(input) {
|
|
40
|
+
const indexPath = getProjectIndexPath(input.homeDir);
|
|
41
|
+
mkdirSync(path.dirname(indexPath), { recursive: true });
|
|
42
|
+
writeFileSync(indexPath, `${JSON5.stringify({ version: 1, projects: input.projects }, null, 2)}\n`, 'utf8');
|
|
43
|
+
return indexPath;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 按固定优先级解析 ep-stage 项目上下文,并返回可审阅 trace。
|
|
47
|
+
*
|
|
48
|
+
* @param input - 显式路径、当前目录、code_list 路径和可选 HOME。
|
|
49
|
+
* @returns 解析出的项目上下文与 context-resolution trace。
|
|
50
|
+
* @throws 当无法定位任何上下文来源时抛出错误。
|
|
51
|
+
*/
|
|
52
|
+
export function resolveStageContext(input) {
|
|
53
|
+
if (input.stageContextPath) {
|
|
54
|
+
const context = readStageContext(input.stageContextPath);
|
|
55
|
+
return {
|
|
56
|
+
context,
|
|
57
|
+
trace: createTrace(context, {
|
|
58
|
+
sourceLabel: '显式 stage-context.md',
|
|
59
|
+
sourcePath: context.stageContextPath ?? path.resolve(input.stageContextPath),
|
|
60
|
+
requestedCodeListPath: input.codeListPath,
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const startDir = path.resolve(input.projectDir ?? input.cwd ?? process.cwd());
|
|
65
|
+
const localStageContextPath = findStageContextPath(startDir);
|
|
66
|
+
if (localStageContextPath) {
|
|
67
|
+
const context = readStageContext(localStageContextPath);
|
|
68
|
+
return {
|
|
69
|
+
context,
|
|
70
|
+
trace: createTrace(context, {
|
|
71
|
+
sourceLabel: '向上查找 stage-context.md',
|
|
72
|
+
sourcePath: localStageContextPath,
|
|
73
|
+
requestedCodeListPath: input.codeListPath,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const indexPath = getProjectIndexPath(input.homeDir);
|
|
78
|
+
const index = readProjectIndex(indexPath);
|
|
79
|
+
const matched = index.projects.find((entry) => matchesProjectIndexEntry(entry, {
|
|
80
|
+
projectDir: input.projectDir,
|
|
81
|
+
cwd: input.cwd,
|
|
82
|
+
codeListPath: input.codeListPath,
|
|
83
|
+
}));
|
|
84
|
+
if (!matched) {
|
|
85
|
+
if (input.projectDir) {
|
|
86
|
+
const context = createDebugProjectContext(input.projectDir, input.codeListPath);
|
|
87
|
+
return {
|
|
88
|
+
context,
|
|
89
|
+
trace: createTrace(context, {
|
|
90
|
+
sourceLabel: '显式 --project-dir 调试上下文',
|
|
91
|
+
sourcePath: context.projectDir,
|
|
92
|
+
requestedCodeListPath: input.codeListPath,
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`stage context not found from ${startDir} or ${indexPath}`);
|
|
97
|
+
}
|
|
98
|
+
const context = matched.stageContextPath
|
|
99
|
+
? readStageContext(resolvePath(path.resolve(matched.projectDir), matched.stageContextPath))
|
|
100
|
+
: normalizeProjectIndexEntry(matched);
|
|
101
|
+
return {
|
|
102
|
+
context,
|
|
103
|
+
trace: createTrace(context, {
|
|
104
|
+
sourceLabel: '用户级 projects.index.json5',
|
|
105
|
+
sourcePath: indexPath,
|
|
106
|
+
requestedCodeListPath: input.codeListPath,
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 根据显式 --project-dir 合成最小调试上下文。
|
|
112
|
+
*
|
|
113
|
+
* @param projectDirInput - 用户传入的项目目录。
|
|
114
|
+
* @param codeListPathInput - 可选 code_list.md 路径。
|
|
115
|
+
* @returns 最小 StageContext,用于开发调试继续进入 .env gate。
|
|
116
|
+
*/
|
|
117
|
+
function createDebugProjectContext(projectDirInput, codeListPathInput) {
|
|
118
|
+
const projectDir = path.resolve(projectDirInput);
|
|
119
|
+
return {
|
|
120
|
+
projectName: path.basename(projectDir),
|
|
121
|
+
projectDir,
|
|
122
|
+
mode: 'glue',
|
|
123
|
+
envPath: '.env',
|
|
124
|
+
envFilePath: path.join(projectDir, '.env'),
|
|
125
|
+
codeListPaths: codeListPathInput ? [path.resolve(codeListPathInput)] : [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 从 markdown frontmatter 中解析原始上下文字段。
|
|
130
|
+
*
|
|
131
|
+
* @param markdown - stage-context.md 原文。
|
|
132
|
+
* @returns 原始上下文字段。
|
|
133
|
+
* @throws 当文件没有 YAML frontmatter 时抛出错误。
|
|
134
|
+
*/
|
|
135
|
+
function parseStageContextFrontmatter(markdown) {
|
|
136
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
|
|
137
|
+
if (!match) {
|
|
138
|
+
throw new Error('stage-context.md missing YAML frontmatter');
|
|
139
|
+
}
|
|
140
|
+
return YAML.parse(match[1] ?? '');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 读取用户级 JSON5 项目索引。
|
|
144
|
+
*
|
|
145
|
+
* @param indexPath - 索引文件路径。
|
|
146
|
+
* @returns 项目索引对象。
|
|
147
|
+
*/
|
|
148
|
+
function readProjectIndex(indexPath) {
|
|
149
|
+
if (!existsSync(indexPath)) {
|
|
150
|
+
return { projects: [] };
|
|
151
|
+
}
|
|
152
|
+
const parsed = JSON5.parse(readFileSync(indexPath, 'utf8'));
|
|
153
|
+
return { projects: parsed.projects ?? [] };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 将用户索引条目归一为 StageContext。
|
|
157
|
+
*
|
|
158
|
+
* @param entry - 用户索引中的项目条目。
|
|
159
|
+
* @returns 归一化后的项目上下文。
|
|
160
|
+
*/
|
|
161
|
+
function normalizeProjectIndexEntry(entry) {
|
|
162
|
+
const projectDir = path.resolve(entry.projectDir);
|
|
163
|
+
const envPath = entry.envPath ?? '.env';
|
|
164
|
+
return {
|
|
165
|
+
projectName: entry.projectName,
|
|
166
|
+
projectDir,
|
|
167
|
+
mode: entry.mode,
|
|
168
|
+
envPath,
|
|
169
|
+
envFilePath: resolvePath(projectDir, envPath),
|
|
170
|
+
knowledgeRoot: entry.knowledgeRoot ? resolvePath(projectDir, entry.knowledgeRoot) : undefined,
|
|
171
|
+
codeListPaths: (entry.codeListPaths ?? []).map((codeListPath) => resolvePath(projectDir, codeListPath)),
|
|
172
|
+
stageContextPath: entry.stageContextPath ? resolvePath(projectDir, entry.stageContextPath) : undefined,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 判断用户索引条目是否匹配当前 projectDir、cwd 或 code_list 路径。
|
|
177
|
+
*
|
|
178
|
+
* @param entry - 用户索引中的项目条目。
|
|
179
|
+
* @param input - 当前解析输入。
|
|
180
|
+
* @returns 是否匹配该条目。
|
|
181
|
+
*/
|
|
182
|
+
function matchesProjectIndexEntry(entry, input) {
|
|
183
|
+
const context = normalizeProjectIndexEntry(entry);
|
|
184
|
+
const candidateDirs = [input.projectDir, input.cwd]
|
|
185
|
+
.filter((candidate) => Boolean(candidate))
|
|
186
|
+
.map((candidate) => path.resolve(candidate));
|
|
187
|
+
const matchedByDir = candidateDirs.some((candidate) => isSameOrInside(candidate, context.projectDir));
|
|
188
|
+
if (matchedByDir) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
if (!input.codeListPath) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
const requestedCodeListPath = path.resolve(input.codeListPath);
|
|
195
|
+
return context.codeListPaths.some((codeListPath) => path.resolve(codeListPath) === requestedCodeListPath);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 向上查找最近的 stage-context.md。
|
|
199
|
+
*
|
|
200
|
+
* @param startDir - 起始目录。
|
|
201
|
+
* @returns 找到的 stage-context.md 路径,不存在则返回 undefined。
|
|
202
|
+
*/
|
|
203
|
+
function findStageContextPath(startDir) {
|
|
204
|
+
let currentDir = startDir;
|
|
205
|
+
while (true) {
|
|
206
|
+
const candidate = path.join(currentDir, 'stage-context.md');
|
|
207
|
+
if (existsSync(candidate)) {
|
|
208
|
+
return candidate;
|
|
209
|
+
}
|
|
210
|
+
const parentDir = path.dirname(currentDir);
|
|
211
|
+
if (parentDir === currentDir) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
currentDir = parentDir;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 创建 context-resolution trace payload。
|
|
219
|
+
*
|
|
220
|
+
* @param context - 已解析上下文。
|
|
221
|
+
* @param source - 上下文来源信息。
|
|
222
|
+
* @returns 可观测 trace payload。
|
|
223
|
+
*/
|
|
224
|
+
function createTrace(context, source) {
|
|
225
|
+
const envExists = existsSync(context.envFilePath);
|
|
226
|
+
return {
|
|
227
|
+
stage: 'context-resolution',
|
|
228
|
+
stageLabel: '项目上下文解析',
|
|
229
|
+
inputs: [
|
|
230
|
+
{ kind: 'project', path: context.projectDir, description: context.projectName },
|
|
231
|
+
...(source.requestedCodeListPath
|
|
232
|
+
? [{ kind: 'code_list', path: path.resolve(source.requestedCodeListPath) }]
|
|
233
|
+
: []),
|
|
234
|
+
{ kind: 'context_source', path: source.sourcePath, description: source.sourceLabel },
|
|
235
|
+
],
|
|
236
|
+
outputs: [
|
|
237
|
+
...(context.stageContextPath
|
|
238
|
+
? [{ kind: 'stage_context', path: context.stageContextPath }]
|
|
239
|
+
: []),
|
|
240
|
+
{ kind: 'env', path: context.envFilePath, description: envExists ? '.env 已存在' : '.env 缺失' },
|
|
241
|
+
],
|
|
242
|
+
reasoningSummary: {
|
|
243
|
+
conclusion: `从${source.sourceLabel}解析项目上下文:${source.sourcePath}`,
|
|
244
|
+
evidenceChain: [
|
|
245
|
+
{
|
|
246
|
+
source: 'human',
|
|
247
|
+
path: source.sourcePath,
|
|
248
|
+
text: `项目 ${context.projectName},模式 ${context.mode},envPath ${context.envPath}`,
|
|
249
|
+
},
|
|
250
|
+
...context.codeListPaths.map((codeListPath) => ({
|
|
251
|
+
source: 'code_list',
|
|
252
|
+
path: codeListPath,
|
|
253
|
+
text: '上下文声明的 code_list 来源',
|
|
254
|
+
})),
|
|
255
|
+
],
|
|
256
|
+
alternatives: ['显式 stage-context.md', '向上查找 stage-context.md', '用户级 projects.index.json5'],
|
|
257
|
+
confidence: 'high',
|
|
258
|
+
risks: envExists ? [] : ['.env 文件缺失,运行前需要补齐登录配置'],
|
|
259
|
+
needsHumanReview: !envExists,
|
|
260
|
+
},
|
|
261
|
+
candidateCount: 1,
|
|
262
|
+
unresolvedCount: envExists ? 0 : 1,
|
|
263
|
+
gate: envExists
|
|
264
|
+
? { status: 'confirmed', confirmedBy: 'policy', reason: '.env 文件存在' }
|
|
265
|
+
: { status: 'needs_review', reason: `.env 文件不存在: ${context.envFilePath}` },
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* 获取用户级项目索引路径。
|
|
270
|
+
*
|
|
271
|
+
* @param homeDir - 可选 HOME 目录。
|
|
272
|
+
* @returns projects.index.json5 绝对路径。
|
|
273
|
+
*/
|
|
274
|
+
function getProjectIndexPath(homeDir) {
|
|
275
|
+
return path.join(homeDir ?? os.homedir(), '.ep-stage', 'projects.index.json5');
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* 解析相对或绝对路径。
|
|
279
|
+
*
|
|
280
|
+
* @param baseDir - 相对路径基准目录。
|
|
281
|
+
* @param targetPath - 待解析路径。
|
|
282
|
+
* @returns 绝对路径。
|
|
283
|
+
*/
|
|
284
|
+
function resolvePath(baseDir, targetPath) {
|
|
285
|
+
return path.isAbsolute(targetPath) ? path.resolve(targetPath) : path.resolve(baseDir, targetPath);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 判断 candidate 是否等于 baseDir 或位于其内部。
|
|
289
|
+
*
|
|
290
|
+
* @param candidate - 候选目录。
|
|
291
|
+
* @param baseDir - 基准目录。
|
|
292
|
+
* @returns 是否为同一路径或子路径。
|
|
293
|
+
*/
|
|
294
|
+
function isSameOrInside(candidate, baseDir) {
|
|
295
|
+
const relativePath = path.relative(baseDir, candidate);
|
|
296
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
297
|
+
}
|