mdkg 0.4.2 → 0.5.0
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 +71 -15
- package/CLI_COMMAND_MATRIX.md +59 -2
- package/README.md +20 -2
- package/dist/cli.js +124 -2
- package/dist/command-contract.json +756 -2
- package/dist/commands/archive.js +31 -14
- package/dist/commands/bundle.js +180 -44
- package/dist/commands/capability.js +1 -0
- package/dist/commands/checkpoint.js +6 -5
- package/dist/commands/event_support.js +16 -7
- package/dist/commands/fix.js +11 -8
- package/dist/commands/git.js +41 -10
- package/dist/commands/goal.js +23 -23
- package/dist/commands/graph.js +64 -10
- package/dist/commands/handoff.js +4 -2
- package/dist/commands/init.js +28 -23
- package/dist/commands/loop.js +1668 -0
- package/dist/commands/loop_descriptors.js +219 -0
- package/dist/commands/mcp.js +101 -11
- package/dist/commands/new.js +49 -3
- package/dist/commands/pack.js +14 -7
- package/dist/commands/search.js +10 -1
- package/dist/commands/skill.js +12 -9
- package/dist/commands/skill_mirror.js +39 -31
- package/dist/commands/subgraph.js +59 -26
- package/dist/commands/task.js +77 -4
- package/dist/commands/upgrade.js +13 -15
- package/dist/commands/validate.js +20 -7
- package/dist/commands/work.js +44 -26
- package/dist/commands/workspace.js +10 -4
- package/dist/core/config.js +45 -17
- package/dist/core/filesystem_authority.js +281 -0
- package/dist/core/project_db_events.js +4 -0
- package/dist/core/project_db_materializer.js +2 -0
- package/dist/core/project_db_migrations.js +238 -181
- package/dist/core/project_db_snapshot.js +64 -14
- package/dist/core/workspace_path.js +7 -0
- package/dist/graph/archive_integrity.js +1 -1
- package/dist/graph/capabilities_index_cache.js +4 -1
- package/dist/graph/frontmatter.js +38 -0
- package/dist/graph/goal_scope.js +4 -1
- package/dist/graph/index_cache.js +37 -7
- package/dist/graph/loop_bindings.js +39 -0
- package/dist/graph/node.js +209 -1
- package/dist/graph/node_body.js +52 -6
- package/dist/graph/skills_index_cache.js +41 -6
- package/dist/graph/skills_indexer.js +5 -2
- package/dist/graph/sqlite_index.js +118 -105
- package/dist/graph/subgraphs.js +142 -5
- package/dist/graph/validate_graph.js +109 -13
- package/dist/graph/workspace_files.js +39 -9
- package/dist/init/AGENT_START.md +16 -0
- package/dist/init/CLI_COMMAND_MATRIX.md +14 -0
- package/dist/init/config.json +7 -1
- package/dist/init/init-manifest.json +49 -4
- package/dist/init/skills/default/pursue-mdkg-loop/SKILL.md +150 -0
- package/dist/init/templates/default/loop.md +160 -0
- package/dist/init/templates/loops/backend-api-cli-bloat-audit.loop.md +117 -0
- package/dist/init/templates/loops/design-frontend-ux-audit.loop.md +117 -0
- package/dist/init/templates/loops/duplicate-code-and-linting-audit.loop.md +114 -0
- package/dist/init/templates/loops/security-audit.loop.md +140 -0
- package/dist/init/templates/loops/tech-stack-best-practices-audit.loop.md +116 -0
- package/dist/init/templates/loops/test-ci-skill-infrastructure-audit.loop.md +116 -0
- package/dist/init/templates/loops/user-story-audit-and-recommendations.loop.md +116 -0
- package/dist/pack/order.js +3 -1
- package/dist/pack/pack.js +139 -6
- package/dist/templates/loader.js +9 -3
- package/dist/util/lock.js +10 -9
- package/dist/util/zip.js +163 -9
- package/package.json +8 -4
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LOOP_COMMAND_DESCRIPTORS = void 0;
|
|
4
|
+
exports.loopDescriptorForSubcommand = loopDescriptorForSubcommand;
|
|
5
|
+
const READ_ONLY_LOOP_SAFETY = {
|
|
6
|
+
json_schema_ref: "mdkg.command_output.v1",
|
|
7
|
+
side_effects: ["none"],
|
|
8
|
+
read_paths: [".mdkg/**"],
|
|
9
|
+
write_paths: [],
|
|
10
|
+
dry_run: { supported: false },
|
|
11
|
+
lock_policy: "none-read-only",
|
|
12
|
+
atomic_write_policy: "none-read-only",
|
|
13
|
+
receipts: ["loop-read-receipt"],
|
|
14
|
+
danger_level: "read-only",
|
|
15
|
+
};
|
|
16
|
+
const LOOP_GRAPH_WRITE_SAFETY = {
|
|
17
|
+
json_schema_ref: "mdkg.command_output.v1",
|
|
18
|
+
side_effects: [
|
|
19
|
+
"create-scoped-loop-and-optional-child-nodes",
|
|
20
|
+
"reserve-sqlite-node-ids-when-configured",
|
|
21
|
+
"rebuild-derived-indexes-when-auto-reindex-is-enabled",
|
|
22
|
+
"append-loop-fork-event-when-event-logging-is-enabled",
|
|
23
|
+
],
|
|
24
|
+
read_paths: [".mdkg/**"],
|
|
25
|
+
write_paths: [".mdkg/**/*.md", ".mdkg/index/**", ".mdkg/events/*.jsonl"],
|
|
26
|
+
dry_run: {
|
|
27
|
+
supported: true,
|
|
28
|
+
flag: "--dry-run",
|
|
29
|
+
side_effects: ["none"],
|
|
30
|
+
write_paths: [],
|
|
31
|
+
reserves_ids: false,
|
|
32
|
+
},
|
|
33
|
+
lock_policy: "mutation-lock-required",
|
|
34
|
+
atomic_write_policy: "exclusive-create-and-atomic-file-writes",
|
|
35
|
+
receipts: ["loop-fork-receipt"],
|
|
36
|
+
danger_level: "moderate",
|
|
37
|
+
};
|
|
38
|
+
const JSON_FLAG = {
|
|
39
|
+
name: "--json",
|
|
40
|
+
value: null,
|
|
41
|
+
required: false,
|
|
42
|
+
description: "Emit deterministic JSON instead of text.",
|
|
43
|
+
};
|
|
44
|
+
const WS_FLAG = {
|
|
45
|
+
name: "--ws",
|
|
46
|
+
value: "<alias>",
|
|
47
|
+
required: false,
|
|
48
|
+
description: "Resolve the command against one workspace alias.",
|
|
49
|
+
};
|
|
50
|
+
const ROOT_FLAG = {
|
|
51
|
+
name: "--root",
|
|
52
|
+
value: "<path>",
|
|
53
|
+
required: false,
|
|
54
|
+
description: "Run against a specific repository root; -r is the short alias.",
|
|
55
|
+
};
|
|
56
|
+
const NO_CACHE_FLAG = {
|
|
57
|
+
name: "--no-cache",
|
|
58
|
+
value: null,
|
|
59
|
+
required: false,
|
|
60
|
+
description: "Build a non-persisting in-memory index projection instead of reading the cache.",
|
|
61
|
+
};
|
|
62
|
+
const NO_REINDEX_FLAG = {
|
|
63
|
+
name: "--no-reindex",
|
|
64
|
+
value: null,
|
|
65
|
+
required: false,
|
|
66
|
+
description: "Do not rebuild a stale or missing index projection.",
|
|
67
|
+
};
|
|
68
|
+
const RUN_ID_FLAG = {
|
|
69
|
+
name: "--run-id",
|
|
70
|
+
value: "<id>",
|
|
71
|
+
required: false,
|
|
72
|
+
description: "Attach an optional run id to the fork event when event logging is enabled.",
|
|
73
|
+
};
|
|
74
|
+
const READ_FLAGS = [ROOT_FLAG, WS_FLAG, JSON_FLAG, NO_CACHE_FLAG, NO_REINDEX_FLAG];
|
|
75
|
+
exports.LOOP_COMMAND_DESCRIPTORS = [
|
|
76
|
+
{
|
|
77
|
+
key: "loop",
|
|
78
|
+
path: ["loop"],
|
|
79
|
+
summary: "mdkg loop command",
|
|
80
|
+
usage: [
|
|
81
|
+
"mdkg loop list [--ws <alias>] [--json]",
|
|
82
|
+
"mdkg loop show <loop-or-template> [--meta] [--ws <alias>] [--json]",
|
|
83
|
+
"mdkg loop fork <template> --scope <scope> [--title <title>] [--materialization default_children|planning_only|manual] [--planning-only] [--no-children] [--dry-run] [--run-id <id>] [--ws <alias>] [--json]",
|
|
84
|
+
"mdkg loop plan <loop> [--ws <alias>] [--json]",
|
|
85
|
+
"mdkg loop next <loop> [--ws <alias>] [--json]",
|
|
86
|
+
"mdkg loop runs <loop> [--ws <alias>] [--json]",
|
|
87
|
+
],
|
|
88
|
+
args: [],
|
|
89
|
+
flags: [ROOT_FLAG, WS_FLAG, JSON_FLAG, NO_CACHE_FLAG, NO_REINDEX_FLAG, RUN_ID_FLAG],
|
|
90
|
+
output_formats: ["text", "json"],
|
|
91
|
+
notes: [
|
|
92
|
+
"loop is one first-class node type. Templates, scoped forks, and run-bearing loops are represented through metadata and links.",
|
|
93
|
+
"mdkg defines reusable process state and graph context; runtimes execute agents, tools, sandboxes, traces, and model routing.",
|
|
94
|
+
],
|
|
95
|
+
safety: {
|
|
96
|
+
...READ_ONLY_LOOP_SAFETY,
|
|
97
|
+
side_effects: ["read-or-write-loop-graph-state"],
|
|
98
|
+
write_paths: [".mdkg/**/*.md", ".mdkg/index/**", ".mdkg/events/*.jsonl"],
|
|
99
|
+
dry_run: { supported: true, commands: ["fork"] },
|
|
100
|
+
lock_policy: "mutation-lock-required-for-fork",
|
|
101
|
+
atomic_write_policy: "exclusive-create-and-atomic-file-writes",
|
|
102
|
+
receipts: ["loop-receipt"],
|
|
103
|
+
danger_level: "mixed",
|
|
104
|
+
},
|
|
105
|
+
handler: "runLoopSubcommand",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: "loop list",
|
|
109
|
+
path: ["loop", "list"],
|
|
110
|
+
summary: "mdkg loop list command",
|
|
111
|
+
usage: ["mdkg loop list [--ws <alias>] [--no-cache] [--no-reindex] [--json]"],
|
|
112
|
+
args: [],
|
|
113
|
+
flags: READ_FLAGS,
|
|
114
|
+
output_formats: ["text", "json"],
|
|
115
|
+
notes: ["Lists indexed loop nodes and reusable seed loop templates."],
|
|
116
|
+
safety: { ...READ_ONLY_LOOP_SAFETY, receipts: ["loop-list-receipt"] },
|
|
117
|
+
handler: "runLoopListCommand",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: "loop show",
|
|
121
|
+
path: ["loop", "show"],
|
|
122
|
+
summary: "mdkg loop show command",
|
|
123
|
+
usage: ["mdkg loop show <loop-or-template> [--meta] [--ws <alias>] [--no-cache] [--no-reindex] [--json]"],
|
|
124
|
+
args: [
|
|
125
|
+
{
|
|
126
|
+
name: "loop-or-template",
|
|
127
|
+
required: true,
|
|
128
|
+
source: "<loop-or-template>",
|
|
129
|
+
description: "Loop node id/qid or seed template name/ref.",
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
flags: [
|
|
133
|
+
{ name: "--meta", value: null, required: false, description: "Show metadata without the full body." },
|
|
134
|
+
...READ_FLAGS,
|
|
135
|
+
],
|
|
136
|
+
output_formats: ["text", "json"],
|
|
137
|
+
notes: ["Shows an indexed loop node or a seed loop template."],
|
|
138
|
+
safety: { ...READ_ONLY_LOOP_SAFETY, receipts: ["loop-show-receipt"] },
|
|
139
|
+
handler: "runLoopShowCommand",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: "loop fork",
|
|
143
|
+
path: ["loop", "fork"],
|
|
144
|
+
summary: "mdkg loop fork command",
|
|
145
|
+
usage: [
|
|
146
|
+
"mdkg loop fork <template> --scope <scope> [--title <title>] [--materialization <mode>] [--planning-only] [--no-children] [--dry-run] [--run-id <id>] [--ws <alias>] [--no-cache] [--no-reindex] [--json]",
|
|
147
|
+
],
|
|
148
|
+
args: [
|
|
149
|
+
{ name: "template", required: true, source: "<template>", description: "Loop template node id/qid or seed template name/ref." },
|
|
150
|
+
],
|
|
151
|
+
flags: [
|
|
152
|
+
{ name: "--scope", value: "<scope>", required: true, description: "Scope ref, qid, URI, path, or description for the scoped loop." },
|
|
153
|
+
{ name: "--title", value: "<title>", required: false, description: "Override the generated scoped loop title." },
|
|
154
|
+
{ name: "--materialization", value: "<mode>", required: false, description: "Child materialization mode: default_children, planning_only, or manual." },
|
|
155
|
+
{ name: "--planning-only", value: null, required: false, description: "Create only the scoped loop shell." },
|
|
156
|
+
{ name: "--no-children", value: null, required: false, description: "Alias for planning-only materialization." },
|
|
157
|
+
{ name: "--dry-run", value: null, required: false, description: "Plan the fork without writing loop or child nodes." },
|
|
158
|
+
RUN_ID_FLAG,
|
|
159
|
+
ROOT_FLAG,
|
|
160
|
+
WS_FLAG,
|
|
161
|
+
JSON_FLAG,
|
|
162
|
+
NO_CACHE_FLAG,
|
|
163
|
+
NO_REINDEX_FLAG,
|
|
164
|
+
],
|
|
165
|
+
output_formats: ["text", "json"],
|
|
166
|
+
notes: [
|
|
167
|
+
"Seed templates resolve from .mdkg/templates/loops/<name>.loop.md.",
|
|
168
|
+
"Default forks create a scoped loop plus linked spike/task/test child nodes.",
|
|
169
|
+
],
|
|
170
|
+
safety: LOOP_GRAPH_WRITE_SAFETY,
|
|
171
|
+
handler: "runLoopForkCommand",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
key: "loop plan",
|
|
175
|
+
path: ["loop", "plan"],
|
|
176
|
+
summary: "mdkg loop plan command",
|
|
177
|
+
usage: ["mdkg loop plan <loop> [--ws <alias>] [--no-cache] [--no-reindex] [--json]"],
|
|
178
|
+
args: [
|
|
179
|
+
{ name: "loop", required: true, source: "<loop>", description: "Loop node id or qid." },
|
|
180
|
+
],
|
|
181
|
+
flags: READ_FLAGS,
|
|
182
|
+
output_formats: ["text", "json"],
|
|
183
|
+
notes: ["Read-only readiness cockpit for loop execution planning."],
|
|
184
|
+
safety: { ...READ_ONLY_LOOP_SAFETY, receipts: ["loop-plan-receipt"] },
|
|
185
|
+
handler: "runLoopPlanCommand",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
key: "loop next",
|
|
189
|
+
path: ["loop", "next"],
|
|
190
|
+
summary: "mdkg loop next command",
|
|
191
|
+
usage: ["mdkg loop next <loop> [--ws <alias>] [--no-cache] [--no-reindex] [--json]"],
|
|
192
|
+
args: [
|
|
193
|
+
{ name: "loop", required: true, source: "<loop>", description: "Loop node id or qid." },
|
|
194
|
+
],
|
|
195
|
+
flags: READ_FLAGS,
|
|
196
|
+
output_formats: ["text", "json"],
|
|
197
|
+
notes: ["Read-only routing for the next actionable child, readiness lane, or blocker recovery step."],
|
|
198
|
+
safety: { ...READ_ONLY_LOOP_SAFETY, receipts: ["loop-next-receipt"] },
|
|
199
|
+
handler: "runLoopNextCommand",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: "loop runs",
|
|
203
|
+
path: ["loop", "runs"],
|
|
204
|
+
summary: "mdkg loop runs command",
|
|
205
|
+
usage: ["mdkg loop runs <loop> [--ws <alias>] [--no-cache] [--no-reindex] [--json]"],
|
|
206
|
+
args: [
|
|
207
|
+
{ name: "loop", required: true, source: "<loop>", description: "Loop node id or qid." },
|
|
208
|
+
],
|
|
209
|
+
flags: READ_FLAGS,
|
|
210
|
+
output_formats: ["text", "json"],
|
|
211
|
+
notes: ["Lists run refs and evidence refs without executing runtime jobs."],
|
|
212
|
+
safety: { ...READ_ONLY_LOOP_SAFETY, receipts: ["loop-runs-receipt"] },
|
|
213
|
+
handler: "runLoopRunsCommand",
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
function loopDescriptorForSubcommand(subcommand) {
|
|
217
|
+
const key = subcommand ? `loop ${subcommand.toLowerCase()}` : "loop";
|
|
218
|
+
return exports.LOOP_COMMAND_DESCRIPTORS.find((descriptor) => descriptor.key === key) ?? exports.LOOP_COMMAND_DESCRIPTORS[0];
|
|
219
|
+
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.handleMcpRequest = handleMcpRequest;
|
|
7
7
|
exports.handleMcpMessage = handleMcpMessage;
|
|
8
8
|
exports.runMcpServeCommand = runMcpServeCommand;
|
|
9
|
-
const readline_1 = __importDefault(require("readline"));
|
|
10
9
|
const fs_1 = __importDefault(require("fs"));
|
|
11
10
|
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const string_decoder_1 = require("string_decoder");
|
|
12
12
|
const status_1 = require("./status");
|
|
13
13
|
const validate_1 = require("./validate");
|
|
14
14
|
const config_1 = require("../core/config");
|
|
@@ -33,6 +33,12 @@ const DEFAULT_SEARCH_LIMIT = 20;
|
|
|
33
33
|
const MAX_PACK_NODES = 100;
|
|
34
34
|
const DEFAULT_PACK_NODES = 20;
|
|
35
35
|
const DEFAULT_PACK_MAX_CHARS = 120000;
|
|
36
|
+
const MAX_MCP_LINE_BYTES = 1024 * 1024;
|
|
37
|
+
const MAX_MCP_JSON_DEPTH = 64;
|
|
38
|
+
const MAX_MCP_BATCH_ITEMS = 50;
|
|
39
|
+
const MAX_MCP_SHOW_BODY_BYTES = 256 * 1024;
|
|
40
|
+
const MAX_MCP_TOOL_PAYLOAD_BYTES = 512 * 1024;
|
|
41
|
+
const MAX_MCP_RESPONSE_BYTES = 1024 * 1024;
|
|
36
42
|
function objectSchema(properties, required = []) {
|
|
37
43
|
return {
|
|
38
44
|
type: "object",
|
|
@@ -312,6 +318,10 @@ function isConcreteGoalCandidate(node, statusRanks) {
|
|
|
312
318
|
return node.status !== "done" && node.status !== "archived";
|
|
313
319
|
}
|
|
314
320
|
function toolResult(payload, isError = false) {
|
|
321
|
+
const compact = JSON.stringify(payload);
|
|
322
|
+
if (Buffer.byteLength(compact, "utf8") > MAX_MCP_TOOL_PAYLOAD_BYTES) {
|
|
323
|
+
throw new errors_1.UsageError(`MCP tool payload exceeds byte limit: ${MAX_MCP_TOOL_PAYLOAD_BYTES}`);
|
|
324
|
+
}
|
|
315
325
|
return {
|
|
316
326
|
content: [
|
|
317
327
|
{
|
|
@@ -388,7 +398,7 @@ function showTool(root, args) {
|
|
|
388
398
|
}
|
|
389
399
|
return {
|
|
390
400
|
command: "mcp.show",
|
|
391
|
-
item: (0, query_output_1.toNodeDetailJson)(node, metaOnly ? undefined : (0, node_body_1.readNodeBody)(root, node)),
|
|
401
|
+
item: (0, query_output_1.toNodeDetailJson)(node, metaOnly ? undefined : (0, node_body_1.readNodeBody)(root, node, MAX_MCP_SHOW_BODY_BYTES)),
|
|
392
402
|
};
|
|
393
403
|
}
|
|
394
404
|
function packTool(root, args) {
|
|
@@ -414,6 +424,8 @@ function packTool(root, args) {
|
|
|
414
424
|
verboseCoreListPath: path_1.default.resolve(root, config.pack.verbose_core_list_path),
|
|
415
425
|
wsHint: ws,
|
|
416
426
|
includeLatestCheckpoint: true,
|
|
427
|
+
maxTraversalNodes: MAX_PACK_NODES * 10,
|
|
428
|
+
maxBodyBytes: maxChars,
|
|
417
429
|
});
|
|
418
430
|
const headingMap = resolvedProfile.bodyMode === "summary"
|
|
419
431
|
? (0, headings_1.loadTemplateHeadingMap)(root, config, Array.from(node_1.ALLOWED_TYPES))
|
|
@@ -617,6 +629,9 @@ function handleMcpRequest(context, raw) {
|
|
|
617
629
|
}
|
|
618
630
|
function handleMcpMessage(context, message) {
|
|
619
631
|
if (Array.isArray(message)) {
|
|
632
|
+
if (message.length === 0 || message.length > MAX_MCP_BATCH_ITEMS) {
|
|
633
|
+
return [jsonRpcError(null, -32600, `batch item count must be between 1 and ${MAX_MCP_BATCH_ITEMS}`)];
|
|
634
|
+
}
|
|
620
635
|
return message
|
|
621
636
|
.map((item) => handleMcpRequest(context, item))
|
|
622
637
|
.filter((item) => Boolean(item));
|
|
@@ -624,33 +639,108 @@ function handleMcpMessage(context, message) {
|
|
|
624
639
|
const response = handleMcpRequest(context, message);
|
|
625
640
|
return response ? [response] : [];
|
|
626
641
|
}
|
|
642
|
+
function jsonNestingWithinLimit(input) {
|
|
643
|
+
let depth = 0;
|
|
644
|
+
let inString = false;
|
|
645
|
+
let escaped = false;
|
|
646
|
+
for (const character of input) {
|
|
647
|
+
if (inString) {
|
|
648
|
+
if (escaped) {
|
|
649
|
+
escaped = false;
|
|
650
|
+
}
|
|
651
|
+
else if (character === "\\") {
|
|
652
|
+
escaped = true;
|
|
653
|
+
}
|
|
654
|
+
else if (character === '"') {
|
|
655
|
+
inString = false;
|
|
656
|
+
}
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (character === '"') {
|
|
660
|
+
inString = true;
|
|
661
|
+
}
|
|
662
|
+
else if (character === "{" || character === "[") {
|
|
663
|
+
depth += 1;
|
|
664
|
+
if (depth > MAX_MCP_JSON_DEPTH) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
else if (character === "}" || character === "]") {
|
|
669
|
+
depth -= 1;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
function writeBoundedMcpResponse(output, response) {
|
|
675
|
+
let serialized = JSON.stringify(response);
|
|
676
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_MCP_RESPONSE_BYTES) {
|
|
677
|
+
serialized = JSON.stringify(jsonRpcError(response.id, -32000, `MCP response exceeds byte limit: ${MAX_MCP_RESPONSE_BYTES}`));
|
|
678
|
+
}
|
|
679
|
+
output.write(`${serialized}\n`);
|
|
680
|
+
}
|
|
627
681
|
async function runMcpServeCommand(options) {
|
|
628
682
|
if (!options.stdio) {
|
|
629
683
|
throw new errors_1.UsageError("mdkg mcp serve requires --stdio");
|
|
630
684
|
}
|
|
631
685
|
const input = options.input ?? process.stdin;
|
|
632
686
|
const output = options.output ?? process.stdout;
|
|
633
|
-
const lines = readline_1.default.createInterface({
|
|
634
|
-
input,
|
|
635
|
-
crlfDelay: Infinity,
|
|
636
|
-
});
|
|
637
687
|
const context = { root: options.root };
|
|
638
|
-
|
|
688
|
+
const decoder = new string_decoder_1.StringDecoder("utf8");
|
|
689
|
+
let pending = "";
|
|
690
|
+
let discardingOversizedLine = false;
|
|
691
|
+
const processLine = (line) => {
|
|
639
692
|
const trimmed = line.trim();
|
|
640
693
|
if (!trimmed) {
|
|
641
|
-
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (!jsonNestingWithinLimit(trimmed)) {
|
|
697
|
+
writeBoundedMcpResponse(output, jsonRpcError(null, -32600, `JSON nesting exceeds limit: ${MAX_MCP_JSON_DEPTH}`));
|
|
698
|
+
return;
|
|
642
699
|
}
|
|
643
700
|
let parsed;
|
|
644
701
|
try {
|
|
645
702
|
parsed = JSON.parse(trimmed);
|
|
646
703
|
}
|
|
647
704
|
catch {
|
|
648
|
-
output
|
|
649
|
-
|
|
705
|
+
writeBoundedMcpResponse(output, jsonRpcError(null, -32700, "Parse error"));
|
|
706
|
+
return;
|
|
650
707
|
}
|
|
651
708
|
for (const response of handleMcpMessage(context, parsed)) {
|
|
652
|
-
output
|
|
709
|
+
writeBoundedMcpResponse(output, response);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
for await (const chunk of input) {
|
|
713
|
+
const text = decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
714
|
+
let start = 0;
|
|
715
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
716
|
+
if (text[index] !== "\n") {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const segment = text.slice(start, index).replace(/\r$/, "");
|
|
720
|
+
if (discardingOversizedLine) {
|
|
721
|
+
discardingOversizedLine = false;
|
|
722
|
+
}
|
|
723
|
+
else if (Buffer.byteLength(pending, "utf8") + Buffer.byteLength(segment, "utf8") > MAX_MCP_LINE_BYTES) {
|
|
724
|
+
writeBoundedMcpResponse(output, jsonRpcError(null, -32600, `request line exceeds byte limit: ${MAX_MCP_LINE_BYTES}`));
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
processLine(pending + segment);
|
|
728
|
+
}
|
|
729
|
+
pending = "";
|
|
730
|
+
start = index + 1;
|
|
653
731
|
}
|
|
732
|
+
if (start < text.length && !discardingOversizedLine) {
|
|
733
|
+
pending += text.slice(start);
|
|
734
|
+
if (Buffer.byteLength(pending, "utf8") > MAX_MCP_LINE_BYTES) {
|
|
735
|
+
pending = "";
|
|
736
|
+
discardingOversizedLine = true;
|
|
737
|
+
writeBoundedMcpResponse(output, jsonRpcError(null, -32600, `request line exceeds byte limit: ${MAX_MCP_LINE_BYTES}`));
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
pending += decoder.end();
|
|
742
|
+
if (!discardingOversizedLine && pending.trim()) {
|
|
743
|
+
processLine(pending);
|
|
654
744
|
}
|
|
655
745
|
return 0;
|
|
656
746
|
}
|
package/dist/commands/new.js
CHANGED
|
@@ -18,7 +18,8 @@ const errors_1 = require("../util/errors");
|
|
|
18
18
|
const qid_1 = require("../util/qid");
|
|
19
19
|
const id_1 = require("../util/id");
|
|
20
20
|
const refs_1 = require("../util/refs");
|
|
21
|
-
const
|
|
21
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
22
|
+
const workspace_path_1 = require("../core/workspace_path");
|
|
22
23
|
const lock_1 = require("../util/lock");
|
|
23
24
|
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
24
25
|
const reindex_1 = require("../graph/reindex");
|
|
@@ -30,6 +31,38 @@ const CONTRACT_METADATA_RE = /^[a-z][a-z0-9_]*(?:-[a-z0-9_]+)*$/;
|
|
|
30
31
|
const CORE_TYPES = new Set(["rule"]);
|
|
31
32
|
const DESIGN_TYPES = new Set(["prd", "edd", "dec", "prop"]);
|
|
32
33
|
const LEGACY_NEW_SPEC_WARNING = "warning: mdkg new spec is legacy; use mdkg new manifest. This alias creates MANIFEST.md with type: manifest during the compatibility release.";
|
|
34
|
+
const NEW_LOOP_NEXT_ACTIONS = [
|
|
35
|
+
"Review reusable loop templates with mdkg loop list",
|
|
36
|
+
"Fork a reusable template with mdkg loop fork <template> --scope <scope>",
|
|
37
|
+
"Inspect raw loop readiness with mdkg loop plan <loop-id>",
|
|
38
|
+
];
|
|
39
|
+
function toPosix(value) {
|
|
40
|
+
return value.split(path_1.default.sep).join("/");
|
|
41
|
+
}
|
|
42
|
+
function loopTemplateSuggestions(root, config) {
|
|
43
|
+
const dir = path_1.default.resolve(root, config.templates.root_path, "loops");
|
|
44
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return fs_1.default
|
|
48
|
+
.readdirSync(dir)
|
|
49
|
+
.filter((entry) => entry.endsWith(".loop.md"))
|
|
50
|
+
.sort()
|
|
51
|
+
.map((entry) => {
|
|
52
|
+
const filePath = path_1.default.join(dir, entry);
|
|
53
|
+
const content = fs_1.default.readFileSync(filePath, "utf8");
|
|
54
|
+
const { frontmatter } = (0, frontmatter_1.parseFrontmatter)(content, filePath);
|
|
55
|
+
const slug = entry.replace(/\.loop\.md$/, "");
|
|
56
|
+
const title = typeof frontmatter.title === "string" && frontmatter.title.trim().length > 0
|
|
57
|
+
? frontmatter.title
|
|
58
|
+
: slug;
|
|
59
|
+
return {
|
|
60
|
+
ref: `template://loops/${slug}`,
|
|
61
|
+
title,
|
|
62
|
+
path: toPosix(path_1.default.relative(root, filePath)),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
33
66
|
function parseCsvList(raw) {
|
|
34
67
|
if (!raw) {
|
|
35
68
|
return [];
|
|
@@ -249,9 +282,10 @@ function runNewCommandLocked(options) {
|
|
|
249
282
|
const fileName = fileNameForType(type, id, slug);
|
|
250
283
|
const wsEntry = config.workspaces[ws];
|
|
251
284
|
const folder = folderForType(type);
|
|
285
|
+
const relativeFilePath = (0, workspace_path_1.workspaceDocumentRelativePath)(wsEntry.path, wsEntry.mdkg_dir, folder, fileName);
|
|
252
286
|
const targetDir = path_1.default.resolve(options.root, wsEntry.path, wsEntry.mdkg_dir, folder);
|
|
253
287
|
const filePath = path_1.default.join(targetDir, fileName);
|
|
254
|
-
if (
|
|
288
|
+
if ((0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath: relativeFilePath })) {
|
|
255
289
|
throw new errors_1.UsageError(`node already exists: ${path_1.default.relative(options.root, filePath)}`);
|
|
256
290
|
}
|
|
257
291
|
const statusInput = options.status?.toLowerCase();
|
|
@@ -410,7 +444,7 @@ function runNewCommandLocked(options) {
|
|
|
410
444
|
redaction_class: redactionClass,
|
|
411
445
|
});
|
|
412
446
|
try {
|
|
413
|
-
(0,
|
|
447
|
+
(0, filesystem_authority_1.writeContainedFileExclusive)({ root: options.root, relativePath: relativeFilePath }, content);
|
|
414
448
|
}
|
|
415
449
|
catch (err) {
|
|
416
450
|
const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : "";
|
|
@@ -447,14 +481,26 @@ function runNewCommandLocked(options) {
|
|
|
447
481
|
if (legacySpecAlias) {
|
|
448
482
|
console.error(LEGACY_NEW_SPEC_WARNING);
|
|
449
483
|
}
|
|
484
|
+
const loopGuidance = type === "loop"
|
|
485
|
+
? {
|
|
486
|
+
next_actions: NEW_LOOP_NEXT_ACTIONS,
|
|
487
|
+
suggested_templates: loopTemplateSuggestions(options.root, config),
|
|
488
|
+
}
|
|
489
|
+
: undefined;
|
|
450
490
|
if (options.json) {
|
|
451
491
|
console.log(JSON.stringify({
|
|
452
492
|
action: "created",
|
|
453
493
|
node: receipt,
|
|
494
|
+
...(loopGuidance ? loopGuidance : {}),
|
|
454
495
|
}, null, 2));
|
|
455
496
|
return;
|
|
456
497
|
}
|
|
457
498
|
console.log(`node created: ${receipt.qid} (${receipt.path})`);
|
|
499
|
+
if (loopGuidance) {
|
|
500
|
+
console.log("next: review reusable loop templates with `mdkg loop list`");
|
|
501
|
+
console.log("next: fork a reusable template with `mdkg loop fork <template> --scope <scope>`");
|
|
502
|
+
console.log(`next: inspect raw loop readiness with \`mdkg loop plan ${receipt.id}\``);
|
|
503
|
+
}
|
|
458
504
|
}
|
|
459
505
|
function runNewCommand(options) {
|
|
460
506
|
const config = (0, config_1.loadConfig)(options.root);
|
package/dist/commands/pack.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.runPackCommand = runPackCommand;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const config_1 = require("../core/config");
|
|
10
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
10
11
|
const index_cache_1 = require("../graph/index_cache");
|
|
11
12
|
const frontmatter_1 = require("../graph/frontmatter");
|
|
12
13
|
const skills_index_cache_1 = require("../graph/skills_index_cache");
|
|
@@ -138,8 +139,12 @@ function loadSkillFullBody(root, skill) {
|
|
|
138
139
|
if (!fs_1.default.existsSync(skillPath)) {
|
|
139
140
|
return renderSkillMetaBody(skill);
|
|
140
141
|
}
|
|
141
|
-
const content =
|
|
142
|
-
|
|
142
|
+
const content = (0, filesystem_authority_1.readContainedFile)({ root, relativePath: skill.path }, "utf8");
|
|
143
|
+
const parsed = (0, frontmatter_1.parseFrontmatter)(content, skillPath);
|
|
144
|
+
if (parsed.frontmatter.name !== skill.name) {
|
|
145
|
+
throw new Error(`cached skill identity mismatch for ${skill.qid}`);
|
|
146
|
+
}
|
|
147
|
+
return parsed.body.trimEnd();
|
|
143
148
|
}
|
|
144
149
|
function uniqueSkillOrder(slugs) {
|
|
145
150
|
const seen = new Set();
|
|
@@ -323,10 +328,13 @@ function runPackCommand(options) {
|
|
|
323
328
|
if (ws && !config.workspaces[ws] && !config.subgraphs[ws]) {
|
|
324
329
|
throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
|
|
325
330
|
}
|
|
331
|
+
const visibility = options.visibility
|
|
332
|
+
? (0, visibility_1.normalizeVisibility)(options.visibility)
|
|
333
|
+
: undefined;
|
|
326
334
|
const { index, rebuilt, stale, warnings } = (0, index_cache_1.loadIndex)({
|
|
327
335
|
root: options.root,
|
|
328
336
|
config,
|
|
329
|
-
useCache: !options.noCache,
|
|
337
|
+
useCache: !options.noCache && !visibility,
|
|
330
338
|
allowReindex: !options.noReindex,
|
|
331
339
|
});
|
|
332
340
|
if (stale && !rebuilt && !options.noCache) {
|
|
@@ -347,9 +355,6 @@ function runPackCommand(options) {
|
|
|
347
355
|
const edges = normalizeEdges([...config.pack.default_edges, ...extraEdges]);
|
|
348
356
|
const skillsPolicy = resolveSkillsPolicy(options.skills);
|
|
349
357
|
const skillsDepth = resolveSkillsDepth(options.skillsDepth);
|
|
350
|
-
const visibility = options.visibility
|
|
351
|
-
? (0, visibility_1.normalizeVisibility)(options.visibility)
|
|
352
|
-
: undefined;
|
|
353
358
|
let resolvedProfile;
|
|
354
359
|
try {
|
|
355
360
|
resolvedProfile = (0, profile_1.resolvePackProfile)({
|
|
@@ -374,6 +379,8 @@ function runPackCommand(options) {
|
|
|
374
379
|
edges,
|
|
375
380
|
verbose: Boolean(options.verbose),
|
|
376
381
|
maxNodes: config.pack.limits.max_nodes,
|
|
382
|
+
maxTraversalNodes: Math.min(config.index.limits.max_files, Math.max(1000, config.pack.limits.max_nodes * 10)),
|
|
383
|
+
maxBodyBytes: config.pack.limits.max_bytes,
|
|
377
384
|
verboseCoreListPath: path_1.default.resolve(options.root, config.pack.verbose_core_list_path),
|
|
378
385
|
wsHint: ws,
|
|
379
386
|
includeLatestCheckpoint: true,
|
|
@@ -386,7 +393,7 @@ function runPackCommand(options) {
|
|
|
386
393
|
const skillsLoad = (0, skills_index_cache_1.loadSkillsIndex)({
|
|
387
394
|
root: options.root,
|
|
388
395
|
config,
|
|
389
|
-
useCache: !options.noCache,
|
|
396
|
+
useCache: !options.noCache && !visibility,
|
|
390
397
|
allowReindex: !options.noReindex,
|
|
391
398
|
});
|
|
392
399
|
if (skillsLoad.stale && !skillsLoad.rebuilt && !options.noCache) {
|
package/dist/commands/search.js
CHANGED
|
@@ -8,6 +8,7 @@ const errors_1 = require("../util/errors");
|
|
|
8
8
|
const sort_1 = require("../util/sort");
|
|
9
9
|
const node_card_1 = require("./node_card");
|
|
10
10
|
const query_output_1 = require("./query_output");
|
|
11
|
+
const DEFAULT_STRUCTURED_SEARCH_LIMIT = 50;
|
|
11
12
|
function normalizeWorkspace(value) {
|
|
12
13
|
if (!value || value === "all") {
|
|
13
14
|
return undefined;
|
|
@@ -84,11 +85,19 @@ function runSearchCommand(options) {
|
|
|
84
85
|
const sorted = (0, sort_1.sortNodesByQid)(nodeResults);
|
|
85
86
|
const format = options.format ?? (options.json ? "json" : undefined);
|
|
86
87
|
if (format) {
|
|
88
|
+
const limit = options.limit ?? DEFAULT_STRUCTURED_SEARCH_LIMIT;
|
|
89
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
90
|
+
throw new errors_1.UsageError("--limit must be a positive integer");
|
|
91
|
+
}
|
|
92
|
+
const items = sorted.slice(0, limit);
|
|
87
93
|
(0, query_output_1.writeStructuredOutput)({
|
|
88
94
|
command: "search",
|
|
89
95
|
kind: "node",
|
|
90
96
|
count: sorted.length,
|
|
91
|
-
|
|
97
|
+
returned_count: items.length,
|
|
98
|
+
limit,
|
|
99
|
+
truncated: items.length < sorted.length,
|
|
100
|
+
items: items.map(query_output_1.toNodeSummaryJson),
|
|
92
101
|
}, format);
|
|
93
102
|
return;
|
|
94
103
|
}
|
package/dist/commands/skill.js
CHANGED
|
@@ -12,6 +12,7 @@ exports.runSkillSyncCommand = runSkillSyncCommand;
|
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const config_1 = require("../core/config");
|
|
15
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
15
16
|
const skills_indexer_1 = require("../graph/skills_indexer");
|
|
16
17
|
const skills_index_cache_1 = require("../graph/skills_index_cache");
|
|
17
18
|
const skills_index_cache_2 = require("../graph/skills_index_cache");
|
|
@@ -20,7 +21,6 @@ const skill_support_1 = require("./skill_support");
|
|
|
20
21
|
const query_output_1 = require("./query_output");
|
|
21
22
|
const event_support_1 = require("./event_support");
|
|
22
23
|
const skill_mirror_1 = require("./skill_mirror");
|
|
23
|
-
const atomic_1 = require("../util/atomic");
|
|
24
24
|
const lock_1 = require("../util/lock");
|
|
25
25
|
function parseCsvList(raw) {
|
|
26
26
|
if (!raw) {
|
|
@@ -170,11 +170,12 @@ function runSkillNewCommandLocked(options) {
|
|
|
170
170
|
if (fs_1.default.existsSync(canonicalPath) && !force) {
|
|
171
171
|
throw new errors_1.UsageError(`skill already exists: ${path_1.default.relative(root, canonicalPath)} (use --force to overwrite)`);
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
const relativeSkillDir = path_1.default.relative(root, skillDir).split(path_1.default.sep).join("/");
|
|
174
|
+
(0, filesystem_authority_1.ensureContainedDirectory)({ root, relativePath: relativeSkillDir });
|
|
175
|
+
(0, filesystem_authority_1.ensureContainedDirectory)({ root, relativePath: `${relativeSkillDir}/references` });
|
|
176
|
+
(0, filesystem_authority_1.ensureContainedDirectory)({ root, relativePath: `${relativeSkillDir}/assets` });
|
|
176
177
|
if (options.withScripts) {
|
|
177
|
-
|
|
178
|
+
(0, filesystem_authority_1.ensureContainedDirectory)({ root, relativePath: `${relativeSkillDir}/scripts` });
|
|
178
179
|
}
|
|
179
180
|
const content = (0, skill_support_1.renderSkillTemplate)({
|
|
180
181
|
name,
|
|
@@ -183,7 +184,7 @@ function runSkillNewCommandLocked(options) {
|
|
|
183
184
|
authors,
|
|
184
185
|
links,
|
|
185
186
|
});
|
|
186
|
-
(0,
|
|
187
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: `${relativeSkillDir}/SKILL.md` }, content);
|
|
187
188
|
(0, skill_support_1.ensureSkillsRegistry)(root, config);
|
|
188
189
|
(0, skill_support_1.refreshSkillsRegistry)(root, config);
|
|
189
190
|
if ((0, skill_mirror_1.shouldMaintainSkillMirrors)(root, config)) {
|
|
@@ -233,6 +234,7 @@ function runSkillListCommand(options) {
|
|
|
233
234
|
config,
|
|
234
235
|
useCache: !options.noCache,
|
|
235
236
|
allowReindex: !options.noReindex,
|
|
237
|
+
persistReindex: false,
|
|
236
238
|
});
|
|
237
239
|
if (stale && !rebuilt && !options.noCache) {
|
|
238
240
|
console.error("warning: skills index is stale; run mdkg index to refresh");
|
|
@@ -261,6 +263,7 @@ function runSkillShowCommand(options) {
|
|
|
261
263
|
config,
|
|
262
264
|
useCache: !options.noCache,
|
|
263
265
|
allowReindex: !options.noReindex,
|
|
266
|
+
persistReindex: false,
|
|
264
267
|
});
|
|
265
268
|
if (stale && !rebuilt && !options.noCache) {
|
|
266
269
|
console.error("warning: skills index is stale; run mdkg index to refresh");
|
|
@@ -269,13 +272,12 @@ function runSkillShowCommand(options) {
|
|
|
269
272
|
if (!skill) {
|
|
270
273
|
throw new errors_1.NotFoundError(`skill not found: ${slug}`);
|
|
271
274
|
}
|
|
272
|
-
const skillPath = path_1.default.resolve(options.root, skill.path);
|
|
273
275
|
let body = "";
|
|
274
276
|
if (!options.metaOnly) {
|
|
275
|
-
if (!
|
|
277
|
+
if (!(0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath: skill.path })) {
|
|
276
278
|
throw new errors_1.NotFoundError(`file not found for ${skill.id}: ${skill.path}`);
|
|
277
279
|
}
|
|
278
|
-
body =
|
|
280
|
+
body = (0, filesystem_authority_1.readContainedFile)({ root: options.root, relativePath: skill.path }).trimEnd();
|
|
279
281
|
}
|
|
280
282
|
const format = options.format ?? (options.json ? "json" : undefined);
|
|
281
283
|
if (format) {
|
|
@@ -347,6 +349,7 @@ function runSkillSearchCommand(options) {
|
|
|
347
349
|
config,
|
|
348
350
|
useCache: !options.noCache,
|
|
349
351
|
allowReindex: !options.noReindex,
|
|
352
|
+
persistReindex: false,
|
|
350
353
|
});
|
|
351
354
|
if (stale && !rebuilt && !options.noCache) {
|
|
352
355
|
console.error("warning: skills index is stale; run mdkg index to refresh");
|