@intentius/chant 0.49.0 → 0.51.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/dist/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/op-progress.d.ts +57 -0
- package/dist/cli/handlers/op-progress.d.ts.map +1 -0
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/run-client.d.ts +21 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run-report.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/handlers/search.d.ts +22 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +47 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +7 -5
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +134 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts +77 -0
- package/dist/lifecycle/assert-live.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +17 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/disruption.d.ts +96 -0
- package/dist/lifecycle/disruption.d.ts.map +1 -0
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/replay.d.ts +2 -0
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +7 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/testing.d.ts +23 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.test.ts +90 -0
- package/src/cli/handlers/lifecycle.ts +30 -5
- package/src/cli/handlers/op-progress.test.ts +202 -0
- package/src/cli/handlers/op-progress.ts +192 -0
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/run-client.test.ts +82 -0
- package/src/cli/handlers/run-client.ts +85 -2
- package/src/cli/handlers/run-report.test.ts +62 -0
- package/src/cli/handlers/run-report.ts +20 -58
- package/src/cli/handlers/run.test.ts +144 -0
- package/src/cli/handlers/run.ts +40 -18
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/handlers/search-drift.test.ts +263 -0
- package/src/cli/handlers/search.ts +150 -1
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +81 -1
- package/src/cli/mcp/op-tools.ts +17 -6
- package/src/cli/mcp/resource-handlers.ts +13 -5
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +47 -3
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/run-progress.ts +9 -5
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +154 -2
- package/src/lifecycle/assert-live.test.ts +125 -0
- package/src/lifecycle/assert-live.ts +154 -0
- package/src/lifecycle/change-set.ts +35 -3
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/disruption.test.ts +186 -0
- package/src/lifecycle/disruption.ts +224 -0
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/replay.test.ts +25 -0
- package/src/lifecycle/replay.ts +11 -3
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +52 -10
- package/src/op/local-output.ts +1 -1
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/testing.test.ts +89 -2
- package/src/testing.ts +63 -3
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
|
@@ -47,7 +47,8 @@ describe("McpServer", () => {
|
|
|
47
47
|
|
|
48
48
|
expect(response.error).toBeUndefined();
|
|
49
49
|
const result = response.result as Record<string, unknown>;
|
|
50
|
-
|
|
50
|
+
// No requested version → negotiates to the latest supported revision (#1194).
|
|
51
|
+
expect(result.protocolVersion).toBe("2026-07-28");
|
|
51
52
|
expect(result.capabilities).toBeDefined();
|
|
52
53
|
expect((result.serverInfo as Record<string, unknown>).name).toBe("chant");
|
|
53
54
|
expect((result.serverInfo as Record<string, unknown>).version).toBe("0.1.0");
|
|
@@ -60,11 +61,273 @@ describe("McpServer", () => {
|
|
|
60
61
|
method: "initialize",
|
|
61
62
|
params: {},
|
|
62
63
|
});
|
|
63
|
-
const result = response
|
|
64
|
+
const result = response!.result as Record<string, unknown>;
|
|
64
65
|
const caps = result.capabilities as Record<string, unknown>;
|
|
65
66
|
expect(caps.tools).toBeDefined();
|
|
66
67
|
expect(caps.resources).toBeDefined();
|
|
67
68
|
});
|
|
69
|
+
|
|
70
|
+
// -----------------------------------------------------------------------
|
|
71
|
+
// Dual-revision negotiation (#1194)
|
|
72
|
+
// -----------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
test("a 2024-11-05 client requesting its own version gets it back unchanged", async () => {
|
|
75
|
+
const response = await server.handleRequest({
|
|
76
|
+
jsonrpc: "2.0",
|
|
77
|
+
id: 1,
|
|
78
|
+
method: "initialize",
|
|
79
|
+
params: { protocolVersion: "2024-11-05", clientInfo: { name: "old-client", version: "1.0" } },
|
|
80
|
+
});
|
|
81
|
+
expect(response!.error).toBeUndefined();
|
|
82
|
+
const result = response!.result as Record<string, unknown>;
|
|
83
|
+
expect(result.protocolVersion).toBe("2024-11-05");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("a 2026-07-28 client requesting its version gets the latest revision", async () => {
|
|
87
|
+
const response = await server.handleRequest({
|
|
88
|
+
jsonrpc: "2.0",
|
|
89
|
+
id: 1,
|
|
90
|
+
method: "initialize",
|
|
91
|
+
params: { _meta: { protocolVersion: "2026-07-28" } },
|
|
92
|
+
});
|
|
93
|
+
expect(response!.error).toBeUndefined();
|
|
94
|
+
const result = response!.result as Record<string, unknown>;
|
|
95
|
+
expect(result.protocolVersion).toBe("2026-07-28");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("an unrecognized requested version falls back to the latest supported", async () => {
|
|
99
|
+
const response = await server.handleRequest({
|
|
100
|
+
jsonrpc: "2.0",
|
|
101
|
+
id: 1,
|
|
102
|
+
method: "initialize",
|
|
103
|
+
params: { protocolVersion: "1999-01-01" },
|
|
104
|
+
});
|
|
105
|
+
const result = response!.result as Record<string, unknown>;
|
|
106
|
+
expect(result.protocolVersion).toBe("2026-07-28");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("no requested version at all defaults to the latest supported", async () => {
|
|
110
|
+
const response = await server.handleRequest({
|
|
111
|
+
jsonrpc: "2.0",
|
|
112
|
+
id: 1,
|
|
113
|
+
method: "initialize",
|
|
114
|
+
params: {},
|
|
115
|
+
});
|
|
116
|
+
const result = response!.result as Record<string, unknown>;
|
|
117
|
+
expect(result.protocolVersion).toBe("2026-07-28");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// -----------------------------------------------------------------------
|
|
122
|
+
// _meta parsing (#1194)
|
|
123
|
+
// -----------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
describe("_meta parsing", () => {
|
|
126
|
+
test("reads requested protocol version from _meta on initialize", async () => {
|
|
127
|
+
const response = await server.handleRequest({
|
|
128
|
+
jsonrpc: "2.0",
|
|
129
|
+
id: 1,
|
|
130
|
+
method: "initialize",
|
|
131
|
+
params: { _meta: { protocolVersion: "2024-11-05" } },
|
|
132
|
+
});
|
|
133
|
+
const result = response!.result as Record<string, unknown>;
|
|
134
|
+
expect(result.protocolVersion).toBe("2024-11-05");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("_meta protocolVersion takes precedence over a legacy top-level one", async () => {
|
|
138
|
+
const response = await server.handleRequest({
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
id: 1,
|
|
141
|
+
method: "initialize",
|
|
142
|
+
params: { protocolVersion: "2024-11-05", _meta: { protocolVersion: "2026-07-28" } },
|
|
143
|
+
});
|
|
144
|
+
const result = response!.result as Record<string, unknown>;
|
|
145
|
+
expect(result.protocolVersion).toBe("2026-07-28");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("client identity in _meta does not interfere with tool dispatch", async () => {
|
|
149
|
+
const response = await server.handleRequest({
|
|
150
|
+
jsonrpc: "2.0",
|
|
151
|
+
id: 1,
|
|
152
|
+
method: "tools/call",
|
|
153
|
+
params: {
|
|
154
|
+
name: "search",
|
|
155
|
+
arguments: { query: "bucket" },
|
|
156
|
+
_meta: {
|
|
157
|
+
protocolVersion: "2026-07-28",
|
|
158
|
+
"io.modelcontextprotocol/clientInfo": { name: "some-agent", version: "3.0" },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
expect(response!.error).toBeUndefined();
|
|
163
|
+
const result = response!.result as { content: Array<{ text: string }>; isError?: boolean };
|
|
164
|
+
expect(result.isError).toBeUndefined();
|
|
165
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
166
|
+
expect(parsed.query).toBe("bucket");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("a request with no _meta and no legacy fields is unaffected", async () => {
|
|
170
|
+
const response = await server.handleRequest({
|
|
171
|
+
jsonrpc: "2.0",
|
|
172
|
+
id: 1,
|
|
173
|
+
method: "tools/list",
|
|
174
|
+
params: {},
|
|
175
|
+
});
|
|
176
|
+
expect(response!.error).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// -----------------------------------------------------------------------
|
|
181
|
+
// Notification silence (#1194)
|
|
182
|
+
// -----------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
describe("notifications", () => {
|
|
185
|
+
test("a message with no id gets no response", async () => {
|
|
186
|
+
const response = await server.handleRequest({
|
|
187
|
+
jsonrpc: "2.0",
|
|
188
|
+
method: "notifications/initialized",
|
|
189
|
+
params: {},
|
|
190
|
+
});
|
|
191
|
+
expect(response).toBeNull();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("an id-less message is not routed through dispatch as Unknown method", async () => {
|
|
195
|
+
// notifications/initialized isn't a dispatch case; silence must come
|
|
196
|
+
// from the id check, not from swallowing a dispatch error.
|
|
197
|
+
const response = await server.handleRequest({
|
|
198
|
+
jsonrpc: "2.0",
|
|
199
|
+
method: "notifications/anything",
|
|
200
|
+
});
|
|
201
|
+
expect(response).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("a message with id 0 (falsy but present) still gets a response", async () => {
|
|
205
|
+
const response = await server.handleRequest({
|
|
206
|
+
jsonrpc: "2.0",
|
|
207
|
+
id: 0,
|
|
208
|
+
method: "tools/list",
|
|
209
|
+
});
|
|
210
|
+
expect(response).not.toBeNull();
|
|
211
|
+
expect(response!.id).toBe(0);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// -----------------------------------------------------------------------
|
|
216
|
+
// server/discover (#1194)
|
|
217
|
+
// -----------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
describe("server/discover", () => {
|
|
220
|
+
test("a 2026-07-28 client can discover capabilities without calling initialize", async () => {
|
|
221
|
+
const response = await server.handleRequest({
|
|
222
|
+
jsonrpc: "2.0",
|
|
223
|
+
id: 1,
|
|
224
|
+
method: "server/discover",
|
|
225
|
+
params: { _meta: { protocolVersion: "2026-07-28" } },
|
|
226
|
+
});
|
|
227
|
+
expect(response!.error).toBeUndefined();
|
|
228
|
+
const result = response!.result as Record<string, unknown>;
|
|
229
|
+
expect(result.protocolVersion).toBe("2026-07-28");
|
|
230
|
+
expect(result.serverInfo).toBeDefined();
|
|
231
|
+
expect(result.capabilities).toBeDefined();
|
|
232
|
+
expect(Array.isArray(result.tools)).toBe(true);
|
|
233
|
+
expect(Array.isArray(result.resources)).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("discover's tools list matches tools/list", async () => {
|
|
237
|
+
const discover = await server.handleRequest({ jsonrpc: "2.0", id: 1, method: "server/discover", params: {} });
|
|
238
|
+
const list = await server.handleRequest({ jsonrpc: "2.0", id: 2, method: "tools/list" });
|
|
239
|
+
const discoverNames = (discover!.result as { tools: Array<{ name: string }> }).tools.map((t) => t.name).sort();
|
|
240
|
+
const listNames = (list!.result as { tools: Array<{ name: string }> }).tools.map((t) => t.name).sort();
|
|
241
|
+
expect(discoverNames).toEqual(listNames);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("discover's resources list matches resources/list, including plugin contributions", async () => {
|
|
245
|
+
const plugin = createMockPlugin({
|
|
246
|
+
name: "test-lex",
|
|
247
|
+
mcpResources: () => [
|
|
248
|
+
{
|
|
249
|
+
uri: "catalog",
|
|
250
|
+
name: "Test Catalog",
|
|
251
|
+
description: "Test resource catalog",
|
|
252
|
+
mimeType: "application/json",
|
|
253
|
+
handler: async () => "[]",
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
});
|
|
257
|
+
const s = new McpServer([plugin]);
|
|
258
|
+
const discover = await s.handleRequest({ jsonrpc: "2.0", id: 1, method: "server/discover", params: {} });
|
|
259
|
+
const list = await s.handleRequest({ jsonrpc: "2.0", id: 2, method: "resources/list" });
|
|
260
|
+
const discoverUris = (discover!.result as { resources: Array<{ uri: string }> }).resources.map((r) => r.uri).sort();
|
|
261
|
+
const listUris = (list!.result as { resources: Array<{ uri: string }> }).resources.map((r) => r.uri).sort();
|
|
262
|
+
expect(discoverUris).toEqual(listUris);
|
|
263
|
+
expect(discoverUris).toContain("chant://test-lex/catalog");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("negotiates protocol version the same way as initialize", async () => {
|
|
267
|
+
const response = await server.handleRequest({
|
|
268
|
+
jsonrpc: "2.0",
|
|
269
|
+
id: 1,
|
|
270
|
+
method: "server/discover",
|
|
271
|
+
params: { protocolVersion: "2024-11-05" },
|
|
272
|
+
});
|
|
273
|
+
const result = response!.result as Record<string, unknown>;
|
|
274
|
+
expect(result.protocolVersion).toBe("2024-11-05");
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// -----------------------------------------------------------------------
|
|
279
|
+
// Structured tool output (#1194)
|
|
280
|
+
// -----------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
describe("structured tool output", () => {
|
|
283
|
+
test("an object-returning handler gets structuredContent alongside text", async () => {
|
|
284
|
+
const response = await server.handleRequest({
|
|
285
|
+
jsonrpc: "2.0",
|
|
286
|
+
id: 1,
|
|
287
|
+
method: "tools/call",
|
|
288
|
+
params: { name: "search", arguments: { query: "bucket" } },
|
|
289
|
+
});
|
|
290
|
+
const result = response!.result as { content: Array<{ text: string }>; structuredContent?: Record<string, unknown> };
|
|
291
|
+
expect(result.structuredContent).toBeDefined();
|
|
292
|
+
expect(result.structuredContent!.query).toBe("bucket");
|
|
293
|
+
const parsedText = JSON.parse(result.content[0].text);
|
|
294
|
+
expect(result.structuredContent).toEqual(parsedText);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("a string-returning handler has no structuredContent", async () => {
|
|
298
|
+
const plugin = createMockPlugin({
|
|
299
|
+
name: "test-lex",
|
|
300
|
+
mcpTools: () => [
|
|
301
|
+
{
|
|
302
|
+
name: "greet",
|
|
303
|
+
description: "Greet",
|
|
304
|
+
inputSchema: { type: "object", properties: {} },
|
|
305
|
+
handler: async () => "hello from plugin",
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
});
|
|
309
|
+
const s = new McpServer([plugin]);
|
|
310
|
+
const response = await s.handleRequest({
|
|
311
|
+
jsonrpc: "2.0",
|
|
312
|
+
id: 1,
|
|
313
|
+
method: "tools/call",
|
|
314
|
+
params: { name: "test-lex:greet", arguments: {} },
|
|
315
|
+
});
|
|
316
|
+
const result = response!.result as { structuredContent?: unknown };
|
|
317
|
+
expect(result.structuredContent).toBeUndefined();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("an isError response has no structuredContent", async () => {
|
|
321
|
+
const response = await server.handleRequest({
|
|
322
|
+
jsonrpc: "2.0",
|
|
323
|
+
id: 1,
|
|
324
|
+
method: "tools/call",
|
|
325
|
+
params: { name: "unknown-tool", arguments: {} },
|
|
326
|
+
});
|
|
327
|
+
const result = response!.result as { isError: boolean; structuredContent?: unknown };
|
|
328
|
+
expect(result.isError).toBe(true);
|
|
329
|
+
expect(result.structuredContent).toBeUndefined();
|
|
330
|
+
});
|
|
68
331
|
});
|
|
69
332
|
|
|
70
333
|
describe("error handling", () => {
|
package/src/cli/mcp/server.ts
CHANGED
|
@@ -7,11 +7,50 @@ import { explainTool, handleExplain } from "./tools/explain";
|
|
|
7
7
|
import { scaffoldTool, createScaffoldHandler } from "./tools/scaffold";
|
|
8
8
|
import { searchTool, createSearchHandler } from "./tools/search";
|
|
9
9
|
import type { LexiconPlugin } from "../../lexicon";
|
|
10
|
-
import type { McpRequest, McpResponse, ToolDefinition, ToolHandler, ResourceDefinition } from "./types";
|
|
10
|
+
import type { McpRequest, McpResponse, McpRequestMeta, ToolDefinition, ToolHandler, ResourceDefinition } from "./types";
|
|
11
11
|
import { createSnapshotTool, createDiffTool } from "./lifecycle-tools";
|
|
12
12
|
import { createOpListTool, createOpRunTool, createOpStatusTool, createOpSignalTool, createOpReportTool } from "./op-tools";
|
|
13
13
|
import { buildResourcesList, handleResourcesRead } from "./resource-handlers";
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Protocol versions this server understands, newest first. `initialize` and
|
|
17
|
+
* `server/discover` both negotiate against this list rather than assuming
|
|
18
|
+
* the client's revision (#1194).
|
|
19
|
+
*/
|
|
20
|
+
const SUPPORTED_PROTOCOL_VERSIONS = ["2026-07-28", "2024-11-05"] as const;
|
|
21
|
+
const LATEST_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pick the protocol version to answer with: the client's requested version
|
|
25
|
+
* when we support it, otherwise our latest. A 2024-11-05 client that asks
|
|
26
|
+
* for `2024-11-05` gets it back unchanged; a 2026-07-28 client — or one
|
|
27
|
+
* that never says — gets the latest revision (#1194).
|
|
28
|
+
*/
|
|
29
|
+
export function negotiateProtocolVersion(requested: string | undefined): string {
|
|
30
|
+
if (requested && (SUPPORTED_PROTOCOL_VERSIONS as readonly string[]).includes(requested)) {
|
|
31
|
+
return requested;
|
|
32
|
+
}
|
|
33
|
+
return LATEST_PROTOCOL_VERSION;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse the `_meta` envelope the 2026-07-28 revision puts on every request
|
|
38
|
+
* (client identity, requested protocol version), falling back to the
|
|
39
|
+
* top-level `protocolVersion`/`clientInfo` fields a prior-revision client
|
|
40
|
+
* sends on `initialize`. Read-side only — the server holds no handshake
|
|
41
|
+
* state to update (#1194).
|
|
42
|
+
*/
|
|
43
|
+
export function parseMeta(params: Record<string, unknown>): { protocolVersion?: string; clientInfo?: { name: string; version?: string } } {
|
|
44
|
+
const meta = (params._meta ?? {}) as McpRequestMeta;
|
|
45
|
+
const protocolVersion =
|
|
46
|
+
(typeof meta.protocolVersion === "string" ? meta.protocolVersion : undefined) ??
|
|
47
|
+
(typeof params.protocolVersion === "string" ? params.protocolVersion : undefined);
|
|
48
|
+
const clientInfo =
|
|
49
|
+
meta["io.modelcontextprotocol/clientInfo"] ??
|
|
50
|
+
(params.clientInfo as { name: string; version?: string } | undefined);
|
|
51
|
+
return { protocolVersion, clientInfo };
|
|
52
|
+
}
|
|
53
|
+
|
|
15
54
|
/**
|
|
16
55
|
* The name a lexicon's MCP tool is registered under: `<lexicon>:<verb>`,
|
|
17
56
|
* whether or not the lexicon already wrote the prefix itself (#1341).
|
|
@@ -135,9 +174,19 @@ export class McpServer {
|
|
|
135
174
|
}
|
|
136
175
|
|
|
137
176
|
/**
|
|
138
|
-
* Handle incoming MCP request
|
|
177
|
+
* Handle incoming MCP request.
|
|
178
|
+
*
|
|
179
|
+
* A message with no `id` is a notification — `notifications/initialized`
|
|
180
|
+
* being the one every client sends — and gets no response in any
|
|
181
|
+
* protocol revision (#1194). Returns `null` for those; callers (see
|
|
182
|
+
* {@link start}) simply skip writing anything back.
|
|
139
183
|
*/
|
|
140
|
-
async handleRequest(request: McpRequest): Promise<McpResponse
|
|
184
|
+
async handleRequest(request: McpRequest & { id: string | number }): Promise<McpResponse>;
|
|
185
|
+
async handleRequest(request: McpRequest): Promise<McpResponse | null>;
|
|
186
|
+
async handleRequest(request: McpRequest): Promise<McpResponse | null> {
|
|
187
|
+
if (request.id === undefined) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
141
190
|
try {
|
|
142
191
|
const result = await this.dispatch(request.method, request.params ?? {});
|
|
143
192
|
return {
|
|
@@ -157,16 +206,35 @@ export class McpServer {
|
|
|
157
206
|
}
|
|
158
207
|
}
|
|
159
208
|
|
|
209
|
+
/**
|
|
210
|
+
* The `initialize` result — also the basis of `server/discover` below.
|
|
211
|
+
*/
|
|
212
|
+
private buildInitializeResult(params: Record<string, unknown>): Record<string, unknown> {
|
|
213
|
+
const { protocolVersion } = parseMeta(params);
|
|
214
|
+
return {
|
|
215
|
+
protocolVersion: negotiateProtocolVersion(protocolVersion),
|
|
216
|
+
capabilities: { tools: {}, resources: {} },
|
|
217
|
+
serverInfo: { name: "chant", version: "0.1.0" },
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
160
221
|
/**
|
|
161
222
|
* Dispatch request to appropriate handler
|
|
162
223
|
*/
|
|
163
224
|
private async dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {
|
|
164
225
|
switch (method) {
|
|
165
226
|
case "initialize":
|
|
227
|
+
// Answered for prior-revision clients too — negotiated, not hard-coded (#1194).
|
|
228
|
+
return this.buildInitializeResult(params);
|
|
229
|
+
|
|
230
|
+
case "server/discover":
|
|
231
|
+
// On-demand capability discovery (#1194): the initialize result
|
|
232
|
+
// merged with the tools/resources listings, so a 2026-07-28 client
|
|
233
|
+
// never has to call `initialize` at all.
|
|
166
234
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
235
|
+
...this.buildInitializeResult(params),
|
|
236
|
+
tools: Array.from(this.tools.values()),
|
|
237
|
+
resources: buildResourcesList(this.pluginResources).resources,
|
|
170
238
|
};
|
|
171
239
|
|
|
172
240
|
case "tools/list":
|
|
@@ -203,6 +271,7 @@ export class McpServer {
|
|
|
203
271
|
|
|
204
272
|
try {
|
|
205
273
|
const result = await handler(toolParams);
|
|
274
|
+
const isStructured = typeof result === "object" && result !== null;
|
|
206
275
|
return {
|
|
207
276
|
content: [
|
|
208
277
|
{
|
|
@@ -210,6 +279,11 @@ export class McpServer {
|
|
|
210
279
|
text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
211
280
|
},
|
|
212
281
|
],
|
|
282
|
+
// Structured output (2025-06-18+): handlers already return plain
|
|
283
|
+
// objects that get stringified above, so agents that want the
|
|
284
|
+
// parsed shape directly get it here too, alongside the text block
|
|
285
|
+
// kept for backward compatibility (#1194).
|
|
286
|
+
...(isStructured ? { structuredContent: result } : {}),
|
|
213
287
|
};
|
|
214
288
|
} catch (error) {
|
|
215
289
|
return {
|
|
@@ -238,7 +312,10 @@ export class McpServer {
|
|
|
238
312
|
try {
|
|
239
313
|
const request = JSON.parse(line) as McpRequest;
|
|
240
314
|
const response = await this.handleRequest(request);
|
|
241
|
-
|
|
315
|
+
// Notifications (#1194) get no response at all — not even an empty one.
|
|
316
|
+
if (response !== null) {
|
|
317
|
+
console.log(JSON.stringify(response));
|
|
318
|
+
}
|
|
242
319
|
} catch (error) {
|
|
243
320
|
const errorResponse: McpResponse = {
|
|
244
321
|
jsonrpc: "2.0",
|
package/src/cli/mcp/types.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP message types
|
|
3
|
+
*
|
|
4
|
+
* `id` is optional per JSON-RPC 2.0: a message with no `id` is a
|
|
5
|
+
* notification (e.g. `notifications/initialized`) and gets no response,
|
|
6
|
+
* in every protocol revision (#1194).
|
|
3
7
|
*/
|
|
4
8
|
export interface McpRequest {
|
|
5
9
|
jsonrpc: "2.0";
|
|
6
|
-
id
|
|
10
|
+
id?: string | number;
|
|
7
11
|
method: string;
|
|
8
12
|
params?: Record<string, unknown>;
|
|
9
13
|
}
|
|
@@ -19,6 +23,18 @@ export interface McpResponse {
|
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The `_meta` envelope the 2026-07-28 revision puts on every request so it
|
|
28
|
+
* is self-contained: client identity and the client's requested protocol
|
|
29
|
+
* version travel here instead of living in handshake state (#1194). A
|
|
30
|
+
* prior-revision client instead sends `protocolVersion`/`clientInfo` as
|
|
31
|
+
* top-level `initialize` params — both forms are read by {@link parseMeta}.
|
|
32
|
+
*/
|
|
33
|
+
export interface McpRequestMeta {
|
|
34
|
+
protocolVersion?: string;
|
|
35
|
+
"io.modelcontextprotocol/clientInfo"?: { name: string; version?: string };
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
/**
|
|
23
39
|
* Tool definition for MCP
|
|
24
40
|
*/
|
|
@@ -30,6 +46,16 @@ export interface ToolDefinition {
|
|
|
30
46
|
properties: Record<string, unknown>;
|
|
31
47
|
required?: string[];
|
|
32
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* JSON schema for `structuredContent` (2025-06-18+), when a tool's result
|
|
51
|
+
* shape is obvious and stable enough to declare. Optional and additive —
|
|
52
|
+
* most tools omit it (#1194).
|
|
53
|
+
*/
|
|
54
|
+
outputSchema?: {
|
|
55
|
+
type: "object";
|
|
56
|
+
properties: Record<string, unknown>;
|
|
57
|
+
required?: string[];
|
|
58
|
+
};
|
|
33
59
|
}
|
|
34
60
|
|
|
35
61
|
/**
|
package/src/cli/registry.ts
CHANGED
|
@@ -26,7 +26,20 @@ export interface ParsedArgs {
|
|
|
26
26
|
temporal?: boolean;
|
|
27
27
|
/** `chant run` — emit the structured OpRunResult as JSON on stdout. */
|
|
28
28
|
json?: boolean;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* `chant run --components <name|all> --progress-json` (local executor) —
|
|
31
|
+
* stream one NDJSON `RunProgressEvent` (../../components/run-progress.ts)
|
|
32
|
+
* per line to stdout while the run executes, so a consumer can render live
|
|
33
|
+
* wave/component/phase/step progress instead of tailing raw logs.
|
|
34
|
+
*
|
|
35
|
+
* `chant run <name> --temporal --progress-json` (chant #1676) — the same
|
|
36
|
+
* flag on an Op's durable path streams one NDJSON `StepRecord`
|
|
37
|
+
* (../../op/local-executor.ts, reconstructed from workflow history by
|
|
38
|
+
* ../handlers/op-progress.ts) per settled step instead.
|
|
39
|
+
*
|
|
40
|
+
* Both are purely additive: run semantics, ordering, and exit code are
|
|
41
|
+
* unchanged; omitted (undefined, not false) when the flag isn't passed.
|
|
42
|
+
*/
|
|
30
43
|
progressJson?: boolean;
|
|
31
44
|
live: boolean;
|
|
32
45
|
/** `chant migrate --from <name>` (default "github") */
|
|
@@ -185,6 +198,25 @@ export interface ParsedArgs {
|
|
|
185
198
|
* what is declared, which is a broader read and a different claim.
|
|
186
199
|
*/
|
|
187
200
|
ambient?: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* `chant search "<q>" --at <ref> --check-live --env <name>` (#1268) —
|
|
203
|
+
* additionally read the estate live and diff the matched rows against the
|
|
204
|
+
* snapshot the answer came from, reusing `diffLive` (the same engine
|
|
205
|
+
* `lifecycle diff --live` uses) scoped to just those rows. Requires `--at`.
|
|
206
|
+
*/
|
|
207
|
+
checkLive?: boolean;
|
|
208
|
+
/**
|
|
209
|
+
* `chant search "<q>" --live --check-snapshot --env <name>` (#1268) — the
|
|
210
|
+
* reverse of `--check-live`: answer live, diff the matched rows against the
|
|
211
|
+
* most recently recorded snapshot. Requires `--live`.
|
|
212
|
+
*/
|
|
213
|
+
checkSnapshot?: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* `chant search "<q>" --check-live|--check-snapshot --fail-on-drift`
|
|
216
|
+
* (#1268) — exit non-zero when the scoped check finds drift, so it is usable
|
|
217
|
+
* as a CI gate. Meaningless without one of the two flags above.
|
|
218
|
+
*/
|
|
219
|
+
failOnDrift?: boolean;
|
|
188
220
|
|
|
189
221
|
/** `chant dev surface-diff --run-examples` — also run the example build harness */
|
|
190
222
|
runExamples?: boolean;
|
|
@@ -194,9 +226,9 @@ export interface ParsedArgs {
|
|
|
194
226
|
check?: boolean;
|
|
195
227
|
/** `chant dev surface-diff --update-snapshot --bump` — bump the lexicon's package.json version by the drift severity so the accepted surface is publishable (#616). */
|
|
196
228
|
bump?: boolean;
|
|
197
|
-
/** `chant components release record --component <name>` (#568) — component name for the release record being appended. */
|
|
229
|
+
/** `chant components release record --component <name>` (#568) — component name for the release record being appended. Also `chant components export <env> --component <name>` (#929) — which component's most recent recorded build to export. */
|
|
198
230
|
component?: string;
|
|
199
|
-
/** `chant components release record --digest <sha256:...>` (#568) — artifact digest to record, joining this release to the build archive/ledger. */
|
|
231
|
+
/** `chant components release record --digest <sha256:...>` (#568) — artifact digest to record, joining this release to the build archive/ledger. Also `chant components export --digest <manifestDigest>` (#929) — a build archive manifest digest to export directly, bypassing env/component resolution. */
|
|
200
232
|
digest?: string;
|
|
201
233
|
/** `chant components release record --git-sha <sha>` (#568) — git commit the deploy was built from. */
|
|
202
234
|
gitSha?: string;
|
|
@@ -218,6 +250,10 @@ export interface ParsedArgs {
|
|
|
218
250
|
fold?: boolean;
|
|
219
251
|
/** `chant build --sandbox` (#1045 Phase 2) — opt-in: run-fallback source files (or every file, without `--fold`) execute together, isolated, in one sandboxed child process instead of in-process. Also settable project-wide via `chant.config.ts`'s `build.sandbox: true`; the flag always wins when set. Default (flag omitted): in-process execution, unchanged. */
|
|
220
252
|
sandbox?: boolean;
|
|
253
|
+
/** `chant build --fold --fold-rank` (#1083) — after a `--fold` build, print blockers ranked by dominator retained-count over the forward import-failure graph, plus the separate reverse-taint bucket (chant #1044). Bare boolean form; see {@link foldRankCollapsedFile} for the file-writing form. No-op without `--fold`. */
|
|
254
|
+
foldRank?: boolean;
|
|
255
|
+
/** `chant build --fold --fold-rank <path>` (#1083) — same ranking as {@link foldRank}, ALSO exported in Brendan Gregg collapsed stack format (weighted by retained count) to `<path>`, so it renders in any flame/icicle viewer. Mutually exclusive with the bare-boolean form at the parse level (same context-sensitive lookahead as `--report`), but the text report still prints either way. */
|
|
256
|
+
foldRankCollapsedFile?: string;
|
|
221
257
|
/** `chant build --param name=value` (#1064) — repeatable. Bound to `params.<name>` (`@intentius/chant/params`) for source to reference, after validation against `chant.config.ts`'s declared `buildParams`. Highest precedence over `--params-file`/a declared `env` mapping/the declared `default`. */
|
|
222
258
|
param?: string[];
|
|
223
259
|
/** `chant build --params-file <path>` (#1064) — a JSON file of `{ "name": value }` build-time parameter values. Second precedence, after `--param`. */
|
|
@@ -228,6 +264,14 @@ export interface ParsedArgs {
|
|
|
228
264
|
* forgejo today) — the same generator `chant build --components --generate
|
|
229
265
|
* <lexicon>` uses, reused rather than re-derived. */
|
|
230
266
|
projection?: string;
|
|
267
|
+
/** `chant operator --interval <duration>` (#1485) — poll interval between rounds, e.g. "30s", "5m". Default: 60s. */
|
|
268
|
+
interval?: string;
|
|
269
|
+
/** `chant operator --lease-ttl <duration>` (#1485) — how long an acquired lease is valid before it's reclaimable by another operator. Default: 5m. */
|
|
270
|
+
leaseTtl?: string;
|
|
271
|
+
/** `chant operator --once` (#1485) — run a single round and exit, instead of looping until Ctrl-C. Also the offline test/cron-invoker story. */
|
|
272
|
+
once?: boolean;
|
|
273
|
+
/** `chant approve <op> <gate> --note <text>` (#1485) — optional free-text context recorded on the gate-resolution fact (e.g. a PR URL). */
|
|
274
|
+
note?: string;
|
|
231
275
|
}
|
|
232
276
|
|
|
233
277
|
/**
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { scanRules } from "./docs-rule-scanning";
|
|
6
|
+
|
|
7
|
+
// #1938: a post-synth check's `description` string containing a
|
|
8
|
+
// backslash-escaped quote used to truncate at the first embedded `"`,
|
|
9
|
+
// garbling the generated docs table entry.
|
|
10
|
+
describe("scanRules — post-synth description extraction (#1938)", () => {
|
|
11
|
+
let dir: string;
|
|
12
|
+
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
dir = mkdtempSync(join(tmpdir(), "docs-rule-scanning-"));
|
|
15
|
+
mkdirSync(join(dir, "lint", "post-synth"), { recursive: true });
|
|
16
|
+
writeFileSync(
|
|
17
|
+
join(dir, "lint", "post-synth", "wk8505.ts"),
|
|
18
|
+
[
|
|
19
|
+
"export const wk8505 = {",
|
|
20
|
+
' id: "WK8505",',
|
|
21
|
+
' description: "A rule\'s \\"why\\" must be non-empty, and a backslash like C:\\\\path must survive intact",',
|
|
22
|
+
" check() { return []; },",
|
|
23
|
+
"};",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n"),
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterAll(() => {
|
|
30
|
+
rmSync(dir, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("extracts the full description past an escaped quote and a backslash", () => {
|
|
34
|
+
const rules = scanRules(dir);
|
|
35
|
+
const rule = rules.find((r) => r.id === "WK8505");
|
|
36
|
+
|
|
37
|
+
expect(rule).toBeDefined();
|
|
38
|
+
expect(rule?.description).toBe(
|
|
39
|
+
'A rule\'s "why" must be non-empty, and a backslash like C:\\path must survive intact',
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -92,7 +92,7 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: ScannedRule[]):
|
|
|
92
92
|
} else {
|
|
93
93
|
// Extract from PostSynthCheck objects: id, description
|
|
94
94
|
const idMatch = content.match(/id:\s*"([^"]+)"/);
|
|
95
|
-
const descMatch = content.match(/description:\s*"([^"]
|
|
95
|
+
const descMatch = content.match(/description:\s*"((?:[^"\\]|\\.)*)"/);
|
|
96
96
|
|
|
97
97
|
if (idMatch) {
|
|
98
98
|
out.push({
|
|
@@ -100,7 +100,7 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: ScannedRule[]):
|
|
|
100
100
|
id: idMatch[1],
|
|
101
101
|
severity: "error",
|
|
102
102
|
category: "post-synth",
|
|
103
|
-
description: descMatch
|
|
103
|
+
description: descMatch ? unescapeStringLiteral(descMatch[1]) : idMatch[1],
|
|
104
104
|
type: "post-synth",
|
|
105
105
|
},
|
|
106
106
|
source: content,
|
|
@@ -110,6 +110,29 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: ScannedRule[]):
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Undo the backslash escaping of a double-quoted JS/TS string literal body
|
|
115
|
+
* captured by a regex like `/description:\s*"((?:[^"\\]|\\.)*)"/` — the
|
|
116
|
+
* capture group still has the source's escape sequences (`\"`, `\\`, `\n`,
|
|
117
|
+
* ...) in it verbatim, so a description written as `"say \"hi\""` would
|
|
118
|
+
* otherwise render in generated docs as `say \"hi\"` instead of `say "hi"`.
|
|
119
|
+
*/
|
|
120
|
+
function unescapeStringLiteral(raw: string): string {
|
|
121
|
+
return raw.replace(/\\(.)/g, (_, ch: string) => {
|
|
122
|
+
switch (ch) {
|
|
123
|
+
case "n":
|
|
124
|
+
return "\n";
|
|
125
|
+
case "t":
|
|
126
|
+
return "\t";
|
|
127
|
+
case "r":
|
|
128
|
+
return "\r";
|
|
129
|
+
default:
|
|
130
|
+
// \" -> ", \\ -> \, \' -> ', and anything else -> itself unescaped
|
|
131
|
+
return ch;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
113
136
|
function extractDescriptionFromComment(
|
|
114
137
|
content: string,
|
|
115
138
|
ruleId: string,
|
package/src/components/README.md
CHANGED
|
@@ -33,6 +33,13 @@ adopting chant.
|
|
|
33
33
|
work for a component whose own name isn't the entity/resource name it
|
|
34
34
|
deploys, falling back to `name` itself when omitted. See
|
|
35
35
|
docs/src/content/docs/components/observability.mdx.
|
|
36
|
+
- **Composite mapping** — `composites` (#1492), optional: the composite kind
|
|
37
|
+
name(s) this component's deployed resources come from, when a consumer
|
|
38
|
+
joining a composite graph to the component DAG (e.g. `chant graph
|
|
39
|
+
--components --format ir`'s node `attrs.composites`, ../cli/handlers/
|
|
40
|
+
graph.ts) can't infer it by kebab-casing `name` against each composite's
|
|
41
|
+
kind. Omitted entirely (the default), that naming convention stays in
|
|
42
|
+
effect with no behavior change.
|
|
36
43
|
- **The three archetypes** — service (build → publish → apply → verify),
|
|
37
44
|
infra (apply → verify, no build), and producer/library (build → publish
|
|
38
45
|
only, no service apply). `deploy` being a plain composition is what makes
|