@c4a/context 0.6.1 → 0.6.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.
@@ -1,208 +1,132 @@
1
1
  ---
2
- name: knowledge-query
3
- description: Query the approved knowledge bundled with {{displayName}}.
2
+ name: {{skillName}}
3
+ description: Query the approved, source-linked knowledge bundled with {{displayName}}. Use for questions about included entities, APIs, behavior, procedures, constraints, decisions, troubleshooting, relationships, and package coverage.
4
4
  ---
5
5
 
6
6
  # Knowledge Query
7
7
 
8
- Use this starter skill when the user asks about APIs, components, usage patterns,
9
- migration details, relationships, or source-linked facts covered by this
10
- package. Customize it before build if the package needs project-specific
11
- routing, terminology, or task workflows.
8
+ Answer from the approved knowledge bundled with this package. Navigate through
9
+ its indexes before opening individual pages, and treat the visible section body
10
+ as evidence rather than relying on memory or frontmatter summaries.
11
+
12
+ ## Query Procedure
13
+
14
+ 1. Classify the request as entity lookup, explanation, procedure, rule,
15
+ relationship, detail, or coverage audit.
16
+ 2. Open the most relevant root index, such as `{{wikisRoot}}/index.md`, then
17
+ follow its links to a likely page.
18
+ 3. Read only the sections needed for the question. Use frontmatter to select
19
+ scope, not to support factual claims.
20
+ 4. For relationship or impact questions, inspect
21
+ `context-build-inventory.json` `structure.edge_records` before reading the
22
+ endpoint pages.
23
+ 5. Answer only from reader-visible section content and source-backed edge
24
+ records. Cite the supporting page or section.
25
+ 6. If the package lacks evidence, report the gap and what was checked instead
26
+ of inferring from nearby content.
27
+
28
+ ## Package Roots
29
+
30
+ | Root | Use |
31
+ |---|---|
32
+ | `{{wikisRoot}}/` | Structured entities and relationships from codegraph, business, and product knowledge. |
33
+ | `{{guidesRoot}}/` | Architecture, procedures, FAQs, decisions, incidents, and troubleshooting. |
34
+ | `{{rulesRoot}}/` | Standards, constraints, acceptance criteria, and test scenarios. |
35
+ | `{{featsRoot}}/` | Feature knowledge when selected into the package. |
12
36
 
13
- ## Non-negotiables
37
+ Start from the root that matches the request. A missing root means that category
38
+ was not selected into this package.
14
39
 
15
- - Structure first: start from OKF indexes and package structure before opening
16
- individual pages.
17
- - Bundled knowledge pages are evidence cards. Do not answer from memory when a
18
- copied page exists.
19
- - Cite every substantive claim with a page path and, when available, a
20
- `context:section` id/source_ref.
21
- - Frontmatter summaries help route the query; reader-visible section content is
22
- the evidence for factual answers.
23
- - If the package does not contain evidence for the requested fact, report a gap
24
- instead of inferring from adjacent pages.
25
- - Do not treat direct grep over bundled OKF root directories as the primary
26
- discovery path. Use it only as a fallback after indexes and page structure do
27
- not identify a scope.
40
+ ## Route By Intent
28
41
 
29
- ## Evidence Shape
42
+ | Intent | First move |
43
+ |---|---|
44
+ | Vague topic or unknown name | Open the likely root index and choose a page from its grouping. |
45
+ | Named entity, API, domain, or action | Open the matching page or nearest group index. Show candidates if names are ambiguous. |
46
+ | Architecture, procedure, FAQ, decision, or incident | Start from `{{guidesRoot}}/index.md`. |
47
+ | Standard, constraint, acceptance, or test question | Start from `{{rulesRoot}}/index.md`. |
48
+ | Relationship or impact | Inspect typed edges, then read both endpoint pages. |
49
+ | Detail inside a known page | Read the relevant `context:section` block. |
50
+ | Coverage, gap, or inventory | Inspect the root indexes and `context-build-inventory.json`. |
30
51
 
31
- Treat each opened knowledge page or package artifact as an evidence card:
52
+ ## Evidence Contract
32
53
 
33
- | Evidence field | Use |
34
- |---|---|
35
- | Page path | Citation handle and package-local identity. |
36
- | Frontmatter `title` / `description` / `node_type` / `tags` | Navigation and scope selection; not enough by itself for factual claims. |
37
- | `context:section` id / kind / source_ref metadata | Section citation handle and evidence boundary. |
38
- | Reader-visible section body | Primary evidence for factual answers. |
39
- | `context-build-inventory.json` `structure.edge_records` | Package-visible relationship evidence when typed edges are selected into this package. |
40
- | `context-build-inventory.json` | Package scope, counts, selected-by reasons, edge records, and build-time visibility evidence. |
41
-
42
- Frontmatter and indexes orient the search; reader-visible section content and
43
- source-backed edge records support claims. If a source_ref points to evidence
44
- that is not bundled, cite the approved knowledge section that reviewed it and avoid expanding
45
- beyond the packaged content.
54
+ Use each opened page or package artifact as an evidence card:
46
55
 
47
- ## Route By Intent
56
+ | Evidence | Valid use |
57
+ |---|---|
58
+ | Page path | Page identity and citation handle. |
59
+ | Frontmatter title, description, stable node identity, and tags | Navigation and scope selection only. |
60
+ | `context:section` id, kind, and `source_ref` | Section identity, citation, and source boundary. |
61
+ | Reader-visible section body | Primary support for factual claims. |
62
+ | `context-build-inventory.json` edge records | Typed relationship evidence. |
63
+ | Root indexes and build inventory | Package scope and coverage evidence. |
48
64
 
49
- | User intent | First move | Evidence move |
50
- |---|---|---|
51
- | Vague topic or unknown name | Open the package's OKF root index, usually `wikis/index.md`, then child indexes such as `wikis/<group>/index.md`. If the package includes other selected OKF roots such as `guides/` or `rules/`, use their indexes too. | Choose candidate pages by title, path, frontmatter, and index grouping. |
52
- | Architecture, procedure, FAQ, decision, incident, or troubleshooting question | Start from `guides/index.md` when present. These pages are mapped from internal `architecture`, `sop`, `faq`, `decision`, and `incident` collections. | Use guide pages for explanations, design narratives, decisions, steps, operational context, and troubleshooting; cite the relevant sections. |
53
- | Structured product or business question | Start from `wikis/index.md`, then the `product` or `business` group index. | Use entity pages and typed relationships to establish scope before reading supporting narratives. |
54
- | Standard, constraint, acceptance, or test scenario question | Start from `rules/index.md` when present. These pages are mapped from internal `standards` and `test` collections. | Use rule pages for normative constraints, acceptance criteria, and validation scenarios. |
55
- | Specific entity/domain/action named | Open the matching page or nearest group index. | Read the page sections and source metadata. |
56
- | Relationship or impact question | Check `context-build-inventory.json` `structure.edge_records`, then related endpoint pages. | Cite typed edge evidence if available; otherwise cite page sections and mark relation gaps. |
57
- | Detail within a known page | Read that page's relevant `context:section` block. | Cite the section id/source_ref and quote or summarize only supported text. |
58
- | Coverage, gaps, or package scope | Check indexes and `context-build-inventory.json`. | Cite those package artifacts plus relevant pages. |
59
-
60
- Classify the user's question before opening pages. Structure queries take
61
- priority over keyword search:
62
-
63
- - Vague problem: show the relevant package structure first, then choose or ask
64
- for a page/group scope. Do not answer from a package-wide text hit before
65
- choosing the likely Node/page.
66
- - Explicit Node/page name: open the matching page or nearest group index. If
67
- several pages normalize to the same name, show the candidates instead of
68
- guessing.
69
- - Relationship/impact: inspect typed edges first, then read both endpoint
70
- pages. If an edge is absent, page co-occurrence is not relationship evidence.
71
- - Detail inside a chosen scope: open only the relevant page sections. Use
72
- search only to locate the section inside that page or group.
73
- - Coverage/gap/audit: use package artifacts first, then supporting pages.
74
-
75
- If the package structure returns multiple plausible pages, ask the user to pick
76
- or state the selection basis before answering. If no page or edge supports the
77
- requested fact, report a gap; do not broaden search until something vaguely
78
- matches.
79
-
80
- ## Workflow
81
-
82
- 1. Start with the relevant OKF root index, usually `wikis/index.md`, to
83
- understand the package scope.
84
- 2. Follow OKF directory indexes such as `wikis/<group>/index.md` before opening
85
- pages. If the package includes other selected OKF roots, inspect their
86
- indexes the same way.
87
- 3. Open only the candidate pages needed for the question; avoid workspace-wide
88
- reading unless the user asks for an inventory.
89
- 4. Inspect frontmatter `node_type`, `sources`, and `context:section` comments to
90
- confirm evidence boundaries.
91
- 5. Answer from reader-visible section content. Use source_ref metadata as
92
- citation and freshness context, not as a license to invent missing facts.
93
- 6. Cite source-linked facts from the knowledge page when the answer depends on
94
- code or reviewed knowledge.
95
- 7. If the indexes and matching pages do not cover the request, return a
96
- structured gap: requested topic, indexes/pages checked, and missing source
97
- span or source_ref.
98
-
99
- For follow-up exploration, stay within the package boundary:
100
-
101
- - User asks about dependencies or impact: inspect `context-build-inventory.json`
102
- `structure.edge_records` and the endpoint pages.
103
- - User asks for full detail after a partial answer: continue within the same
104
- page or its child index before expanding outward.
105
- - User asks for a specific feature or behavior: use exact terminology,
106
- bilingual terms, product aliases, API names, flags, and error strings from the
107
- question to locate a section, then answer from the section body.
108
- - User mentions another page through a referenced edge or section link: open it
109
- only if the relationship question requires it or the user asks to drill down.
110
- - Do not auto-open every related page, edge endpoint, or search hit just because
111
- it is present.
65
+ Do not infer a relationship from page co-occurrence. If a `source_ref` points to
66
+ source material that is not bundled, cite the approved section that reviewed it
67
+ and do not expand beyond its visible content.
112
68
 
113
69
  ## Search Fallback
114
70
 
115
- Use text search only after the structure path above fails to identify a scope.
71
+ Search only when indexes and page structure do not identify a useful scope, or
72
+ when a candidate page is too large to read in full. Use `rg` for an exact name,
73
+ API, configuration key, path, or error string. For multiple terms, Chinese
74
+ phrases, or several large indexes, run the bundled
75
+ [`scripts/search.mjs`](scripts/search.mjs) BM25 ranker:
116
76
 
117
- - Search with exact product names, API names, bilingual terms, aliases, and
118
- error strings from the user's question.
119
- - Prefer scoped search under a likely group/page over a package-wide scan.
120
- - A search hit is only a lead. Open the containing page/section and answer from
121
- the section body, not from the hit line alone.
122
- - If search returns only unrelated pages, report the gap instead of broadening
123
- until something vaguely matches.
124
-
125
- Search is keyword-literal, so use deliberate terms:
77
+ ```bash
78
+ node <current knowledge-query Skill directory>/scripts/search.mjs --query '<terms>' --limit 8
79
+ ```
126
80
 
127
- - mix Chinese and English names when both may appear in the package;
128
- - include version numbers, API names, config keys, command flags, and exact
129
- error strings when the user provides them;
130
- - try aliases and synonyms only to locate a candidate page, not to justify a
131
- claim;
132
- - after a search hit, return to structure: identify the page, section, and
133
- evidence boundary before answering.
81
+ The script locates `{{packageName}}` from a raw package or TTKB installation.
82
+ If discovery is ambiguous, add `--root <package directory containing
83
+ context-build-inventory.json>`. It chunks Markdown mechanically by headings and
84
+ bounded line ranges, then returns paths, line ranges, headings, and previews; it
85
+ does not interpret meaning.
134
86
 
135
- Do not use search for relationship or impact claims when a typed edge is
136
- available. Relationship answers should cite the edge first; search can only
137
- find endpoint details.
87
+ Treat every hit as a lead and open its page and section before answering. For
88
+ relationship or impact claims, use typed edges from
89
+ `context-build-inventory.json`; BM25 scores and text co-occurrence are not
90
+ relationship evidence.
138
91
 
139
- ## Citation Shape
92
+ ## Citations And Gaps
140
93
 
141
- Use compact citations keyed to package artifacts:
94
+ Use compact citations tied to claims:
142
95
 
143
96
  ```text
144
- Page identity/title: <claim> [<okf-root>/path/page.md]
145
- Section claim: <claim> [<okf-root>/path/page.md#section-id]
146
- Source-backed detail: <claim> [<okf-root>/path/page.md#section-id, source_ref]
147
- Relationship/edge: <claim> [context-build-inventory.json#structure.edge_records edge:<type>]
148
- Package coverage/gap: <claim> [context-build-inventory.json]
97
+ Page: <claim> [<root>/path/page.md]
98
+ Section: <claim> [<root>/path/page.md#section-id]
99
+ Source-bound: <claim> [<root>/path/page.md#section-id, source_ref]
100
+ Relationship: <claim> [context-build-inventory.json#structure.edge_records edge:<type>]
101
+ Coverage: <claim> [context-build-inventory.json]
149
102
  ```
150
103
 
151
- If several sections support the same claim, cite the strongest one or two and
152
- explain what each supports. Do not list citations without tying them to claims.
104
+ When evidence is missing, return:
153
105
 
154
- For relationship answers, cite the typed edge when present and then cite endpoint
155
- sections for the facts about each endpoint. If no edge exists, do not infer a relationship from co-occurrence; say that the package lacks a source-backed relationship edge and cite any weaker page-level evidence separately.
156
-
157
- Use citation scope conservatively:
106
+ ```text
107
+ Gap: this package does not contain evidence for <missing point>.
108
+ Checked: <indexes, pages, or artifacts>.
109
+ Next useful source: <source, page, or source_ref if known>.
110
+ ```
158
111
 
159
- - Page identity/title claims can cite the page path.
160
- - Factual behavior, constraints, examples, warnings, and usage claims need a
161
- `context:section` citation when available.
162
- - Package coverage or known-limit claims need `context-build-inventory.json`
163
- and/or the checked indexes/pages.
164
- - `source_ref` metadata names the reviewed evidence boundary, but the source
165
- body may not be bundled. Do not invent source text that is not present in the
166
- packaged knowledge page.
112
+ Distinguish “not evidenced by this package” from “not true.” Do not fill gaps
113
+ from memory, previous conversations, or source files outside the package.
167
114
 
168
- ## Gap Shape
115
+ ## Package Boundary
169
116
 
170
- When the package lacks evidence, say so explicitly:
117
+ - The bundled OKF-compatible roots are the source of truth for this Skill.
118
+ - The package contains approved knowledge selected by its Context workspace; it
119
+ does not claim complete coverage of the underlying product or codebase.
120
+ - Do not scan every page or edge when a narrower indexed scope answers the
121
+ request.
171
122
 
172
- ```text
173
- Gap: this package did not return evidence for <missing point>.
174
- Checked: <indexes/pages/artifacts>.
175
- Next useful source need: <source, page, or source_ref if known>.
176
- ```
123
+ Approved knowledge files: `{{knowledgeCount}}`
177
124
 
178
- Do not fill the gap from general product knowledge, previous conversations, or
179
- raw source files outside this package.
180
-
181
- If the likely cause is that source material was captured but not approved into
182
- this package, say that the packaged knowledge does not include it. Do not tell
183
- the user it is false; distinguish "not evidenced here" from "not true."
184
-
185
- ## Knowledge Boundary
186
-
187
- - The bundled OKF root directories, usually including `wikis/`, are the source
188
- of truth for this skill.
189
- - OKF root mapping: `wikis/` maps from the structured `codegraph`, `business`,
190
- and `product` collections; `guides/` maps from `architecture`, `sop`, `faq`,
191
- `decision`, and `incident`; `rules/` maps from `standards` and `test`;
192
- `feats/` maps from `feats`.
193
- - Treat `wikis/` as the primary entity-and-relationship layer. Guides and rules
194
- may explain, operationalize, or constrain that structured knowledge, but
195
- directory co-location alone is not relationship evidence.
196
- - Bundled OKF root directories follow the C4A OKF Profile.
197
- - Prefer OKF indexes, `context-build-inventory.json`, package manifests when
198
- present, build inventory, and page source span metadata over raw text search.
199
- - Do not rely on memory when a knowledge page exists.
200
- - Do not claim the package is complete; it only contains approved knowledge that
201
- was selected by the Context workspace.
202
- - If the package lacks project-specific instructions, state that the default
203
- skill is a generic query entry and report gaps instead of inventing workflow
204
- guidance.
205
-
206
- ## Included Knowledge
125
+ ## Template Author Recommendation
207
126
 
208
- Approved knowledge files: `{{knowledgeCount}}`
127
+ This is a complete generic query Skill, but package authors should replace or
128
+ edit it before publishing when the package has project-specific terminology,
129
+ common user intents, preferred entry pages, known limits, or task workflows.
130
+ Update the description, routing table, and package-boundary guidance to match
131
+ the actual package. If the generic behavior is intentionally sufficient,
132
+ explicitly accept the unchanged default during Context package-template review.
@@ -0,0 +1,264 @@
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>] [--limit <n>] [--json]",
18
+ " node search.mjs <text> [--root <package-root>] [--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, 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 === "--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 === "--query") options.query = value;
39
+ else options.limit = Number(value);
40
+ continue;
41
+ }
42
+ positional.push(arg);
43
+ }
44
+ if (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50) {
45
+ throw new Error("--limit must be an integer between 1 and 50");
46
+ }
47
+ options.query ??= positional.join(" ");
48
+ if (options.query.trim().length === 0) throw new Error("query must not be empty");
49
+ return options;
50
+ }
51
+
52
+ function isPackageRoot(directory) {
53
+ return existsSync(join(directory, "context-build-inventory.json"));
54
+ }
55
+
56
+ function ancestorPackageRoot(start) {
57
+ let current = resolve(start);
58
+ while (true) {
59
+ if (isPackageRoot(current)) return current;
60
+ const parent = dirname(current);
61
+ if (parent === current) return undefined;
62
+ current = parent;
63
+ }
64
+ }
65
+
66
+ function findInventories(directory, depth = 0) {
67
+ if (!existsSync(directory) || depth > 5) return [];
68
+ const inventory = join(directory, "context-build-inventory.json");
69
+ if (existsSync(inventory)) return [inventory];
70
+ const matches = [];
71
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
72
+ if (!entry.isDirectory()) continue;
73
+ matches.push(...findInventories(join(directory, entry.name), depth + 1));
74
+ }
75
+ return matches;
76
+ }
77
+
78
+ function inventoryPackageName(inventoryPath) {
79
+ try {
80
+ return JSON.parse(readFileSync(inventoryPath, "utf8"))?.package?.name;
81
+ } catch {
82
+ return undefined;
83
+ }
84
+ }
85
+
86
+ function resolvePackageRoot(explicitRoot) {
87
+ if (explicitRoot !== undefined) {
88
+ const root = resolve(explicitRoot);
89
+ if (!isPackageRoot(root)) throw new Error(`package root has no context-build-inventory.json: ${root}`);
90
+ return root;
91
+ }
92
+ const scriptRoot = ancestorPackageRoot(dirname(fileURLToPath(import.meta.url)));
93
+ if (scriptRoot !== undefined) return scriptRoot;
94
+ const cwdRoot = ancestorPackageRoot(process.cwd());
95
+ if (cwdRoot !== undefined) return cwdRoot;
96
+ const inventories = findInventories(join(process.cwd(), ".ttkb", "packages"));
97
+ const matching = inventories.filter((path) => inventoryPackageName(path) === PACKAGE_NAME);
98
+ if (matching.length === 1) return dirname(matching[0]);
99
+ if (matching.length > 1) {
100
+ throw new Error(`multiple installed packages named ${PACKAGE_NAME}; pass --root explicitly`);
101
+ }
102
+ throw new Error("cannot locate the knowledge package; pass --root <package-root>");
103
+ }
104
+
105
+ function knowledgeRoots(packageRoot) {
106
+ const inventoryPath = join(packageRoot, "context-build-inventory.json");
107
+ let declared = [];
108
+ try {
109
+ const inventory = JSON.parse(readFileSync(inventoryPath, "utf8"));
110
+ declared = Object.values(inventory?.package?.distribution?.roots ?? {})
111
+ .filter((value) => typeof value === "string");
112
+ } catch {
113
+ declared = [];
114
+ }
115
+ return [...new Set([...declared, ...STANDARD_ROOTS])]
116
+ .map((root) => join(packageRoot, root))
117
+ .filter((root) => existsSync(root) && statSync(root).isDirectory());
118
+ }
119
+
120
+ function markdownFiles(directory) {
121
+ const files = [];
122
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
123
+ const path = join(directory, entry.name);
124
+ if (entry.isDirectory()) files.push(...markdownFiles(path));
125
+ else if (entry.isFile() && entry.name.endsWith(".md")) files.push(path);
126
+ }
127
+ return files;
128
+ }
129
+
130
+ function headingChunks(content) {
131
+ const lines = content.split(/\r?\n/u);
132
+ const starts = [];
133
+ for (let index = 0; index < lines.length; index++) {
134
+ if (/^#{1,6}\s+\S/u.test(lines[index] ?? "")) starts.push(index);
135
+ }
136
+ if (starts.length === 0) starts.push(0);
137
+ const chunks = [];
138
+ for (let headingIndex = 0; headingIndex < starts.length; headingIndex++) {
139
+ const start = starts[headingIndex] ?? 0;
140
+ const end = starts[headingIndex + 1] ?? lines.length;
141
+ const heading = (lines[start] ?? "").replace(/^#{1,6}\s+/u, "").trim() || "Document";
142
+ for (let offset = start; offset < end; offset += MAX_CHUNK_LINES - CHUNK_OVERLAP) {
143
+ const chunkEnd = Math.min(end, offset + MAX_CHUNK_LINES);
144
+ chunks.push({
145
+ heading,
146
+ startLine: offset + 1,
147
+ endLine: chunkEnd,
148
+ text: lines.slice(offset, chunkEnd).join("\n"),
149
+ });
150
+ if (chunkEnd === end) break;
151
+ }
152
+ }
153
+ return chunks;
154
+ }
155
+
156
+ function tokens(text) {
157
+ const normalized = text.toLocaleLowerCase();
158
+ const result = normalized.match(/[a-z0-9@._:/-]+|\p{Script=Han}+/gu) ?? [];
159
+ const expanded = [];
160
+ for (const token of result) {
161
+ expanded.push(token);
162
+ if (/^\p{Script=Han}+$/u.test(token)) {
163
+ for (let index = 0; index < token.length - 1; index++) expanded.push(token.slice(index, index + 2));
164
+ } else {
165
+ expanded.push(...token.split(/[^a-z0-9]+/u).filter((part) => part.length > 1));
166
+ }
167
+ }
168
+ return expanded.filter((token) => token.length > 0);
169
+ }
170
+
171
+ function termFrequency(items) {
172
+ const counts = new Map();
173
+ for (const item of items) counts.set(item, (counts.get(item) ?? 0) + 1);
174
+ return counts;
175
+ }
176
+
177
+ function preview(text, queryTerms, query) {
178
+ const lines = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
179
+ const loweredQuery = query.toLocaleLowerCase().trim();
180
+ const matching = [...lines].sort((left, right) => {
181
+ const leftText = left.toLocaleLowerCase();
182
+ const rightText = right.toLocaleLowerCase();
183
+ const lineScore = (value) =>
184
+ (value.includes(loweredQuery) ? queryTerms.length + 2 : 0)
185
+ + queryTerms.filter((term) => value.includes(term)).length;
186
+ return lineScore(rightText) - lineScore(leftText);
187
+ })[0];
188
+ return (matching ?? lines[0] ?? "").replace(/\s+/gu, " ").slice(0, 240);
189
+ }
190
+
191
+ function search(packageRoot, query, limit) {
192
+ const documents = [];
193
+ for (const root of knowledgeRoots(packageRoot)) {
194
+ for (const file of markdownFiles(root)) {
195
+ const path = relative(packageRoot, file).split("\\").join("/");
196
+ const content = readFileSync(file, "utf8");
197
+ for (const chunk of headingChunks(content)) {
198
+ const weightedText = `${path} ${chunk.heading} ${chunk.heading} ${chunk.text}`;
199
+ const chunkTokens = tokens(weightedText);
200
+ documents.push({ ...chunk, path, tokens: chunkTokens, tf: termFrequency(chunkTokens) });
201
+ }
202
+ }
203
+ }
204
+ const queryTerms = [...new Set(tokens(query))];
205
+ const documentFrequency = new Map();
206
+ for (const term of queryTerms) {
207
+ documentFrequency.set(term, documents.filter((document) => document.tf.has(term)).length);
208
+ }
209
+ const averageLength = documents.reduce((sum, document) => sum + document.tokens.length, 0)
210
+ / Math.max(documents.length, 1);
211
+ const loweredQuery = query.toLocaleLowerCase().trim();
212
+ const scored = documents.map((document) => {
213
+ let score = 0;
214
+ for (const term of queryTerms) {
215
+ const frequency = document.tf.get(term) ?? 0;
216
+ if (frequency === 0) continue;
217
+ const matches = documentFrequency.get(term) ?? 0;
218
+ const idf = Math.log(1 + (documents.length - matches + 0.5) / (matches + 0.5));
219
+ const denominator = frequency + 1.2 * (0.25 + 0.75 * document.tokens.length / Math.max(averageLength, 1));
220
+ score += idf * frequency * 2.2 / denominator;
221
+ }
222
+ const searchable = `${document.path}\n${document.heading}\n${document.text}`.toLocaleLowerCase();
223
+ if (searchable.includes(loweredQuery)) score += 4;
224
+ if (`${document.path} ${document.heading}`.toLocaleLowerCase().includes(loweredQuery)) score += 2;
225
+ return { ...document, score };
226
+ }).filter((document) => document.score > 0);
227
+ scored.sort((left, right) =>
228
+ right.score - left.score
229
+ || left.path.localeCompare(right.path)
230
+ || left.startLine - right.startLine
231
+ );
232
+ return scored.slice(0, limit).map((document) => ({
233
+ score: Number(document.score.toFixed(4)),
234
+ path: document.path,
235
+ heading: document.heading,
236
+ start_line: document.startLine,
237
+ end_line: document.endLine,
238
+ preview: preview(document.text, queryTerms, query),
239
+ }));
240
+ }
241
+
242
+ try {
243
+ const options = parseArgs(process.argv.slice(2));
244
+ if (options.help) {
245
+ process.stdout.write(`${usage()}\n`);
246
+ process.exit(0);
247
+ }
248
+ const root = resolvePackageRoot(options.root);
249
+ const results = search(root, options.query, options.limit);
250
+ if (options.json) {
251
+ process.stdout.write(`${JSON.stringify({ package: PACKAGE_NAME, root, query: options.query, results }, null, 2)}\n`);
252
+ } else if (results.length === 0) {
253
+ process.stdout.write(`No matching knowledge found for: ${options.query}\n`);
254
+ } else {
255
+ for (const [index, result] of results.entries()) {
256
+ process.stdout.write(`${index + 1}. ${result.path}:${result.start_line}-${result.end_line} [${result.score}] ${result.heading}\n`);
257
+ process.stdout.write(` ${result.preview}\n`);
258
+ }
259
+ }
260
+ } catch (error) {
261
+ process.stderr.write(`knowledge-search: ${error instanceof Error ? error.message : String(error)}\n`);
262
+ process.stderr.write(`${usage()}\n`);
263
+ process.exit(2);
264
+ }
@@ -6,7 +6,7 @@ tags:
6
6
  - context
7
7
  - knowledge-base
8
8
  timestamp: "{{knowledgeTimestamp}}"
9
- resource: "context://package/{{packageName}}/wikis"
9
+ resource: "context://package/{{packageName}}/{{wikisRoot}}"
10
10
  package: "{{packageName}}"
11
11
  package_kind: "{{packageKind}}"
12
12
  knowledge_count: {{knowledgeCount}}
@@ -14,7 +14,7 @@ knowledge_count: {{knowledgeCount}}
14
14
 
15
15
  <!-- context:template
16
16
  This file is a starter template. It is rendered by `context build` and copied to
17
- `dist/<package-name>/wikis/index.md`.
17
+ `dist/<package-name>/{{wikisRoot}}/index.md`.
18
18
 
19
19
  Template comments that start with `context:template` are removed from build output.
20
20
  Read the template variable guide before customizing:
@@ -22,9 +22,9 @@ node_modules/@c4a/context/docs/reference/template-variables.md
22
22
 
23
23
  Customize this file before calling the package usable. Add the bundle scope,
24
24
  intended readers, recommended reading order, known gaps, product scenarios,
25
- or task-focused entry sections. The default root index lists only the
26
- next-level directories; context build generates directory-level index.md
27
- files below it.
25
+ or task-focused entry sections. The default root index links small directory
26
+ contents directly and links to child index.md files when a directory exceeds
27
+ the package navigation threshold.
28
28
  -->
29
29
 
30
30
  # {{displayName}}
@@ -38,6 +38,6 @@ Use this index as the entry point, then open the linked knowledge pages for sour
38
38
 
39
39
  ## How To Use
40
40
 
41
- - Start from the directory list above, then open child indexes and linked knowledge pages for source-linked details.
42
- - Use `skills/knowledge-query/SKILL.md` when this bundle is installed as an agent knowledge package.
43
- - Customize `src/package-templates/kb/wikis/index.md` before build to add bundle scope, known gaps, project-specific reading paths, or task entry points.
41
+ - Start from the navigation above, then open linked pages or child indexes for source-linked details.
42
+ - Use the bundled knowledge-query Skill when this bundle is installed as an agent knowledge package.
43
+ - Package authors should replace or edit this generic index before publishing when the package needs bundle scope, known gaps, project-specific reading paths, or task entry points; otherwise explicitly accept the unchanged default during Context package-template review.
@@ -0,0 +1,30 @@
1
+ # {{displayName}}
2
+
3
+ 本知识包由 Context 工作区生成。
4
+
5
+ - 包名:`{{packageName}}`
6
+ - 类型:`{{packageKind}}`
7
+ - 已批准知识文件:`{{knowledgeCount}}`
8
+
9
+ ## 使用方式
10
+
11
+ 将包内 Markdown 作为经过审核、可追溯来源的产品与代码知识。回答问题时优先使用知识页中的可见正文,不要依赖记忆补全。
12
+
13
+ 随包提供的 knowledge-query Skill 会指导 Agent 从 `{{wikisRoot}}/` 等 OKF 索引开始导航,并引用实际读取的知识页。
14
+
15
+ 包内可能包含以下 OKF 根目录:
16
+
17
+ - `{{wikisRoot}}/`:结构化实体和关系,对应 codegraph、business、product。
18
+ - `{{guidesRoot}}/`:架构、流程、FAQ、决策和故障记录。
19
+ - `{{rulesRoot}}/`:标准、约束、验收条件和测试场景。
20
+ - `{{featsRoot}}/`:被选择进入包内的功能知识。
21
+
22
+ 这些目录使用兼容 OKF 的 Context profile:OKF 字段和 Context 扩展字段保持在顶层,不生成额外的 `context` 或 `schema` 包装字段。`{{wikisRoot}}/` 是实体与关系层,guides 和 rules 可以围绕其中内容进行解释或约束。
23
+
24
+ ## 包含的知识
25
+
26
+ `context build` 会把已批准且被当前包选择的 Markdown 复制到这里。目录索引和 `context-build-inventory.json` 用于导航、覆盖检查与关系查询。
27
+
28
+ ## 模板作者建议
29
+
30
+ 这是可直接工作的通用模板。正式发布前,如果知识包存在专用术语、常见用户意图、推荐入口、已知边界或固定任务流程,作者应替换或编辑本文件及 knowledge-query Skill;如果通用行为已经足够,应在 Context 的包模板 Review 中明确接受未修改模板。