@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.
- package/README.md +25 -6
- package/README.zh-CN.md +128 -0
- package/contracts.d.ts +5 -0
- package/docs/README.md +24 -8
- package/docs/getting-started.md +81 -35
- package/docs/guides/agent-dialogue.md +38 -350
- package/docs/guides/agent-guide.md +144 -94
- package/docs/guides/lark-resources.md +115 -0
- package/docs/guides/package-outputs.md +95 -25
- package/docs/reference/package-templates.md +121 -32
- package/docs/reference/project-api.md +261 -84
- package/docs/reference/template-variables.md +32 -15
- package/index.d.ts +13 -5
- package/index.js +113 -29
- package/package.json +4 -1
- package/phases.d.ts +75 -6
- package/templates/package-templates/kb/AGENTS.md +13 -16
- package/templates/package-templates/kb/skills/knowledge-query/SKILL.md +102 -177
- package/templates/package-templates/kb/skills/knowledge-query/scripts/search.mjs +268 -0
- package/templates/package-templates/kb/wikis/index.md +8 -8
- package/templates/package-templates/llms/llms.txt +0 -1
- package/templates/package-templates.zh-CN/kb/AGENTS.md +30 -0
- package/templates/package-templates.zh-CN/kb/skills/knowledge-query/SKILL.md +99 -0
- package/templates/package-templates.zh-CN/kb/skills/knowledge-query/scripts/search.mjs +268 -0
- package/templates/package-templates.zh-CN/kb/wikis/index.md +39 -0
- package/templates/package-templates.zh-CN/llms/llms.txt +8 -0
- package/templates/project-skills/maintain-project-knowledge/SKILL.md +58 -0
- package/templates/project-skills.zh-CN/maintain-project-knowledge/SKILL.md +48 -0
|
@@ -1,208 +1,133 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
+
## Evidence Contract
|
|
32
53
|
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
evidence boundary before answering.
|
|
81
|
+
The script locates `{{packageName}}` when it runs inside the package tree. If a
|
|
82
|
+
package manager copied this Skill elsewhere, add `--root <package directory
|
|
83
|
+
containing context-build-inventory.json>`, or use `--base <package collection>`
|
|
84
|
+
to locate this package by its inventory name. It chunks Markdown mechanically
|
|
85
|
+
by headings and bounded line ranges, then returns paths, line ranges, headings,
|
|
86
|
+
and previews; it does not interpret meaning.
|
|
134
87
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
88
|
+
Treat every hit as a lead and open its page and section before answering. For
|
|
89
|
+
relationship or impact claims, use typed edges from
|
|
90
|
+
`context-build-inventory.json`; BM25 scores and text co-occurrence are not
|
|
91
|
+
relationship evidence.
|
|
138
92
|
|
|
139
|
-
##
|
|
93
|
+
## Citations And Gaps
|
|
140
94
|
|
|
141
|
-
Use compact citations
|
|
95
|
+
Use compact citations tied to claims:
|
|
142
96
|
|
|
143
97
|
```text
|
|
144
|
-
Page
|
|
145
|
-
Section
|
|
146
|
-
Source-
|
|
147
|
-
Relationship
|
|
148
|
-
|
|
98
|
+
Page: <claim> [<root>/path/page.md]
|
|
99
|
+
Section: <claim> [<root>/path/page.md#section-id]
|
|
100
|
+
Source-bound: <claim> [<root>/path/page.md#section-id, source_ref]
|
|
101
|
+
Relationship: <claim> [context-build-inventory.json#structure.edge_records edge:<type>]
|
|
102
|
+
Coverage: <claim> [context-build-inventory.json]
|
|
149
103
|
```
|
|
150
104
|
|
|
151
|
-
|
|
152
|
-
explain what each supports. Do not list citations without tying them to claims.
|
|
105
|
+
When evidence is missing, return:
|
|
153
106
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
107
|
+
```text
|
|
108
|
+
Gap: this package does not contain evidence for <missing point>.
|
|
109
|
+
Checked: <indexes, pages, or artifacts>.
|
|
110
|
+
Next useful source: <source, page, or source_ref if known>.
|
|
111
|
+
```
|
|
158
112
|
|
|
159
|
-
|
|
160
|
-
|
|
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.
|
|
113
|
+
Distinguish “not evidenced by this package” from “not true.” Do not fill gaps
|
|
114
|
+
from memory, previous conversations, or source files outside the package.
|
|
167
115
|
|
|
168
|
-
##
|
|
116
|
+
## Package Boundary
|
|
169
117
|
|
|
170
|
-
|
|
118
|
+
- The bundled OKF-compatible roots are the source of truth for this Skill.
|
|
119
|
+
- The package contains approved knowledge selected by its Context workspace; it
|
|
120
|
+
does not claim complete coverage of the underlying product or codebase.
|
|
121
|
+
- Do not scan every page or edge when a narrower indexed scope answers the
|
|
122
|
+
request.
|
|
171
123
|
|
|
172
|
-
|
|
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
|
-
```
|
|
124
|
+
Approved knowledge files: `{{knowledgeCount}}`
|
|
177
125
|
|
|
178
|
-
|
|
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
|
|
126
|
+
## Template Author Recommendation
|
|
207
127
|
|
|
208
|
-
|
|
128
|
+
This is a complete generic query Skill, but package authors should replace or
|
|
129
|
+
edit it before publishing when the package has project-specific terminology,
|
|
130
|
+
common user intents, preferred entry pages, known limits, or task workflows.
|
|
131
|
+
Update the description, routing table, and package-boundary guidance to match
|
|
132
|
+
the actual package. If the generic behavior is intentionally sufficient,
|
|
133
|
+
explicitly accept the unchanged default during Context package-template 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
|
+
}
|
|
@@ -6,7 +6,7 @@ tags:
|
|
|
6
6
|
- context
|
|
7
7
|
- knowledge-base
|
|
8
8
|
timestamp: "{{knowledgeTimestamp}}"
|
|
9
|
-
resource: "context://package/{{packageName}}/
|
|
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>/
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
42
|
-
- Use
|
|
43
|
-
-
|
|
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 中明确接受未修改模板。
|