@jagilber-org/index-server 1.28.9 → 1.28.19
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/CHANGELOG.md +109 -1
- package/CONTRIBUTING.md +13 -0
- package/README.md +10 -14
- package/dist/config/featureConfig.js +4 -1
- package/dist/dashboard/client/admin.html +69 -29
- package/dist/dashboard/client/js/admin.embeddings.js +97 -5
- package/dist/dashboard/client/js/admin.instructions.js +1 -1
- package/dist/dashboard/server/AdminPanel.js +38 -0
- package/dist/dashboard/server/ApiRoutes.js +14 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +76 -1
- package/dist/dashboard/server/routes/instructions.routes.js +4 -11
- package/dist/dashboard/server/routes/scripts.routes.js +35 -10
- package/dist/dashboard/server/routes/status.routes.js +77 -0
- package/dist/models/instruction.d.ts +2 -1
- package/dist/models/instruction.js +2 -0
- package/dist/schemas/index-server.code-schema.json +52478 -0
- package/dist/schemas/index.d.ts +7 -164
- package/dist/schemas/index.js +45 -63
- package/dist/schemas/instructionSchema.d.ts +46 -0
- package/dist/schemas/instructionSchema.js +159 -0
- package/{schemas → dist/schemas}/json-schema/instruction-content-type.schema.json +6 -4
- package/{schemas → dist/schemas}/json-schema/instruction-instruction-entry.schema.json +6 -4
- package/dist/schemas/manifest.json +78 -0
- package/dist/server/index-server.js +7 -1
- package/dist/services/bootstrapGating.js +2 -2
- package/dist/services/handlers/instructions.add.js +18 -0
- package/dist/services/handlers/instructions.groom.js +6 -1
- package/dist/services/handlers/instructions.import.js +42 -7
- package/dist/services/handlers.activation.js +3 -1
- package/dist/services/handlers.dashboardConfig.js +2 -1
- package/dist/services/handlers.feedback.d.ts +4 -4
- package/dist/services/handlers.feedback.js +390 -27
- package/dist/services/handlers.instructionSchema.js +73 -31
- package/dist/services/handlers.search.js +11 -6
- package/dist/services/indexLoader.js +7 -0
- package/dist/services/instructionRecordValidation.js +32 -84
- package/dist/services/mcpConfig/flagCatalog.d.ts +1 -1
- package/dist/services/mcpConfig/flagCatalog.js +2 -0
- package/dist/services/mcpConfig/formats.js +2 -6
- package/dist/services/messaging/agentMailbox.d.ts +6 -1
- package/dist/services/messaging/agentMailbox.js +10 -3
- package/dist/services/seedBootstrap.contentModel.d.ts +13 -0
- package/dist/services/seedBootstrap.contentModel.js +166 -0
- package/dist/services/seedBootstrap.contentTypes.d.ts +5 -0
- package/dist/services/seedBootstrap.contentTypes.js +76 -0
- package/dist/services/seedBootstrap.d.ts +1 -0
- package/dist/services/seedBootstrap.js +101 -15
- package/dist/services/toolRegistry.js +52 -24
- package/dist/services/toolRegistry.zod.js +84 -37
- package/dist/versioning/schemaVersion.d.ts +1 -1
- package/dist/versioning/schemaVersion.js +1 -13
- package/package.json +17 -3
- package/schemas/index-server.code-schema.json +31019 -25047
- package/schemas/instruction.schema.json +16 -6
- package/schemas/manifest.json +3 -3
- package/scripts/README.md +20 -0
- package/scripts/build/README.md +41 -0
- package/scripts/build/setup-wizard-paths.mjs +27 -0
- package/scripts/build/setup-wizard.mjs +7 -21
- package/scripts/client/README.md +26 -0
- package/scripts/client/index-server-client.ps1 +203 -0
- package/scripts/client/index-server-client.sh +149 -0
- package/scripts/client/powershell-mcp-server.ps1 +83 -0
- package/scripts/client/powershell-mcp-template.ps1 +85 -0
- package/scripts/hooks/README.md +40 -0
- package/server.json +2 -2
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-admin-session.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-session-history-entry.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-persisted-web-socket-connection.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-config.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-data.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-manifest.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/SessionPersistence-session-persistence-metadata.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/instruction-audience-scope.schema.json +0 -0
- /package/{schemas → dist/schemas}/json-schema/instruction-requirement-level.schema.json +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildContentModelSeed = buildContentModelSeed;
|
|
7
|
+
/**
|
|
8
|
+
* Builds the canonical "content model" seed (002-content-model) from the
|
|
9
|
+
* authoritative instruction JSON schema at module-load time.
|
|
10
|
+
*
|
|
11
|
+
* Single source of truth: the schema. The body here is regenerated on every
|
|
12
|
+
* server start, so any change to the schema's `contentType.enum`, its
|
|
13
|
+
* description, the top-level `required` array, or referenced field
|
|
14
|
+
* descriptions automatically flows into the seed. A drift test
|
|
15
|
+
* (src/tests/contentModelSeed.spec.ts) enforces the wiring.
|
|
16
|
+
*
|
|
17
|
+
* Constraint (constitution A-7): generalized, public-safe, environment-agnostic.
|
|
18
|
+
*/
|
|
19
|
+
const instruction_schema_json_1 = __importDefault(require("../../schemas/instruction.schema.json"));
|
|
20
|
+
/**
|
|
21
|
+
* Parse the `contentType` schema description for `<name> (<desc>)` fragments
|
|
22
|
+
* and filter to the canonical enum members.
|
|
23
|
+
*/
|
|
24
|
+
function parseContentTypeMatrix(s) {
|
|
25
|
+
const ct = s.properties.contentType;
|
|
26
|
+
if (!ct || !Array.isArray(ct.enum) || typeof ct.description !== 'string') {
|
|
27
|
+
throw new Error('content-model seed: schema is missing properties.contentType.enum or .description');
|
|
28
|
+
}
|
|
29
|
+
const enumSet = new Set(ct.enum);
|
|
30
|
+
const rows = [];
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const re = /([a-z][a-z-]*) \(([^)]+)\)/g;
|
|
33
|
+
let m;
|
|
34
|
+
while ((m = re.exec(ct.description)) !== null) {
|
|
35
|
+
const value = m[1];
|
|
36
|
+
const description = m[2].trim();
|
|
37
|
+
if (!enumSet.has(value))
|
|
38
|
+
continue;
|
|
39
|
+
if (seen.has(value))
|
|
40
|
+
continue;
|
|
41
|
+
seen.add(value);
|
|
42
|
+
rows.push({ value, description });
|
|
43
|
+
}
|
|
44
|
+
// Guarantee every enum member appears even if the description prose drifts.
|
|
45
|
+
for (const v of ct.enum) {
|
|
46
|
+
if (!seen.has(v))
|
|
47
|
+
rows.push({ value: v, description: '(no description in schema)' });
|
|
48
|
+
}
|
|
49
|
+
// Stable order: schema enum order
|
|
50
|
+
rows.sort((a, b) => ct.enum.indexOf(a.value) - ct.enum.indexOf(b.value));
|
|
51
|
+
return rows;
|
|
52
|
+
}
|
|
53
|
+
function fieldBullet(fieldName, s) {
|
|
54
|
+
const desc = s.properties[fieldName]?.description;
|
|
55
|
+
if (!desc) {
|
|
56
|
+
throw new Error(`content-model seed: schema property '${fieldName}' has no description`);
|
|
57
|
+
}
|
|
58
|
+
return `- \`${fieldName}\` — ${desc}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Common optional fields surfaced to agents. Order is curated for narrative
|
|
62
|
+
* flow; the *descriptions* are pulled from the schema so the seed cannot
|
|
63
|
+
* drift from `index_schema`. Adding/removing entries here is a deliberate
|
|
64
|
+
* editorial choice, but the prose is never duplicated.
|
|
65
|
+
*/
|
|
66
|
+
const COMMON_OPTIONAL_FIELDS = [
|
|
67
|
+
'priorityTier',
|
|
68
|
+
'semanticSummary',
|
|
69
|
+
'primaryCategory',
|
|
70
|
+
'owner',
|
|
71
|
+
'classification',
|
|
72
|
+
'version',
|
|
73
|
+
'status',
|
|
74
|
+
'reviewIntervalDays',
|
|
75
|
+
'lastReviewedAt',
|
|
76
|
+
'nextReviewDue',
|
|
77
|
+
'rationale'
|
|
78
|
+
];
|
|
79
|
+
function readSchemaVersion(s) {
|
|
80
|
+
const sv = s.properties.schemaVersion;
|
|
81
|
+
if (!sv || !Array.isArray(sv.enum) || sv.enum.length === 0) {
|
|
82
|
+
throw new Error('content-model seed: schema.properties.schemaVersion.enum is missing or empty');
|
|
83
|
+
}
|
|
84
|
+
// Pick the highest enum value (strings compared numerically when possible).
|
|
85
|
+
const sorted = [...sv.enum].sort((a, b) => {
|
|
86
|
+
const na = Number(a);
|
|
87
|
+
const nb = Number(b);
|
|
88
|
+
if (Number.isFinite(na) && Number.isFinite(nb))
|
|
89
|
+
return nb - na;
|
|
90
|
+
return b.localeCompare(a);
|
|
91
|
+
});
|
|
92
|
+
return sorted[0];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the canonical seed object for `002-content-model` from the schema.
|
|
96
|
+
*
|
|
97
|
+
* The function is pure: same schema input → same seed output. Tests rely on
|
|
98
|
+
* this determinism to assert drift-safety.
|
|
99
|
+
*
|
|
100
|
+
* @returns Canonical seed `{ file, id, json }` ready to push into CANONICAL_SEEDS.
|
|
101
|
+
*/
|
|
102
|
+
function buildContentModelSeed() {
|
|
103
|
+
const s = instruction_schema_json_1.default;
|
|
104
|
+
if (!Array.isArray(s.required) || s.required.length === 0) {
|
|
105
|
+
throw new Error('content-model seed: schema.required is missing or empty');
|
|
106
|
+
}
|
|
107
|
+
const matrix = parseContentTypeMatrix(s);
|
|
108
|
+
const requiredLines = s.required.map(f => fieldBullet(f, s)).join('\n');
|
|
109
|
+
const optionalLines = COMMON_OPTIONAL_FIELDS.map(f => fieldBullet(f, s)).join('\n');
|
|
110
|
+
const matrixRows = matrix
|
|
111
|
+
.map(r => `| \`${r.value}\` | ${r.description} |`)
|
|
112
|
+
.join('\n');
|
|
113
|
+
const schemaVersionValue = readSchemaVersion(s);
|
|
114
|
+
const body = `# Index Server Content Model
|
|
115
|
+
|
|
116
|
+
Knowledge for AI agents writing or evaluating instruction entries. This document is regenerated from the canonical \`schemas/instruction.schema.json\` on every server start, so it always matches the validator the index actually runs.
|
|
117
|
+
|
|
118
|
+
For the full machine-validatable schema (with current ranges, runtime limits, and the promotion-workflow checklist) call the \`index_schema\` MCP tool. Treat that tool as the validation source of truth at runtime; this seed is the conceptual knowledge guide.
|
|
119
|
+
|
|
120
|
+
## Required fields
|
|
121
|
+
|
|
122
|
+
Every instruction entry must include:
|
|
123
|
+
|
|
124
|
+
${requiredLines}
|
|
125
|
+
|
|
126
|
+
## \`contentType\` decision matrix
|
|
127
|
+
|
|
128
|
+
Pick the \`contentType\` that matches what the entry is for:
|
|
129
|
+
|
|
130
|
+
| Value | Use when |
|
|
131
|
+
|-------|----------|
|
|
132
|
+
${matrixRows}
|
|
133
|
+
|
|
134
|
+
Default is \`instruction\`.
|
|
135
|
+
|
|
136
|
+
## Common optional fields
|
|
137
|
+
|
|
138
|
+
${optionalLines}
|
|
139
|
+
|
|
140
|
+
Call \`index_schema\` for the authoritative list, validation rules, and minimal example.
|
|
141
|
+
|
|
142
|
+
## Note on adjacent concepts
|
|
143
|
+
|
|
144
|
+
Some agent platforms use terms such as plugin, MCP server, or connector for deployment surfaces. When documenting those surfaces in Index Server, choose the canonical \`contentType\` by purpose: external system guidance is \`integration\`, reusable context is \`knowledge\`, a callable capability is \`skill\`, and a multi-step process is \`workflow\`.
|
|
145
|
+
`;
|
|
146
|
+
return {
|
|
147
|
+
file: '002-content-model.json',
|
|
148
|
+
id: '002-content-model',
|
|
149
|
+
json: {
|
|
150
|
+
id: '002-content-model',
|
|
151
|
+
title: 'Index Server Content Model & Field Reference',
|
|
152
|
+
body,
|
|
153
|
+
audience: 'all',
|
|
154
|
+
requirement: 'recommended',
|
|
155
|
+
priority: 95,
|
|
156
|
+
priorityTier: 'P1',
|
|
157
|
+
contentType: 'knowledge',
|
|
158
|
+
categories: ['bootstrap', 'content-model', 'reference', 'schema'],
|
|
159
|
+
primaryCategory: 'reference',
|
|
160
|
+
owner: 'system',
|
|
161
|
+
version: '1.0.0',
|
|
162
|
+
schemaVersion: schemaVersionValue,
|
|
163
|
+
semanticSummary: 'Knowledge for AI agents: required fields, the contentType decision matrix, and pointer to index_schema for the live JSON schema.'
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildContentTypesSeed = buildContentTypesSeed;
|
|
7
|
+
const instruction_schema_json_1 = __importDefault(require("../../schemas/instruction.schema.json"));
|
|
8
|
+
function contentTypeRows(s) {
|
|
9
|
+
const ct = s.properties.contentType;
|
|
10
|
+
if (!ct || !Array.isArray(ct.enum) || typeof ct.description !== 'string') {
|
|
11
|
+
throw new Error('content-types seed: schema is missing properties.contentType.enum or .description');
|
|
12
|
+
}
|
|
13
|
+
const rows = [];
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const enumSet = new Set(ct.enum);
|
|
16
|
+
const re = /([a-z][a-z-]*) \(([^)]+)\)/g;
|
|
17
|
+
let m;
|
|
18
|
+
while ((m = re.exec(ct.description)) !== null) {
|
|
19
|
+
const value = m[1];
|
|
20
|
+
if (!enumSet.has(value) || seen.has(value))
|
|
21
|
+
continue;
|
|
22
|
+
seen.add(value);
|
|
23
|
+
rows.push({ value, description: m[2].trim() });
|
|
24
|
+
}
|
|
25
|
+
for (const value of ct.enum) {
|
|
26
|
+
if (!seen.has(value))
|
|
27
|
+
rows.push({ value, description: '(no description in schema)' });
|
|
28
|
+
}
|
|
29
|
+
rows.sort((a, b) => ct.enum.indexOf(a.value) - ct.enum.indexOf(b.value));
|
|
30
|
+
return rows;
|
|
31
|
+
}
|
|
32
|
+
function readSchemaVersion(s) {
|
|
33
|
+
const sv = s.properties.schemaVersion;
|
|
34
|
+
if (!sv || !Array.isArray(sv.enum) || sv.enum.length === 0) {
|
|
35
|
+
throw new Error('content-types seed: schema.properties.schemaVersion.enum is missing or empty');
|
|
36
|
+
}
|
|
37
|
+
return [...sv.enum].sort((a, b) => Number(b) - Number(a))[0];
|
|
38
|
+
}
|
|
39
|
+
function buildContentTypesSeed() {
|
|
40
|
+
const s = instruction_schema_json_1.default;
|
|
41
|
+
const matrixRows = contentTypeRows(s)
|
|
42
|
+
.map(r => `| \`${r.value}\` | ${r.description} |`)
|
|
43
|
+
.join('\n');
|
|
44
|
+
const body = `# Index Server Content Types
|
|
45
|
+
|
|
46
|
+
This knowledge seed lists the canonical \`contentType\` taxonomy from \`schemas/instruction.schema.json\`. It is regenerated from the schema on every server start so agents see the same enum values the validator enforces.
|
|
47
|
+
|
|
48
|
+
For the full machine-validatable schema, call the \`index_schema\` MCP tool.
|
|
49
|
+
|
|
50
|
+
| Value | Use when |
|
|
51
|
+
|-------|----------|
|
|
52
|
+
${matrixRows}
|
|
53
|
+
|
|
54
|
+
Default is \`instruction\`.
|
|
55
|
+
`;
|
|
56
|
+
return {
|
|
57
|
+
file: '003-content-types.json',
|
|
58
|
+
id: '003-content-types',
|
|
59
|
+
json: {
|
|
60
|
+
id: '003-content-types',
|
|
61
|
+
title: 'Index Server Content Types',
|
|
62
|
+
body,
|
|
63
|
+
audience: 'all',
|
|
64
|
+
requirement: 'recommended',
|
|
65
|
+
priority: 94,
|
|
66
|
+
priorityTier: 'P1',
|
|
67
|
+
contentType: 'knowledge',
|
|
68
|
+
categories: ['bootstrap', 'content-types', 'schema'],
|
|
69
|
+
primaryCategory: 'schema',
|
|
70
|
+
owner: 'system',
|
|
71
|
+
version: '1.0.0',
|
|
72
|
+
schemaVersion: readSchemaVersion(s),
|
|
73
|
+
semanticSummary: 'Canonical Index Server contentType taxonomy generated from instruction.schema.json.',
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -11,6 +11,8 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
11
11
|
const indexContext_1 = require("./indexContext");
|
|
12
12
|
const logger_1 = require("./logger");
|
|
13
13
|
const runtimeConfig_1 = require("../config/runtimeConfig");
|
|
14
|
+
const seedBootstrap_contentModel_1 = require("./seedBootstrap.contentModel");
|
|
15
|
+
const seedBootstrap_contentTypes_1 = require("./seedBootstrap.contentTypes");
|
|
14
16
|
// Canonical seed instruction objects (kept intentionally minimal – DO NOT add environment specific data)
|
|
15
17
|
const CANONICAL_SEEDS = [
|
|
16
18
|
{
|
|
@@ -156,13 +158,15 @@ docker compose up # HTTP on :8787
|
|
|
156
158
|
Restart your MCP client after configuration changes. Verify with \`health_check\`.
|
|
157
159
|
|
|
158
160
|
For full configuration options: see \`docs/mcp_configuration.md\` and \`docs/configuration.md\`.`,
|
|
159
|
-
audience: '
|
|
160
|
-
requirement: '
|
|
161
|
+
audience: 'all',
|
|
162
|
+
requirement: 'mandatory',
|
|
161
163
|
priority: 100,
|
|
164
|
+
priorityTier: 'P1',
|
|
165
|
+
contentType: 'instruction',
|
|
162
166
|
categories: ['bootstrap', 'mcp-activation', 'quick-start', 'documentation'],
|
|
163
167
|
owner: 'system',
|
|
164
|
-
version: 3,
|
|
165
|
-
schemaVersion: '
|
|
168
|
+
version: '3.0.0',
|
|
169
|
+
schemaVersion: '6',
|
|
166
170
|
semanticSummary: 'Index Server quick start: search-first workflow, knowledge contribution, copilot instructions setup, and MCP client configuration for AI agents'
|
|
167
171
|
}
|
|
168
172
|
},
|
|
@@ -173,17 +177,26 @@ For full configuration options: see \`docs/mcp_configuration.md\` and \`docs/con
|
|
|
173
177
|
id: '001-lifecycle-bootstrap',
|
|
174
178
|
title: 'Lifecycle Bootstrap: Local-First Instruction Strategy',
|
|
175
179
|
body: 'Purpose: Early lifecycle guidance after bootstrap confirmation. Keep index minimal; prefer local-first P0/P1 additions; promote only after stability.',
|
|
176
|
-
audience: '
|
|
180
|
+
audience: 'all',
|
|
177
181
|
requirement: 'recommended',
|
|
178
|
-
|
|
182
|
+
priority: 99,
|
|
183
|
+
priorityTier: 'P1',
|
|
184
|
+
contentType: 'instruction',
|
|
179
185
|
categories: ['bootstrap', 'lifecycle'],
|
|
180
186
|
owner: 'system',
|
|
181
|
-
version: 1,
|
|
182
|
-
schemaVersion: '
|
|
187
|
+
version: '1.0.0',
|
|
188
|
+
schemaVersion: '6',
|
|
183
189
|
semanticSummary: 'Lifecycle and promotion guardrails after bootstrap confirmation',
|
|
184
190
|
reviewIntervalDays: 120
|
|
185
191
|
}
|
|
186
|
-
}
|
|
192
|
+
},
|
|
193
|
+
// 002-content-model is generated from schemas/instruction.schema.json at
|
|
194
|
+
// module load. Single source of truth: any change to the schema's required
|
|
195
|
+
// fields, contentType enum, or referenced field descriptions automatically
|
|
196
|
+
// flows into the seed body. Drift is enforced by
|
|
197
|
+
// src/tests/contentModelSeed.spec.ts.
|
|
198
|
+
(0, seedBootstrap_contentModel_1.buildContentModelSeed)(),
|
|
199
|
+
(0, seedBootstrap_contentTypes_1.buildContentTypesSeed)()
|
|
187
200
|
];
|
|
188
201
|
function computeCanonicalHash() {
|
|
189
202
|
const canonical = CANONICAL_SEEDS.map(s => ({ id: s.id, file: s.file, json: s.json })).sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -198,7 +211,7 @@ function autoSeedBootstrap() {
|
|
|
198
211
|
const cfg = (0, runtimeConfig_1.getRuntimeConfig)().bootstrapSeed;
|
|
199
212
|
const disabled = !cfg.autoSeed;
|
|
200
213
|
const dir = safeInstructionsDir();
|
|
201
|
-
const summary = { dir, created: [], existing: [], skipped: [], disabled, hash: computeCanonicalHash() };
|
|
214
|
+
const summary = { dir, created: [], existing: [], skipped: [], upgraded: [], disabled, hash: computeCanonicalHash() };
|
|
202
215
|
if (disabled) {
|
|
203
216
|
summary.reason = 'disabled_by_env';
|
|
204
217
|
return summary;
|
|
@@ -215,10 +228,76 @@ function autoSeedBootstrap() {
|
|
|
215
228
|
for (const seed of CANONICAL_SEEDS) {
|
|
216
229
|
const target = path_1.default.join(dir, seed.file);
|
|
217
230
|
const exists = fs_1.default.existsSync(target);
|
|
231
|
+
// Canonical body hash (matches the sourceHash baked at write time).
|
|
232
|
+
// Used both to bake into freshly written seeds and to detect drift
|
|
233
|
+
// against existing on-disk seeds whose generated body has changed
|
|
234
|
+
// (e.g. 002-content-model after an instruction.schema.json edit).
|
|
235
|
+
const canonicalBody = typeof seed.json.body === 'string' ? seed.json.body : '';
|
|
236
|
+
const canonicalSourceHash = crypto_1.default.createHash('sha256').update(canonicalBody, 'utf8').digest('hex');
|
|
218
237
|
if (exists) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
238
|
+
// Detect schema-invalid stale seeds from earlier versions and rewrite
|
|
239
|
+
// them. Pre-v1.28.10 wrote `requirement: 'required'` and
|
|
240
|
+
// `priorityTier: 'p1'` (lowercase), both rejected by the current
|
|
241
|
+
// instruction.schema.json. Without this upgrade path, broken seeds
|
|
242
|
+
// persist forever because we previously never overwrote existing files.
|
|
243
|
+
// We deliberately scope the upgrade to enum violations (not arbitrary
|
|
244
|
+
// user edits) so hand-curated valid seeds are preserved. RCA 2026-05-07.
|
|
245
|
+
//
|
|
246
|
+
// Additionally: detect canonical-body drift (sourceHash mismatch)
|
|
247
|
+
// for canonical seeds. The 002-content-model seed is generated from
|
|
248
|
+
// instruction.schema.json on every server start; without this check
|
|
249
|
+
// an existing on-disk seed would silently go stale after a schema
|
|
250
|
+
// edit, violating the documented single-source-of-truth contract.
|
|
251
|
+
// RCA 2026-05-08 (PR #324 quality review).
|
|
252
|
+
let stale = false;
|
|
253
|
+
let staleReason = '';
|
|
254
|
+
try {
|
|
255
|
+
const existing = JSON.parse(fs_1.default.readFileSync(target, 'utf8'));
|
|
256
|
+
const validRequirement = ['mandatory', 'critical', 'recommended', 'optional', 'deprecated'];
|
|
257
|
+
const validPriorityTier = ['P1', 'P2', 'P3', 'P4'];
|
|
258
|
+
if (typeof existing.requirement === 'string' && !validRequirement.includes(existing.requirement)) {
|
|
259
|
+
stale = true;
|
|
260
|
+
staleReason = `invalid requirement="${existing.requirement}"`;
|
|
261
|
+
}
|
|
262
|
+
else if (typeof existing.priorityTier === 'string' && !validPriorityTier.includes(existing.priorityTier)) {
|
|
263
|
+
stale = true;
|
|
264
|
+
staleReason = `invalid priorityTier="${existing.priorityTier}"`;
|
|
265
|
+
}
|
|
266
|
+
else if (typeof existing.sourceHash === 'string' && existing.sourceHash !== canonicalSourceHash) {
|
|
267
|
+
// sourceHash on disk no longer matches the in-code canonical body.
|
|
268
|
+
// Refresh so canonical seeds track the current source-of-truth.
|
|
269
|
+
stale = true;
|
|
270
|
+
staleReason = `canonical body drift: on-disk sourceHash=${existing.sourceHash.slice(0, 12)}… expected=${canonicalSourceHash.slice(0, 12)}…`;
|
|
271
|
+
}
|
|
272
|
+
else if (typeof existing.sourceHash !== 'string') {
|
|
273
|
+
// Pre-sourceHash legacy install: an older Index Server version
|
|
274
|
+
// wrote canonical seeds without baking sourceHash. Without this
|
|
275
|
+
// branch a stale legacy body would skip refresh forever, since
|
|
276
|
+
// the hash-mismatch check above never trips. Treat absence as
|
|
277
|
+
// drift so legacy installs converge on the current canonical
|
|
278
|
+
// body. RCA 2026-05-08 (PR #324 reliability advisory).
|
|
279
|
+
stale = true;
|
|
280
|
+
staleReason = 'missing sourceHash (legacy pre-sourceHash install)';
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
stale = true;
|
|
285
|
+
staleReason = `unparseable_json: ${(e instanceof Error) ? e.message : String(e)}`;
|
|
286
|
+
}
|
|
287
|
+
if (!stale) {
|
|
288
|
+
summary.existing.push(seed.file);
|
|
289
|
+
summary.skipped.push(seed.file);
|
|
290
|
+
continue; // valid existing seed; do not overwrite
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
fs_1.default.unlinkSync(target);
|
|
294
|
+
}
|
|
295
|
+
catch { /* fall through to write */ }
|
|
296
|
+
try {
|
|
297
|
+
process.stderr.write(`[seed] upgrading stale seed ${seed.file}: ${staleReason}\n`);
|
|
298
|
+
}
|
|
299
|
+
catch { /* ignore */ }
|
|
300
|
+
// fall through to write canonical content
|
|
222
301
|
}
|
|
223
302
|
// Directory empty OR missing seed triggers creation.
|
|
224
303
|
try {
|
|
@@ -226,10 +305,17 @@ function autoSeedBootstrap() {
|
|
|
226
305
|
// Inject timestamps at write time so loaders never trigger
|
|
227
306
|
// [invariant-repair] firstSeenTs WARN noise on subsequent reads.
|
|
228
307
|
const nowIso = new Date().toISOString();
|
|
229
|
-
|
|
308
|
+
// Bake-in sourceHash so integrity_verify reports zero drift on a fresh install.
|
|
309
|
+
// Schema requires sha256(body) for the body field; without this seeds appear
|
|
310
|
+
// as drift forever (expected hash empty, actual populated).
|
|
311
|
+
const sourceHash = canonicalSourceHash;
|
|
312
|
+
const stamped = { createdAt: nowIso, updatedAt: nowIso, firstSeenTs: nowIso, sourceHash, ...seed.json };
|
|
230
313
|
fs_1.default.writeFileSync(tmp, JSON.stringify(stamped, null, 2), { encoding: 'utf8' });
|
|
231
314
|
fs_1.default.renameSync(tmp, target);
|
|
232
|
-
|
|
315
|
+
if (exists)
|
|
316
|
+
summary.upgraded.push(seed.file);
|
|
317
|
+
else
|
|
318
|
+
summary.created.push(seed.file);
|
|
233
319
|
}
|
|
234
320
|
catch (e) {
|
|
235
321
|
summary.reason = `partial_failure ${(e instanceof Error) ? e.message : String(e)}`;
|
|
@@ -12,20 +12,9 @@ const schemas_1 = require("../schemas");
|
|
|
12
12
|
const featureFlags_1 = require("./featureFlags");
|
|
13
13
|
const envUtils_1 = require("../utils/envUtils");
|
|
14
14
|
const runtimeConfig_1 = require("../config/runtimeConfig");
|
|
15
|
+
const instruction_1 = require("../models/instruction");
|
|
15
16
|
// Input schema helpers (keep intentionally permissive if params optional)
|
|
16
17
|
const stringReq = (name) => ({ type: 'object', additionalProperties: false, required: [name], properties: { [name]: { type: 'string' } } });
|
|
17
|
-
const extensionsInputSchema = {
|
|
18
|
-
type: 'object',
|
|
19
|
-
additionalProperties: {
|
|
20
|
-
anyOf: [
|
|
21
|
-
{ type: 'string' },
|
|
22
|
-
{ type: 'number' },
|
|
23
|
-
{ type: 'boolean' },
|
|
24
|
-
{ type: 'array', items: {} },
|
|
25
|
-
{ type: 'object', additionalProperties: true },
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
18
|
const DANGEROUS_DIAGNOSTIC_TOOLS = new Set([
|
|
30
19
|
'diagnostics_block',
|
|
31
20
|
'diagnostics_microtaskFlood',
|
|
@@ -42,14 +31,21 @@ function buildInstructionBodyInputSchema(baseDescription) {
|
|
|
42
31
|
function withDynamicInstructionBodyLimits(name, inputSchema) {
|
|
43
32
|
const schema = JSON.parse(JSON.stringify(inputSchema));
|
|
44
33
|
if (name === 'index_add') {
|
|
45
|
-
const
|
|
34
|
+
const entry = schema.properties?.entry ?? {};
|
|
35
|
+
const entryProps = entry.properties ?? {};
|
|
46
36
|
entryProps.body = buildInstructionBodyInputSchema('Instruction body.');
|
|
37
|
+
// Sub-schema $id must be unique across compile cycles so Ajv (a) does
|
|
38
|
+
// not reject duplicate-id registration and (b) can still resolve the
|
|
39
|
+
// embedded "#/definitions/..." $refs scoped to this schema.
|
|
40
|
+
entry.$id = `tool-input/index_add/entry/${++subSchemaCounter}`;
|
|
47
41
|
}
|
|
48
42
|
else if (name === 'index_import') {
|
|
49
43
|
const entries = schema.properties?.entries?.oneOf;
|
|
50
44
|
const arrayVariant = Array.isArray(entries) ? entries.find((candidate) => candidate.type === 'array') : undefined;
|
|
51
|
-
const
|
|
45
|
+
const items = arrayVariant?.items ?? {};
|
|
46
|
+
const itemProps = items.properties ?? {};
|
|
52
47
|
itemProps.body = buildInstructionBodyInputSchema('Instruction body.');
|
|
48
|
+
items.$id = `tool-input/index_import/entry/${++subSchemaCounter}`;
|
|
53
49
|
}
|
|
54
50
|
else if (name === 'index_dispatch') {
|
|
55
51
|
const props = schema.properties ?? {};
|
|
@@ -59,6 +55,7 @@ function withDynamicInstructionBodyLimits(name, inputSchema) {
|
|
|
59
55
|
}
|
|
60
56
|
return schema;
|
|
61
57
|
}
|
|
58
|
+
let subSchemaCounter = 0;
|
|
62
59
|
// Explicit param schemas derived from handlers in toolHandlers.ts
|
|
63
60
|
const INPUT_SCHEMAS = {
|
|
64
61
|
// graph export (Phase 1 + Phase 2 enrichment). All params optional.
|
|
@@ -94,7 +91,7 @@ const INPUT_SCHEMAS = {
|
|
|
94
91
|
keywords: { type: 'array', items: { type: 'string' }, description: 'Explicit keyword array for search action when the caller wants direct token control.' },
|
|
95
92
|
ids: { type: 'array', items: { type: 'string' }, description: 'Array of instruction IDs for remove or export actions.' },
|
|
96
93
|
category: { type: 'string', description: 'Filter by category for list action.' },
|
|
97
|
-
contentType: { type: 'string', enum: [
|
|
94
|
+
contentType: { type: 'string', enum: [...instruction_1.CONTENT_TYPES], description: 'Filter by content type for list, search, or query actions, or specify the entry content type for add action.' },
|
|
98
95
|
text: { type: 'string', description: 'Full-text search within query action.' },
|
|
99
96
|
includeCategories: { type: 'boolean', description: 'Search categories in addition to id/title/semanticSummary/body for search action.' },
|
|
100
97
|
caseSensitive: { type: 'boolean', description: 'Enable case-sensitive matching for search action.' },
|
|
@@ -145,20 +142,27 @@ const INPUT_SCHEMAS = {
|
|
|
145
142
|
// NOTE: instructions_query & instructions_categories removed as standalone tools.
|
|
146
143
|
// They are now exclusively accessed via index_dispatch with actions 'query' and 'categories'.
|
|
147
144
|
// legacy read-only instruction method schemas removed in favor of dispatcher
|
|
145
|
+
//
|
|
146
|
+
// index_import / index_add tool input schemas are DERIVED from the canonical
|
|
147
|
+
// instruction schema via buildToolInputEntrySchema(). The two surfaces only
|
|
148
|
+
// differ in their caller-required minimum (index_add accepts {id, body};
|
|
149
|
+
// index_import requires {id, title, body, priority, audience, requirement}).
|
|
150
|
+
// Both reject server-managed properties (additionalProperties:false +
|
|
151
|
+
// server-managed keys stripped) and share property definitions, enums and
|
|
152
|
+
// patterns with on-disk validation. Hand-maintaining these schemas was the
|
|
153
|
+
// original drift source — see src/schemas/instructionSchema.ts.
|
|
148
154
|
'index_import': { type: 'object', additionalProperties: false, properties: {
|
|
149
155
|
entries: { oneOf: [
|
|
150
|
-
{ type: 'array', minItems: 1, items:
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
{ type: 'array', minItems: 1, items: (0, schemas_1.buildToolInputEntrySchema)({
|
|
157
|
+
required: ['id', 'title', 'body', 'priority', 'audience', 'requirement'],
|
|
158
|
+
}) },
|
|
153
159
|
{ type: 'string', description: 'Stringified JSON array of instruction entries, or a file path to a JSON array of instruction entries' }
|
|
154
160
|
] },
|
|
155
161
|
source: { type: 'string', description: 'Directory path containing .json instruction files to import' },
|
|
156
162
|
mode: { enum: ['skip', 'overwrite'] }
|
|
157
163
|
} },
|
|
158
164
|
'index_add': { type: 'object', additionalProperties: false, required: ['entry'], properties: {
|
|
159
|
-
entry: {
|
|
160
|
-
id: { type: 'string', minLength: 1, maxLength: 120, pattern: '^[a-z0-9](?:[a-z0-9-_]{0,118}[a-z0-9])?$' }, title: { type: 'string' }, body: { type: 'string' }, rationale: { type: 'string' }, priority: { type: 'number', minimum: 1, maximum: 100 }, audience: { type: 'string', enum: ['individual', 'group', 'all'] }, requirement: { type: 'string', enum: ['mandatory', 'critical', 'recommended', 'optional', 'deprecated'] }, categories: { type: 'array', items: { type: 'string', pattern: '^[a-z0-9][a-z0-9-_]{0,48}$' } }, deprecatedBy: { type: 'string' }, riskScore: { type: 'number' }, version: { type: 'string' }, owner: { type: 'string' }, status: { type: 'string', enum: ['approved', 'draft', 'review', 'deprecated'] }, priorityTier: { type: 'string', enum: ['P1', 'P2', 'P3', 'P4'] }, classification: { type: 'string', enum: ['public', 'internal', 'restricted'] }, lastReviewedAt: { type: 'string' }, nextReviewDue: { type: 'string' }, semanticSummary: { type: 'string' }, changeLog: { type: 'array', items: { type: 'object', additionalProperties: true } }, contentType: { type: 'string', enum: ['instruction', 'template', 'workflow', 'reference', 'example', 'agent', 'chat-session'] }, extensions: extensionsInputSchema
|
|
161
|
-
} },
|
|
165
|
+
entry: (0, schemas_1.buildToolInputEntrySchema)({ required: ['id', 'body'] }),
|
|
162
166
|
overwrite: { type: 'boolean' },
|
|
163
167
|
lax: { type: 'boolean' }
|
|
164
168
|
} },
|
|
@@ -208,6 +212,28 @@ const INPUT_SCHEMAS = {
|
|
|
208
212
|
metadata: { type: 'object', additionalProperties: true },
|
|
209
213
|
tags: { type: 'array', maxItems: 10, items: { type: 'string' } }
|
|
210
214
|
} },
|
|
215
|
+
'feedback_manage': { type: 'object', additionalProperties: false, required: ['action'], properties: {
|
|
216
|
+
action: { type: 'string', enum: ['submit', 'list', 'get', 'update', 'delete', 'stats'], description: 'Feedback management action to perform.' },
|
|
217
|
+
id: { type: 'string', description: 'Feedback entry id for get, update, and delete actions.' },
|
|
218
|
+
type: { type: 'string', enum: ['issue', 'status', 'security', 'feature-request', 'bug-report', 'performance', 'usability', 'other'] },
|
|
219
|
+
severity: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] },
|
|
220
|
+
status: { type: 'string', enum: ['new', 'acknowledged', 'in-progress', 'resolved', 'closed'] },
|
|
221
|
+
title: { type: 'string', maxLength: 200 },
|
|
222
|
+
description: { type: 'string', maxLength: 10000 },
|
|
223
|
+
context: { type: 'object', additionalProperties: true, properties: {
|
|
224
|
+
clientInfo: { type: 'object', properties: { name: { type: 'string' }, version: { type: 'string' } } },
|
|
225
|
+
serverVersion: { type: 'string' },
|
|
226
|
+
environment: { type: 'object', additionalProperties: true },
|
|
227
|
+
sessionId: { type: 'string' },
|
|
228
|
+
toolName: { type: 'string' },
|
|
229
|
+
requestId: { type: 'string' }
|
|
230
|
+
} },
|
|
231
|
+
metadata: { type: 'object', additionalProperties: true },
|
|
232
|
+
tags: { type: 'array', maxItems: 10, items: { type: 'string' } },
|
|
233
|
+
limit: { type: 'number', minimum: 1, maximum: 200, description: 'Maximum entries to return for list action.' },
|
|
234
|
+
offset: { type: 'number', minimum: 0, description: 'Pagination offset for list action.' },
|
|
235
|
+
since: { type: 'string', description: 'ISO date filter for list and stats actions.' }
|
|
236
|
+
} },
|
|
211
237
|
// instructions search tool - PRIMARY discovery mechanism
|
|
212
238
|
'index_search': { type: 'object', additionalProperties: false, required: ['keywords'], properties: {
|
|
213
239
|
keywords: {
|
|
@@ -221,7 +247,7 @@ const INPUT_SCHEMAS = {
|
|
|
221
247
|
limit: { type: 'number', minimum: 1, maximum: 100, default: 50, description: 'Maximum number of instruction IDs to return' },
|
|
222
248
|
includeCategories: { type: 'boolean', default: false, description: 'Include categories in search scope' },
|
|
223
249
|
caseSensitive: { type: 'boolean', default: false, description: 'Perform case-sensitive matching' },
|
|
224
|
-
contentType: { type: 'string', enum: [
|
|
250
|
+
contentType: { type: 'string', enum: [...instruction_1.CONTENT_TYPES], description: 'Filter results by content type (optional)' }
|
|
225
251
|
} },
|
|
226
252
|
// promote_from_repo tool
|
|
227
253
|
'promote_from_repo': { type: 'object', additionalProperties: false, required: ['repoPath'], properties: {
|
|
@@ -322,15 +348,16 @@ INPUT_SCHEMAS['trace_dump'] = { type: 'object', additionalProperties: false, pro
|
|
|
322
348
|
} };
|
|
323
349
|
// Stable & mutation classification lists (mirrors usage in toolHandlers; exported to remove duplication there).
|
|
324
350
|
exports.STABLE = new Set(['health_check', 'feedback_submit', 'graph_export', 'index_dispatch', 'index_search', 'index_governanceHash', 'prompt_review', 'integrity_verify', 'usage_track', 'usage_hotset', 'metrics_snapshot', 'gates_evaluate', 'meta_tools', 'help_overview', 'index_schema', 'manifest_status', 'index_diagnostics', 'meta_activation_guide', 'meta_check_activation', 'bootstrap', 'bootstrap_status', 'feature_status', 'index_health', 'index_inspect', 'index_debug', 'integrity_manifest', 'messaging_read', 'messaging_list_channels', 'messaging_stats', 'messaging_get', 'messaging_thread', 'trace_dump']);
|
|
325
|
-
exports.MUTATION = new Set(['index_add', 'index_import', 'index_repair', 'index_reload', 'index_remove', 'index_groom', 'index_enrich', 'index_governanceUpdate', 'index_normalize', 'usage_flush', 'manifest_refresh', 'manifest_repair', 'promote_from_repo', 'bootstrap_request', 'bootstrap_confirmFinalize', 'messaging_send', 'messaging_ack', 'messaging_update', 'messaging_purge', 'messaging_reply', 'diagnostics_block', 'diagnostics_microtaskFlood', 'diagnostics_memoryPressure']);
|
|
351
|
+
exports.MUTATION = new Set(['feedback_manage', 'index_add', 'index_import', 'index_repair', 'index_reload', 'index_remove', 'index_groom', 'index_enrich', 'index_governanceUpdate', 'index_normalize', 'usage_flush', 'manifest_refresh', 'manifest_repair', 'promote_from_repo', 'bootstrap_request', 'bootstrap_confirmFinalize', 'messaging_send', 'messaging_ack', 'messaging_update', 'messaging_purge', 'messaging_reply', 'diagnostics_block', 'diagnostics_microtaskFlood', 'diagnostics_memoryPressure']);
|
|
326
352
|
// Tool tier classification (002-tool-consolidation spec)
|
|
327
353
|
// core: always visible, essential daily use
|
|
328
354
|
// extended: opt-in via INDEX_SERVER_FLAG_TOOLS_EXTENDED=1 or flags.json tools_extended:true
|
|
329
355
|
// admin: opt-in via INDEX_SERVER_FLAG_TOOLS_ADMIN=1, rarely needed ops/debug tools
|
|
330
356
|
const TOOL_TIERS = {
|
|
331
|
-
// Core (
|
|
357
|
+
// Core (feedback_submit remains always visible for agent reporting; feedback_manage is the management dispatcher)
|
|
332
358
|
'health_check': 'core',
|
|
333
359
|
'feedback_submit': 'core',
|
|
360
|
+
'feedback_manage': 'extended',
|
|
334
361
|
'index_dispatch': 'core',
|
|
335
362
|
'index_search': 'core',
|
|
336
363
|
'prompt_review': 'core',
|
|
@@ -459,6 +486,7 @@ function describeTool(name) {
|
|
|
459
486
|
case 'meta_tools': return 'Enumerate available tools & their metadata.';
|
|
460
487
|
// feedback system descriptions
|
|
461
488
|
case 'feedback_submit': return 'Submit feedback entry (issue, status report, security alert, feature request, etc.).';
|
|
489
|
+
case 'feedback_manage': return 'Manage feedback entries through a single action dispatcher. Actions: submit, list, get, update, delete, stats.';
|
|
462
490
|
case 'bootstrap': return 'Unified bootstrap dispatcher. Actions: request, confirm, status.';
|
|
463
491
|
case 'manifest_status': return 'Report index manifest presence and drift summary.';
|
|
464
492
|
case 'manifest_refresh': return 'Rewrite manifest from current index state.';
|