@c4a/context 0.6.1 → 0.6.3

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,99 @@
1
+ ---
2
+ name: {{skillName}}
3
+ description: 查询 {{displayName}} 中经过审核、可追溯来源的知识。用于查找包内实体、API、行为、流程、约束、决策、排障信息、关系和覆盖范围。
4
+ ---
5
+
6
+ # 知识查询
7
+
8
+ 只根据本包包含的已批准知识回答。先通过索引缩小范围,再打开具体页面;正文是事实证据,frontmatter 摘要只用于导航。
9
+
10
+ ## 查询步骤
11
+
12
+ 1. 判断请求属于实体查找、解释、流程、规则、关系、细节还是覆盖检查。
13
+ 2. 从最相关的根索引开始,例如 `{{wikisRoot}}/index.md`,再沿链接进入具体页面。
14
+ 3. 只读取回答所需的章节;frontmatter 用来选择范围,不作为事实依据。
15
+ 4. 查询关系或影响范围时,先检查 `context-build-inventory.json` 中的 `structure.edge_records`,再读取两端页面。
16
+ 5. 结论只能来自可见章节正文和来源支持的边记录,并引用对应页面或章节。
17
+ 6. 如果包内没有证据,明确报告缺口和已经检查的范围,不要根据邻近内容推断。
18
+
19
+ ## 知识根目录
20
+
21
+ | 根目录 | 用途 |
22
+ |---|---|
23
+ | `{{wikisRoot}}/` | 来自 codegraph、business、product 的结构化实体和关系。 |
24
+ | `{{guidesRoot}}/` | 架构、流程、FAQ、决策、故障和排障说明。 |
25
+ | `{{rulesRoot}}/` | 标准、约束、验收条件和测试场景。 |
26
+ | `{{featsRoot}}/` | 被选择进入包内的功能知识。 |
27
+
28
+ 缺少某个根目录表示当前知识包没有选择该类别。
29
+
30
+ ## 按意图导航
31
+
32
+ | 意图 | 第一步 |
33
+ |---|---|
34
+ | 模糊主题或未知名称 | 打开可能相关的根索引,从分组中选择候选页面。 |
35
+ | 指定实体、API、领域或动作 | 打开匹配页面或最近的分组索引;有歧义时列出候选。 |
36
+ | 架构、流程、FAQ、决策或故障 | 从 `{{guidesRoot}}/index.md` 开始。 |
37
+ | 标准、约束、验收或测试 | 从 `{{rulesRoot}}/index.md` 开始。 |
38
+ | 关系或影响范围 | 检查类型化边,再读取两端页面。 |
39
+ | 已知页面中的细节 | 读取对应 `context:section`。 |
40
+ | 覆盖范围或缺口 | 检查根索引和 `context-build-inventory.json`。 |
41
+
42
+ ## 证据契约
43
+
44
+ | 证据 | 可支持的内容 |
45
+ |---|---|
46
+ | 页面路径 | 页面身份和引用位置。 |
47
+ | frontmatter 的标题、描述、稳定节点标识和标签 | 导航和范围选择。 |
48
+ | `context:section` 的 id、kind、`source_ref` | 章节身份、引用位置和来源边界。 |
49
+ | 读者可见的章节正文 | 事实结论的主要依据。 |
50
+ | `context-build-inventory.json` 的边记录 | 类型化关系证据。 |
51
+ | 根索引和构建清单 | 包的范围和覆盖情况。 |
52
+
53
+ 不要根据页面同时出现来推断关系。如果 `source_ref` 指向未随包分发的来源,只能引用已经过审核的可见章节,不能扩展到正文没有表达的内容。
54
+
55
+ ## 搜索兜底
56
+
57
+ 只有索引和页面结构无法确定范围,或候选页面过大不适合整页读取时才搜索。精确名称、API、配置键、路径或错误字符串可先用 `rg`;包含多个关键词、中文短语或多个大型索引时,执行本 Skill 随附的 [`scripts/search.mjs`](scripts/search.mjs) 做 BM25 排序:
58
+
59
+ ```bash
60
+ node <当前 knowledge-query Skill 目录>/scripts/search.mjs --query '<关键词>' --limit 8
61
+ ```
62
+
63
+ 脚本位于知识包目录内时会自动定位 `{{packageName}}`。如果包管理工具将本 Skill 复制到了其他位置,增加 `--root <包含 context-build-inventory.json 的包目录>`;也可以使用 `--base <多包集合根目录>`,按构建清单中的包名定位。它按 Markdown 标题和固定行块机械切分,返回路径、行号、标题和短预览,不判断内容语义。
64
+
65
+ 搜索命中只是线索。回答前必须打开命中的页面和章节;关系或影响范围仍以 `context-build-inventory.json` 的类型化边为准,不能用 BM25 分数或文本共现替代关系证据。
66
+
67
+ ## 引用与缺口
68
+
69
+ 引用应紧跟结论:
70
+
71
+ ```text
72
+ 页面: <结论> [<root>/path/page.md]
73
+ 章节: <结论> [<root>/path/page.md#section-id]
74
+ 来源绑定: <结论> [<root>/path/page.md#section-id, source_ref]
75
+ 关系: <结论> [context-build-inventory.json#structure.edge_records edge:<type>]
76
+ 覆盖: <结论> [context-build-inventory.json]
77
+ ```
78
+
79
+ 没有证据时返回:
80
+
81
+ ```text
82
+ 缺口:本知识包没有包含 <缺失点> 的证据。
83
+ 已检查:<索引、页面或构建产物>。
84
+ 下一步来源:<已知时填写来源、页面或 source_ref>。
85
+ ```
86
+
87
+ “本包没有证据”不等于“事实不成立”。禁止使用模型记忆、旧对话或包外源码填补缺口。
88
+
89
+ ## 包边界
90
+
91
+ - 包内兼容 OKF 的根目录是本 Skill 的事实来源。
92
+ - 本包只包含 Context 工作区选择并批准的知识,不声明完整覆盖底层产品或代码库。
93
+ - 已有更窄索引范围可以回答时,不要扫描所有页面或关系边。
94
+
95
+ 已批准知识文件:`{{knowledgeCount}}`
96
+
97
+ ## 模板作者建议
98
+
99
+ 这是可直接工作的通用查询 Skill。正式发布前,如果知识包存在专用术语、常见用户意图、推荐入口、已知边界或固定任务流程,作者应修改 description、路由表和包边界说明;如果通用行为已经足够,应在 Context 的包模板 Review 中明确接受未修改模板。
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const PACKAGE_NAME = "{{packageName}}";
8
+ const STANDARD_ROOTS = ["wikis", "guides", "rules", "feats"];
9
+ const MAX_CHUNK_LINES = 80;
10
+ const CHUNK_OVERLAP = 5;
11
+
12
+ function usage() {
13
+ return [
14
+ "Search an approved knowledge package with deterministic BM25 ranking.",
15
+ "",
16
+ "Usage:",
17
+ " node search.mjs --query <text> [--root <package-root>] [--base <package-collection>] [--limit <n>] [--json]",
18
+ " node search.mjs <text> [--root <package-root>] [--base <package-collection>] [--limit <n>] [--json]",
19
+ "",
20
+ "The package root is detected from context-build-inventory.json when possible.",
21
+ ].join("\n");
22
+ }
23
+
24
+ function parseArgs(argv) {
25
+ const positional = [];
26
+ const options = { root: undefined, base: undefined, query: undefined, limit: 8, json: false };
27
+ for (let index = 0; index < argv.length; index++) {
28
+ const arg = argv[index];
29
+ if (arg === "--help" || arg === "-h") return { help: true };
30
+ if (arg === "--json") {
31
+ options.json = true;
32
+ continue;
33
+ }
34
+ if (arg === "--root" || arg === "--base" || arg === "--query" || arg === "--limit") {
35
+ const value = argv[++index];
36
+ if (value === undefined) throw new Error(`missing value for ${arg}`);
37
+ if (arg === "--root") options.root = value;
38
+ else if (arg === "--base") options.base = value;
39
+ else if (arg === "--query") options.query = value;
40
+ else options.limit = Number(value);
41
+ continue;
42
+ }
43
+ positional.push(arg);
44
+ }
45
+ if (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50) {
46
+ throw new Error("--limit must be an integer between 1 and 50");
47
+ }
48
+ options.query ??= positional.join(" ");
49
+ if (options.query.trim().length === 0) throw new Error("query must not be empty");
50
+ return options;
51
+ }
52
+
53
+ function isPackageRoot(directory) {
54
+ return existsSync(join(directory, "context-build-inventory.json"));
55
+ }
56
+
57
+ function ancestorPackageRoot(start) {
58
+ let current = resolve(start);
59
+ while (true) {
60
+ if (isPackageRoot(current)) return current;
61
+ const parent = dirname(current);
62
+ if (parent === current) return undefined;
63
+ current = parent;
64
+ }
65
+ }
66
+
67
+ function findInventories(directory, depth = 0) {
68
+ if (!existsSync(directory) || depth > 5) return [];
69
+ const inventory = join(directory, "context-build-inventory.json");
70
+ if (existsSync(inventory)) return [inventory];
71
+ const matches = [];
72
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
73
+ if (!entry.isDirectory()) continue;
74
+ matches.push(...findInventories(join(directory, entry.name), depth + 1));
75
+ }
76
+ return matches;
77
+ }
78
+
79
+ function inventoryPackageName(inventoryPath) {
80
+ try {
81
+ return JSON.parse(readFileSync(inventoryPath, "utf8"))?.package?.name;
82
+ } catch {
83
+ return undefined;
84
+ }
85
+ }
86
+
87
+ function resolvePackageRoot(explicitRoot, packageCollection) {
88
+ if (explicitRoot !== undefined) {
89
+ const root = resolve(explicitRoot);
90
+ if (!isPackageRoot(root)) throw new Error(`package root has no context-build-inventory.json: ${root}`);
91
+ return root;
92
+ }
93
+ const scriptRoot = ancestorPackageRoot(dirname(fileURLToPath(import.meta.url)));
94
+ if (scriptRoot !== undefined) return scriptRoot;
95
+ const cwdRoot = ancestorPackageRoot(process.cwd());
96
+ if (cwdRoot !== undefined) return cwdRoot;
97
+ if (packageCollection === undefined) {
98
+ throw new Error("cannot locate the knowledge package; pass --root <package-root> or --base <package-collection>");
99
+ }
100
+ const inventories = findInventories(resolve(packageCollection));
101
+ const matching = inventories.filter((path) => inventoryPackageName(path) === PACKAGE_NAME);
102
+ if (matching.length === 1) return dirname(matching[0]);
103
+ if (matching.length > 1) {
104
+ throw new Error(`multiple installed packages named ${PACKAGE_NAME}; pass --root explicitly`);
105
+ }
106
+ throw new Error("cannot locate the knowledge package; pass --root <package-root>");
107
+ }
108
+
109
+ function knowledgeRoots(packageRoot) {
110
+ const inventoryPath = join(packageRoot, "context-build-inventory.json");
111
+ let declared = [];
112
+ try {
113
+ const inventory = JSON.parse(readFileSync(inventoryPath, "utf8"));
114
+ declared = Object.values(inventory?.package?.distribution?.roots ?? {})
115
+ .filter((value) => typeof value === "string");
116
+ } catch {
117
+ declared = [];
118
+ }
119
+ return [...new Set([...declared, ...STANDARD_ROOTS])]
120
+ .map((root) => join(packageRoot, root))
121
+ .filter((root) => existsSync(root) && statSync(root).isDirectory());
122
+ }
123
+
124
+ function markdownFiles(directory) {
125
+ const files = [];
126
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
127
+ const path = join(directory, entry.name);
128
+ if (entry.isDirectory()) files.push(...markdownFiles(path));
129
+ else if (entry.isFile() && entry.name.endsWith(".md")) files.push(path);
130
+ }
131
+ return files;
132
+ }
133
+
134
+ function headingChunks(content) {
135
+ const lines = content.split(/\r?\n/u);
136
+ const starts = [];
137
+ for (let index = 0; index < lines.length; index++) {
138
+ if (/^#{1,6}\s+\S/u.test(lines[index] ?? "")) starts.push(index);
139
+ }
140
+ if (starts.length === 0) starts.push(0);
141
+ const chunks = [];
142
+ for (let headingIndex = 0; headingIndex < starts.length; headingIndex++) {
143
+ const start = starts[headingIndex] ?? 0;
144
+ const end = starts[headingIndex + 1] ?? lines.length;
145
+ const heading = (lines[start] ?? "").replace(/^#{1,6}\s+/u, "").trim() || "Document";
146
+ for (let offset = start; offset < end; offset += MAX_CHUNK_LINES - CHUNK_OVERLAP) {
147
+ const chunkEnd = Math.min(end, offset + MAX_CHUNK_LINES);
148
+ chunks.push({
149
+ heading,
150
+ startLine: offset + 1,
151
+ endLine: chunkEnd,
152
+ text: lines.slice(offset, chunkEnd).join("\n"),
153
+ });
154
+ if (chunkEnd === end) break;
155
+ }
156
+ }
157
+ return chunks;
158
+ }
159
+
160
+ function tokens(text) {
161
+ const normalized = text.toLocaleLowerCase();
162
+ const result = normalized.match(/[a-z0-9@._:/-]+|\p{Script=Han}+/gu) ?? [];
163
+ const expanded = [];
164
+ for (const token of result) {
165
+ expanded.push(token);
166
+ if (/^\p{Script=Han}+$/u.test(token)) {
167
+ for (let index = 0; index < token.length - 1; index++) expanded.push(token.slice(index, index + 2));
168
+ } else {
169
+ expanded.push(...token.split(/[^a-z0-9]+/u).filter((part) => part.length > 1));
170
+ }
171
+ }
172
+ return expanded.filter((token) => token.length > 0);
173
+ }
174
+
175
+ function termFrequency(items) {
176
+ const counts = new Map();
177
+ for (const item of items) counts.set(item, (counts.get(item) ?? 0) + 1);
178
+ return counts;
179
+ }
180
+
181
+ function preview(text, queryTerms, query) {
182
+ const lines = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
183
+ const loweredQuery = query.toLocaleLowerCase().trim();
184
+ const matching = [...lines].sort((left, right) => {
185
+ const leftText = left.toLocaleLowerCase();
186
+ const rightText = right.toLocaleLowerCase();
187
+ const lineScore = (value) =>
188
+ (value.includes(loweredQuery) ? queryTerms.length + 2 : 0)
189
+ + queryTerms.filter((term) => value.includes(term)).length;
190
+ return lineScore(rightText) - lineScore(leftText);
191
+ })[0];
192
+ return (matching ?? lines[0] ?? "").replace(/\s+/gu, " ").slice(0, 240);
193
+ }
194
+
195
+ function search(packageRoot, query, limit) {
196
+ const documents = [];
197
+ for (const root of knowledgeRoots(packageRoot)) {
198
+ for (const file of markdownFiles(root)) {
199
+ const path = relative(packageRoot, file).split("\\").join("/");
200
+ const content = readFileSync(file, "utf8");
201
+ for (const chunk of headingChunks(content)) {
202
+ const weightedText = `${path} ${chunk.heading} ${chunk.heading} ${chunk.text}`;
203
+ const chunkTokens = tokens(weightedText);
204
+ documents.push({ ...chunk, path, tokens: chunkTokens, tf: termFrequency(chunkTokens) });
205
+ }
206
+ }
207
+ }
208
+ const queryTerms = [...new Set(tokens(query))];
209
+ const documentFrequency = new Map();
210
+ for (const term of queryTerms) {
211
+ documentFrequency.set(term, documents.filter((document) => document.tf.has(term)).length);
212
+ }
213
+ const averageLength = documents.reduce((sum, document) => sum + document.tokens.length, 0)
214
+ / Math.max(documents.length, 1);
215
+ const loweredQuery = query.toLocaleLowerCase().trim();
216
+ const scored = documents.map((document) => {
217
+ let score = 0;
218
+ for (const term of queryTerms) {
219
+ const frequency = document.tf.get(term) ?? 0;
220
+ if (frequency === 0) continue;
221
+ const matches = documentFrequency.get(term) ?? 0;
222
+ const idf = Math.log(1 + (documents.length - matches + 0.5) / (matches + 0.5));
223
+ const denominator = frequency + 1.2 * (0.25 + 0.75 * document.tokens.length / Math.max(averageLength, 1));
224
+ score += idf * frequency * 2.2 / denominator;
225
+ }
226
+ const searchable = `${document.path}\n${document.heading}\n${document.text}`.toLocaleLowerCase();
227
+ if (searchable.includes(loweredQuery)) score += 4;
228
+ if (`${document.path} ${document.heading}`.toLocaleLowerCase().includes(loweredQuery)) score += 2;
229
+ return { ...document, score };
230
+ }).filter((document) => document.score > 0);
231
+ scored.sort((left, right) =>
232
+ right.score - left.score
233
+ || left.path.localeCompare(right.path)
234
+ || left.startLine - right.startLine
235
+ );
236
+ return scored.slice(0, limit).map((document) => ({
237
+ score: Number(document.score.toFixed(4)),
238
+ path: document.path,
239
+ heading: document.heading,
240
+ start_line: document.startLine,
241
+ end_line: document.endLine,
242
+ preview: preview(document.text, queryTerms, query),
243
+ }));
244
+ }
245
+
246
+ try {
247
+ const options = parseArgs(process.argv.slice(2));
248
+ if (options.help) {
249
+ process.stdout.write(`${usage()}\n`);
250
+ process.exit(0);
251
+ }
252
+ const root = resolvePackageRoot(options.root, options.base);
253
+ const results = search(root, options.query, options.limit);
254
+ if (options.json) {
255
+ process.stdout.write(`${JSON.stringify({ package: PACKAGE_NAME, root, query: options.query, results }, null, 2)}\n`);
256
+ } else if (results.length === 0) {
257
+ process.stdout.write(`No matching knowledge found for: ${options.query}\n`);
258
+ } else {
259
+ for (const [index, result] of results.entries()) {
260
+ process.stdout.write(`${index + 1}. ${result.path}:${result.start_line}-${result.end_line} [${result.score}] ${result.heading}\n`);
261
+ process.stdout.write(` ${result.preview}\n`);
262
+ }
263
+ }
264
+ } catch (error) {
265
+ process.stderr.write(`knowledge-search: ${error instanceof Error ? error.message : String(error)}\n`);
266
+ process.stderr.write(`${usage()}\n`);
267
+ process.exit(2);
268
+ }
@@ -0,0 +1,39 @@
1
+ ---
2
+ type: Knowledge Bundle
3
+ title: "{{displayName}}"
4
+ description: "由 Context 工作区生成的已批准知识包。"
5
+ tags:
6
+ - context
7
+ - knowledge-base
8
+ timestamp: "{{knowledgeTimestamp}}"
9
+ resource: "context://package/{{packageName}}/{{wikisRoot}}"
10
+ package: "{{packageName}}"
11
+ package_kind: "{{packageKind}}"
12
+ knowledge_count: {{knowledgeCount}}
13
+ ---
14
+
15
+ <!-- context:template
16
+ 这是一个起始模板。`context build` 渲染后会将其复制到
17
+ `dist/<package-name>/{{wikisRoot}}/index.md`。
18
+
19
+ 以 context:template 开头的模板注释不会进入构建产物。
20
+ 修改前请阅读模板变量手册:
21
+ node_modules/@c4a/context/docs/reference/template-variables.md
22
+
23
+ 正式发布前,应补充知识包范围、目标读者、推荐阅读顺序、已知缺口、使用场景或任务入口。
24
+ 小目录会直接列出内容,超过导航阈值时会链接到子目录 index.md。
25
+ -->
26
+
27
+ # {{displayName}}
28
+
29
+ 本知识包包含 `{{knowledgeCount}}` 个已批准知识页面。请先使用本索引定位范围,再打开具体知识页获取可追溯来源的细节。
30
+
31
+ ## 内容
32
+
33
+ {{knowledgeGroupsMarkdown}}
34
+
35
+ ## 使用方式
36
+
37
+ - 从上方导航开始,再进入知识页或子目录索引。
38
+ - 安装为 Agent 知识包后,可以使用随包提供的 knowledge-query Skill。
39
+ - 如果包需要专用范围、已知缺口、阅读路径或任务入口,作者应替换或编辑本通用索引;否则应在 Context 包模板 Review 中明确接受未修改模板。
@@ -0,0 +1,8 @@
1
+ # {{packageName}}
2
+
3
+ 由 Context 生成。
4
+
5
+ 包类型:{{packageKind}}
6
+ 已批准知识文件:{{knowledgeCount}}
7
+
8
+ {{knowledge}}
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: maintain-project-knowledge
3
+ description: Maintain a project-local Context knowledge workspace by checking registered repository and document sources, selecting the affected project scope, and handing lifecycle actions to the installed Context Skill. Customize the name, description, project facts, and impact map before using this template in a real project.
4
+ ---
5
+
6
+ # Maintain project knowledge
7
+
8
+ Use this file as a project adapter, not as a copy of the Context workflow. Read
9
+ the installed Context Skill first and treat its current Route, resources, and
10
+ commands as authoritative. Keep only stable project facts and source-impact
11
+ mappings here.
12
+
13
+ ## Repository sources
14
+
15
+ Start with the current Context Route. If registered repository modules are
16
+ missing or their local links are broken, follow the route-selected repository
17
+ recovery procedure. Let the user choose an existing checkout, authorize a scan
18
+ inside one bounded directory, or explicitly clone the registered pinned commit.
19
+
20
+ When a checkout already exists, inspect its origin, HEAD, dirty state, and
21
+ registered subpaths before asking whether to keep the recorded commit or update
22
+ to another explicit commit. Never pull, switch, clean, or reset an external
23
+ checkout without separate authority.
24
+
25
+ Repository sources are ready for knowledge work only when every selected module
26
+ resolves to its registered remote and commit, all required subpaths exist,
27
+ unknown local changes are preserved, Context has materialized its module links,
28
+ and the current Route no longer reports a repository-readiness blocker.
29
+
30
+ Add the project's stable mapping from repository changes to declared extraction
31
+ scope here. Do not infer this mapping from filenames during a maintenance run.
32
+
33
+ ## Remote document sources
34
+
35
+ Use the current Context Route and the installed document-provider Skills or CLI
36
+ to read registered remote documents. Keep this section short: provider login,
37
+ permissions, capture, sub-page traversal, and fidelity diagnostics belong to
38
+ the provider and Context resources selected for the current step.
39
+
40
+ A remote document is ready for knowledge work only when its registered identity
41
+ is unchanged unless the user explicitly adds a new source, the current account
42
+ can read the required body and assets, relevant child pages are represented as
43
+ their own sources, capture fidelity has no blocking error, and the current Route
44
+ allows structure or extraction work to begin.
45
+
46
+ Add project-specific source ownership, expected document families, and update
47
+ boundaries here. Do not copy credentials, source bodies, or volatile command
48
+ lines into this Skill.
49
+
50
+ ## Project facts and completion
51
+
52
+ Record only durable project paths, declared package outputs, physical-to-logical
53
+ repository grouping, and evidence-backed impact rules. Do not duplicate Context
54
+ phase commands or manually edit lifecycle-owned directories.
55
+
56
+ Report completion using the current Context Route and build receipt, including
57
+ the checked source range, changed and unchanged knowledge, unresolved permissions
58
+ or fidelity blockers, and whether a package was built or separately published.
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: maintain-project-knowledge
3
+ description: 维护项目内的 Context 知识生产工作区;用于检查已登记仓库和文档来源、判断项目影响范围,并把生命周期动作交回已安装的 Context Skill。用于真实项目前,请修改名称、描述、项目事实和影响映射。
4
+ ---
5
+
6
+ # 维护项目知识
7
+
8
+ 本 Skill 是项目适配层,不复制 Context 工作流。先读取已安装的 Context
9
+ Skill,并以当前 Route、资源和命令为权威;这里只保留稳定的项目事实和来源
10
+ 影响映射。
11
+
12
+ ## 仓库来源的获取和更新
13
+
14
+ 先检查当前 Context Route。已登记仓库模块缺失或本地链接断开时,使用 Route
15
+ 选择的仓库恢复流程,让用户选择已有 checkout、授权在一个限定目录内扫描,
16
+ 或明确允许 clone 已登记的固定 commit。
17
+
18
+ checkout 已存在时,先检查 origin、HEAD、dirty 状态和已登记 subpath,再询问
19
+ 用户保持当前登记版本,还是更新到另一个明确 commit。没有单独授权时,不得对
20
+ 外部 checkout 执行 pull、切换、清理或 reset。
21
+
22
+ 只有当每个目标模块都对应已登记 remote 和 commit、全部 subpath 存在、未知
23
+ 本地修改得到保护、Context 已物化模块链接,并且当前 Route 不再报告仓库未
24
+ 就绪时,仓库来源才可以进入知识更新。
25
+
26
+ 在这里补充本项目稳定的“仓库变化 → 提取范围”映射。维护过程中不得仅根据
27
+ 文件名临时猜测影响关系。
28
+
29
+ ## 远程文档来源的获取和更新
30
+
31
+ 通过当前 Context Route 以及宿主安装的文档提供方 Skill 或 CLI 读取已登记
32
+ 文档。本节保持简短:登录、权限、采集、子页遍历和保真诊断由当前步骤选择的
33
+ 提供方与 Context 资源负责。
34
+
35
+ 只有当来源身份保持稳定(除非用户明确新增来源)、当前账号能够读取所需正文
36
+ 和资源、需要正文的子页已作为独立来源处理、采集不存在阻塞性 fidelity error,
37
+ 并且当前 Route 允许开始结构或提取工作时,远程文档才可以进入知识更新。
38
+
39
+ 在这里补充本项目的来源归属、预期文档类型和更新边界。不要把凭据、来源正文
40
+ 或易漂移的完整命令写进 Skill。
41
+
42
+ ## 项目事实与完成标准
43
+
44
+ 只记录长期稳定的项目路径、包输出、物理仓库与逻辑来源分组,以及有证据的影响
45
+ 规则。不得复制 Context 阶段命令,也不得手工修改生命周期托管目录。
46
+
47
+ 以当前 Context Route 和构建回执报告完成情况,说明检查范围、知识新增/更新/
48
+ 无变化、尚未解决的权限或保真问题,以及知识包是仅完成构建还是已经另行发布。