@intentius/chant 0.84.0 → 0.86.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/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts +2 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +1 -0
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts +44 -0
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -0
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +13 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +4 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -0
- package/dist/composite.d.ts.map +1 -1
- package/dist/lexicon.d.ts +44 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +13 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/sessions.d.ts +23 -0
- package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +1 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +4 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +152 -0
- package/dist/workspace/composites.d.ts.map +1 -0
- package/dist/workspace/conformance/index.d.ts +211 -0
- package/dist/workspace/conformance/index.d.ts.map +1 -0
- package/dist/workspace/conformance/vitest.d.ts +11 -0
- package/dist/workspace/conformance/vitest.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +28 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +40 -0
- package/dist/workspace/declared-kinds.d.ts +43 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +24 -2
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +6 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-joins.d.ts +74 -9
- package/dist/workspace/intent-joins.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +90 -6
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/ls.d.ts +31 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/member-commands.d.ts +7 -2
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +52 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +51 -0
- package/dist/workspace/record-sessions.d.ts.map +1 -0
- package/dist/workspace/record-source.d.ts +2 -0
- package/dist/workspace/record-source.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +65 -4
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts +90 -0
- package/dist/workspace/records-since.d.ts.map +1 -0
- package/dist/workspace/records-write.d.ts +164 -0
- package/dist/workspace/records-write.d.ts.map +1 -0
- package/dist/workspace/records.d.ts +202 -15
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runtimes.d.ts +60 -0
- package/dist/workspace/runtimes.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +90 -0
- package/dist/workspace/status-gates.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +17 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work.d.ts +56 -0
- package/dist/workspace/work.d.ts.map +1 -0
- package/package.json +19 -1
- package/src/cli/handlers/graph.ts +4 -0
- package/src/cli/main.test.ts +9 -0
- package/src/cli/main.ts +56 -3
- package/src/cli/mcp/resource-handlers.ts +17 -0
- package/src/cli/mcp/server.test.ts +140 -4
- package/src/cli/mcp/server.ts +5 -1
- package/src/cli/mcp/tools/composites.ts +98 -0
- package/src/cli/mcp/tools/search.ts +47 -5
- package/src/cli/registry.ts +13 -1
- package/src/components/cli-support.test.ts +16 -0
- package/src/components/cli-support.ts +8 -2
- package/src/composite.ts +9 -0
- package/src/lexicon.ts +47 -0
- package/src/lifecycle/gate-ledger.ts +14 -0
- package/src/workspace/__fixtures__/contract-repo.ts +17 -0
- package/src/workspace/__fixtures__/sessions.ts +66 -0
- package/src/workspace/checks/records.ts +19 -0
- package/src/workspace/checks.test.ts +2 -0
- package/src/workspace/checks.ts +7 -1
- package/src/workspace/composites.schema.json +533 -0
- package/src/workspace/composites.test.ts +334 -0
- package/src/workspace/composites.ts +316 -0
- package/src/workspace/conformance/__fixture__/app/package.json +7 -0
- package/src/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +32 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +364 -0
- package/src/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
- package/src/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
- package/src/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
- package/src/workspace/conformance/__fixture__/delivery/package.json +7 -0
- package/src/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
- package/src/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
- package/src/workspace/conformance/conformance.test.ts +149 -0
- package/src/workspace/conformance/index.mjs +31 -0
- package/src/workspace/conformance/index.ts +453 -0
- package/src/workspace/conformance/vitest.ts +62 -0
- package/src/workspace/declaration.schema.json +40 -0
- package/src/workspace/declaration.ts +62 -0
- package/src/workspace/declared-kinds.test.ts +321 -0
- package/src/workspace/declared-kinds.ts +76 -0
- package/src/workspace/graph-cli.ts +40 -4
- package/src/workspace/intent-cli.ts +54 -7
- package/src/workspace/intent-joins.test.ts +60 -0
- package/src/workspace/intent-joins.ts +117 -20
- package/src/workspace/intent.schema.json +357 -19
- package/src/workspace/intent.test.ts +235 -20
- package/src/workspace/intent.ts +396 -51
- package/src/workspace/ls.schema.json +34 -0
- package/src/workspace/ls.ts +69 -4
- package/src/workspace/member-commands.ts +11 -5
- package/src/workspace/read-contract.test.ts +52 -3
- package/src/workspace/reason-codes.test.ts +48 -4
- package/src/workspace/reason-codes.ts +67 -2
- package/src/workspace/record-assets.test.ts +3 -1
- package/src/workspace/record-sessions.ts +105 -0
- package/src/workspace/record-source.ts +14 -5
- package/src/workspace/records-amend.schema.json +167 -0
- package/src/workspace/records-cli.ts +246 -19
- package/src/workspace/records-contract.test.ts +77 -2
- package/src/workspace/records-formats.test.ts +640 -0
- package/src/workspace/records-new.schema.json +158 -0
- package/src/workspace/records-quorum.test.ts +196 -0
- package/src/workspace/records-review.schema.json +202 -0
- package/src/workspace/records-sessions.test.ts +108 -0
- package/src/workspace/records-since.schema.json +193 -0
- package/src/workspace/records-since.test.ts +174 -0
- package/src/workspace/records-since.ts +259 -0
- package/src/workspace/records-write-contract.test.ts +125 -0
- package/src/workspace/records-write.test.ts +373 -0
- package/src/workspace/records-write.ts +736 -0
- package/src/workspace/records.schema.json +187 -9
- package/src/workspace/records.test.ts +93 -0
- package/src/workspace/records.ts +683 -49
- package/src/workspace/runtimes.ts +107 -0
- package/src/workspace/status-contract.test.ts +163 -0
- package/src/workspace/status-gates.ts +215 -0
- package/src/workspace/status.schema.json +69 -3
- package/src/workspace/status.ts +35 -2
- package/src/workspace/work.test.ts +388 -0
- package/src/workspace/work.ts +163 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -50,6 +50,16 @@
|
|
|
50
50
|
"default": "./src/cli/index.ts"
|
|
51
51
|
},
|
|
52
52
|
"./workspace/*.schema.json": "./src/workspace/*.schema.json",
|
|
53
|
+
"./workspace/conformance": {
|
|
54
|
+
"development": "./src/workspace/conformance/index.ts",
|
|
55
|
+
"types": "./dist/workspace/conformance/index.d.ts",
|
|
56
|
+
"default": "./src/workspace/conformance/index.mjs"
|
|
57
|
+
},
|
|
58
|
+
"./workspace/conformance/vitest": {
|
|
59
|
+
"development": "./src/workspace/conformance/vitest.ts",
|
|
60
|
+
"types": "./dist/workspace/conformance/vitest.d.ts",
|
|
61
|
+
"default": "./src/workspace/conformance/vitest.ts"
|
|
62
|
+
},
|
|
53
63
|
"./cli/*": {
|
|
54
64
|
"development": "./src/cli/*.ts",
|
|
55
65
|
"types": "./dist/cli/*.d.ts",
|
|
@@ -104,6 +114,14 @@
|
|
|
104
114
|
"devDependencies": {
|
|
105
115
|
"@cdktn/hcl2json": "^0.24.0"
|
|
106
116
|
},
|
|
117
|
+
"peerDependencies": {
|
|
118
|
+
"vitest": ">=2"
|
|
119
|
+
},
|
|
120
|
+
"peerDependenciesMeta": {
|
|
121
|
+
"vitest": {
|
|
122
|
+
"optional": true
|
|
123
|
+
}
|
|
124
|
+
},
|
|
107
125
|
"overrides": {
|
|
108
126
|
"esbuild": "^0.28.1"
|
|
109
127
|
}
|
|
@@ -619,6 +619,10 @@ async function runComponentGraphView(
|
|
|
619
619
|
wave: waveOf.get(name) ?? null,
|
|
620
620
|
liveNames: graph.liveNames?.[name] ?? [name],
|
|
621
621
|
...(graph.composites?.[name] ? { composites: graph.composites[name] } : {}),
|
|
622
|
+
// The archetype (#2662), declared or inferred, so a reader such as
|
|
623
|
+
// `chant workspace graph --composites` labels a component without
|
|
624
|
+
// importing its source.
|
|
625
|
+
...(graph.archetypes?.[name] ? { archetype: graph.archetypes[name] } : {}),
|
|
622
626
|
},
|
|
623
627
|
// Deep-link the node to its `*.component.ts` (behold's inspect panel).
|
|
624
628
|
...(graph.files?.[name] ? { sourceLoc: { file: graph.files[name] } } : {}),
|
package/src/cli/main.test.ts
CHANGED
|
@@ -688,6 +688,15 @@ describe("workspace records (#2546)", () => {
|
|
|
688
688
|
});
|
|
689
689
|
});
|
|
690
690
|
|
|
691
|
+
describe("workspace graph --composites (#2662)", () => {
|
|
692
|
+
test("parses as a boolean on workspace graph", () => {
|
|
693
|
+
const args = parseArgs(["workspace", "graph", "--composites", "--at", "HEAD", "--json"]);
|
|
694
|
+
expect(args).toMatchObject({ command: "workspace", path: "graph", composites: true, at: "HEAD", json: true });
|
|
695
|
+
expect(() => parseArgs(["workspace", "graph", "--composites=yes"])).toThrow();
|
|
696
|
+
expect(resolveCommand(args, commandRegistry)?.def.name).toBe("workspace graph");
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
|
|
691
700
|
describe("workspace graph --intent (#2651)", () => {
|
|
692
701
|
test("parses the region and every --kind, in order", () => {
|
|
693
702
|
const args = parseArgs(["workspace", "graph", "--intent", "app/server.mjs:3-7", "--kind", "a.kind.mjs", "--kind", "b.kind.mjs", "--at", "HEAD", "--json"]);
|
package/src/cli/main.ts
CHANGED
|
@@ -69,6 +69,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
69
69
|
"--strict",
|
|
70
70
|
"--validate",
|
|
71
71
|
"--use-composites",
|
|
72
|
+
"--composites",
|
|
72
73
|
"--stacks",
|
|
73
74
|
"--components",
|
|
74
75
|
"--up",
|
|
@@ -365,12 +366,35 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
365
366
|
if (!result.kind || result.kind.startsWith("-")) throw new Error("--kind needs a kind file: --kind <path>");
|
|
366
367
|
// Repeatable for `workspace graph --intent` (#2651); the others read the last one.
|
|
367
368
|
(result.kinds ??= []).push(result.kind);
|
|
369
|
+
} else if (arg === "--composites") {
|
|
370
|
+
// `chant workspace graph --composites` (#2662)
|
|
371
|
+
result.composites = true;
|
|
368
372
|
} else if (arg === "--intent") {
|
|
369
373
|
// `chant workspace graph --intent <path[:start-end]>` (#2651)
|
|
370
374
|
result.intent = args[++i];
|
|
371
375
|
if (!result.intent || result.intent.startsWith("-")) throw new Error("--intent needs a region: --intent <path[:start-end]>");
|
|
372
376
|
} else if (arg === "--current") {
|
|
373
377
|
result.current = true;
|
|
378
|
+
} else if (arg === "--set") {
|
|
379
|
+
// `chant workspace records amend <id> --set <file|->` (#2670)
|
|
380
|
+
result.set = args[++i];
|
|
381
|
+
if (!result.set || (result.set.startsWith("-") && result.set !== "-")) throw new Error("--set needs a JSON file, or - for standard input: --set <file|->");
|
|
382
|
+
} else if (arg === "--verdict") {
|
|
383
|
+
// `chant workspace records review <id> --verdict agree|dissent|abstain` (#2670)
|
|
384
|
+
result.verdict = args[++i];
|
|
385
|
+
if (!result.verdict || result.verdict.startsWith("-")) throw new Error("--verdict needs agree, dissent or abstain");
|
|
386
|
+
} else if (arg === "--by") {
|
|
387
|
+
// `chant workspace records review <id> --by <principal>` (#2670): whoever the caller says.
|
|
388
|
+
result.by = args[++i];
|
|
389
|
+
if (!result.by || result.by.startsWith("-")) throw new Error("--by needs the reviewer: --by <principal>");
|
|
390
|
+
} else if (arg === "--session") {
|
|
391
|
+
// `chant workspace records review <id> --session <id>` (#2670)
|
|
392
|
+
result.session = args[++i];
|
|
393
|
+
if (!result.session || result.session.startsWith("-")) throw new Error("--session needs a session id: --session <id>");
|
|
394
|
+
} else if (arg === "--prefix") {
|
|
395
|
+
// `chant workspace records new <kind> --prefix <prefix>` (#2670): the id prefix to allocate under.
|
|
396
|
+
result.prefix = args[++i];
|
|
397
|
+
if (!result.prefix || result.prefix.startsWith("-")) throw new Error("--prefix needs an id prefix: --prefix <prefix>");
|
|
374
398
|
} else if (arg === "--require") {
|
|
375
399
|
// `chant workspace records|verify --require attested` (#2547)
|
|
376
400
|
result.require = args[++i];
|
|
@@ -717,8 +741,9 @@ Workspace (level 1, #2524):
|
|
|
717
741
|
beside it and marks the members whose digests differ.
|
|
718
742
|
Read only; never fetches. --json prints the
|
|
719
743
|
read-contract document
|
|
720
|
-
workspace records --kind <kind file> [--current] [--at <rev>] [--base <rev>] [--require attested] [--json]
|
|
721
|
-
|
|
744
|
+
workspace records [--kind <kind file>] [--current] [--at <rev>] [--base <rev>] [--require attested] [--json]
|
|
745
|
+
Without --kind, every record kind the declaration
|
|
746
|
+
names. Read the records a record kind locates, validated
|
|
722
747
|
against its schema, with reason codes for invalid
|
|
723
748
|
ones. --current leaves out superseded records; --at
|
|
724
749
|
reads a commit's git objects. Needs no workspace file.
|
|
@@ -727,9 +752,32 @@ Workspace (level 1, #2524):
|
|
|
727
752
|
--require attested exits 2 if any record is not
|
|
728
753
|
attested. A pinned file that changed is a warning,
|
|
729
754
|
asset-drift or asset-missing
|
|
755
|
+
workspace records [--kind <kind file>] --since <rev> [--at <rev>] [--json]
|
|
756
|
+
What changed in the records between <rev> and --at
|
|
757
|
+
(default: the working tree): new and removed records,
|
|
758
|
+
state transitions, new verdicts, new supersessions
|
|
759
|
+
and changed pins
|
|
730
760
|
workspace records pin <path>
|
|
731
761
|
Print the path from the workspace root and the
|
|
732
762
|
sha256 of a file, for a decision's evidence pin
|
|
763
|
+
workspace records new [<kind file>] --from <file|-> [--prefix <prefix>] [--dry-run]
|
|
764
|
+
Write one new record in the kind's directory from
|
|
765
|
+
the JSON fields given, after validating them as
|
|
766
|
+
records would read them. Without a kind file, the one
|
|
767
|
+
kind the declaration names. Allocates the next id when
|
|
768
|
+
the fields hold none. Prints {path, id} as JSON and
|
|
769
|
+
never commits
|
|
770
|
+
workspace records amend <id> [--kind <kind file>] --set <file|-> [--dry-run]
|
|
771
|
+
Set top-level fields of one record. A closed record
|
|
772
|
+
never changes, and an approved one changes only its
|
|
773
|
+
state (upward), pins and reviews; anything else is
|
|
774
|
+
refused with amend-supersede-instead. Prints
|
|
775
|
+
{path, id, changed}
|
|
776
|
+
workspace records review <id> [--kind <kind file>] --verdict agree|dissent|abstain --by <principal> [--note <text>] [--session <id>] [--dry-run]
|
|
777
|
+
Append a review to one record, dated and bound to
|
|
778
|
+
the digest of the record text. A dissent needs
|
|
779
|
+
--note. The principal is not checked; attestation is
|
|
780
|
+
the seal's job. Prints {path, id, review}
|
|
733
781
|
workspace verify [--base <rev>] [--head <rev>] [--require attested]
|
|
734
782
|
Check the commits in base..head against the signers
|
|
735
783
|
and roles read from base. A change to the signers file
|
|
@@ -773,10 +821,15 @@ Workspace (level 1, #2524):
|
|
|
773
821
|
read-contract document. --at <rev> runs each member's
|
|
774
822
|
source as it was at that commit; --kind adds the
|
|
775
823
|
records' asset and constrains links
|
|
824
|
+
workspace graph --composites [--at <rev>] [--member <name>] [-o <file>]
|
|
825
|
+
Each composite instance the members declare, with the
|
|
826
|
+
components whose contract can deploy it; an instance
|
|
827
|
+
with none lists an empty set
|
|
776
828
|
workspace graph --intent <path[:start-end]> [--at <rev>] [--kind <kind file>...] [--json]
|
|
777
829
|
The intent graph over one region: the commits that
|
|
778
830
|
touched it, the decisions whose constrains cover it,
|
|
779
|
-
the artifacts they pin, and findings with closed codes
|
|
831
|
+
the artifacts they pin, and findings with closed codes.
|
|
832
|
+
Without --kind, every record kind the declaration names
|
|
780
833
|
|
|
781
834
|
Lifecycle (alias: lc):
|
|
782
835
|
lifecycle snapshot <env> Query API, save metadata to orphan branch
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolve, join, dirname } from "node:path";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import type { ResourceDefinition } from "./types";
|
|
4
|
+
import type { LexiconPlugin } from "../../lexicon";
|
|
5
|
+
import { collectComposites } from "./tools/composites";
|
|
4
6
|
import { getContext } from "./resources/context";
|
|
5
7
|
import { readSnapshot, readEnvironmentSnapshots } from "../../lifecycle/git";
|
|
6
8
|
import { discoverOps } from "../../op/discover";
|
|
@@ -44,6 +46,12 @@ export const coreResourceDefinitions: ResourceDefinition[] = [
|
|
|
44
46
|
description: "Latest run state for a named Op",
|
|
45
47
|
mimeType: "application/json",
|
|
46
48
|
},
|
|
49
|
+
{
|
|
50
|
+
uri: "chant://composites",
|
|
51
|
+
name: "Composite catalog",
|
|
52
|
+
description: "Every composite the loaded lexicons export, with what each bundles and its parameters (#2662)",
|
|
53
|
+
mimeType: "application/json",
|
|
54
|
+
},
|
|
47
55
|
{
|
|
48
56
|
uri: "chant://knowledge",
|
|
49
57
|
name: "Knowledge bundle",
|
|
@@ -99,9 +107,18 @@ export function collectExamples(
|
|
|
99
107
|
export async function handleResourcesRead(
|
|
100
108
|
params: Record<string, unknown>,
|
|
101
109
|
pluginResources: Map<string, PluginResourceEntry>,
|
|
110
|
+
plugins: LexiconPlugin[] = [],
|
|
102
111
|
): Promise<unknown> {
|
|
103
112
|
const uri = params.uri as string;
|
|
104
113
|
|
|
114
|
+
// The whole composite catalog (#2662), static data from each lexicon's
|
|
115
|
+
// `composites()`; the `composites` tool filters the same list.
|
|
116
|
+
if (uri === "chant://composites") {
|
|
117
|
+
return {
|
|
118
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(collectComposites(plugins), null, 2) }],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
105
122
|
if (uri === "chant://context") {
|
|
106
123
|
return {
|
|
107
124
|
contents: [
|
|
@@ -778,6 +778,142 @@ describe("McpServer", () => {
|
|
|
778
778
|
});
|
|
779
779
|
});
|
|
780
780
|
|
|
781
|
+
// -----------------------------------------------------------------------
|
|
782
|
+
// Composite catalog (#2662)
|
|
783
|
+
// -----------------------------------------------------------------------
|
|
784
|
+
|
|
785
|
+
describe("composite catalog", () => {
|
|
786
|
+
const awsLike = createMockPlugin({
|
|
787
|
+
name: "aws",
|
|
788
|
+
composites: () => [
|
|
789
|
+
{
|
|
790
|
+
name: "LambdaSqs",
|
|
791
|
+
lexicon: "aws",
|
|
792
|
+
description: "A Lambda function fed by an SQS queue through an event source mapping.",
|
|
793
|
+
bundles: ["EventSourceMapping", "Function", "Queue", "Role"],
|
|
794
|
+
params: [{ name: "queueName", type: "Value<string>", required: false }],
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "VpcDefault",
|
|
798
|
+
lexicon: "aws",
|
|
799
|
+
description: "A VPC with public and private subnets.",
|
|
800
|
+
bundles: ["Subnet", "Vpc"],
|
|
801
|
+
params: [{ name: "cidr", type: "string", required: false }],
|
|
802
|
+
},
|
|
803
|
+
],
|
|
804
|
+
mcpResources: () => [
|
|
805
|
+
{
|
|
806
|
+
uri: "resource-catalog",
|
|
807
|
+
name: "Catalog",
|
|
808
|
+
description: "Resource catalog",
|
|
809
|
+
mimeType: "application/json",
|
|
810
|
+
handler: async () => JSON.stringify([
|
|
811
|
+
{ className: "Queue", resourceType: "AWS::SQS::Queue", kind: "resource" },
|
|
812
|
+
]),
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
});
|
|
816
|
+
const k8sLike = createMockPlugin({
|
|
817
|
+
name: "k8s",
|
|
818
|
+
composites: () => [
|
|
819
|
+
{
|
|
820
|
+
name: "WebApp",
|
|
821
|
+
lexicon: "k8s",
|
|
822
|
+
description: "A Deployment and Service.",
|
|
823
|
+
bundles: ["Deployment", "Service"],
|
|
824
|
+
params: [{ name: "image", type: "string", required: true }],
|
|
825
|
+
},
|
|
826
|
+
],
|
|
827
|
+
});
|
|
828
|
+
const noComposites = createMockPlugin({ name: "cedar" });
|
|
829
|
+
|
|
830
|
+
async function call(s: McpServer, name: string, args: Record<string, unknown>) {
|
|
831
|
+
const response = await s.handleRequest({
|
|
832
|
+
jsonrpc: "2.0",
|
|
833
|
+
id: 1,
|
|
834
|
+
method: "tools/call",
|
|
835
|
+
params: { name, arguments: args },
|
|
836
|
+
});
|
|
837
|
+
return JSON.parse((response.result as { content: Array<{ text: string }> }).content[0].text);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
test("the composites tool answers what a lexicon has, with bundles and params", async () => {
|
|
841
|
+
const s = new McpServer([awsLike, k8sLike, noComposites]);
|
|
842
|
+
const parsed = await call(s, "composites", { lexicon: "aws" });
|
|
843
|
+
expect(parsed.total).toBe(2);
|
|
844
|
+
expect(parsed.composites.map((c: { name: string }) => c.name)).toEqual(["LambdaSqs", "VpcDefault"]);
|
|
845
|
+
expect(parsed.composites[0].bundles).toContain("Queue");
|
|
846
|
+
expect(parsed.composites[0].params[0].name).toBe("queueName");
|
|
847
|
+
expect(parsed.lexicons).toEqual(["aws", "k8s", "cedar"]);
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
test("with no filter it lists every lexicon's composites; a lexicon with none adds nothing", async () => {
|
|
851
|
+
const s = new McpServer([awsLike, k8sLike, noComposites]);
|
|
852
|
+
const parsed = await call(s, "composites", {});
|
|
853
|
+
expect(parsed.total).toBe(3);
|
|
854
|
+
expect((await call(s, "composites", { lexicon: "cedar" })).total).toBe(0);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test("query matches a bundled kind, and a name match ranks first", async () => {
|
|
858
|
+
const s = new McpServer([awsLike, k8sLike]);
|
|
859
|
+
const byKind = await call(s, "composites", { query: "queue" });
|
|
860
|
+
expect(byKind.composites.map((c: { name: string }) => c.name)).toEqual(["LambdaSqs"]);
|
|
861
|
+
const byName = await call(s, "composites", { query: "vpc" });
|
|
862
|
+
expect(byName.composites[0].name).toBe("VpcDefault");
|
|
863
|
+
expect((await call(s, "composites", { limit: 1 })).composites).toHaveLength(1);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
test("chant://composites lists the whole catalog", async () => {
|
|
867
|
+
const s = new McpServer([awsLike, k8sLike, noComposites]);
|
|
868
|
+
const list = await s.handleRequest({ jsonrpc: "2.0", id: 1, method: "resources/list" });
|
|
869
|
+
const uris = (list.result as { resources: Array<{ uri: string }> }).resources.map((r) => r.uri);
|
|
870
|
+
expect(uris).toContain("chant://composites");
|
|
871
|
+
|
|
872
|
+
const read = await s.handleRequest({
|
|
873
|
+
jsonrpc: "2.0",
|
|
874
|
+
id: 2,
|
|
875
|
+
method: "resources/read",
|
|
876
|
+
params: { uri: "chant://composites" },
|
|
877
|
+
});
|
|
878
|
+
const contents = (read.result as { contents: Array<{ mimeType: string; text: string }> }).contents;
|
|
879
|
+
expect(contents[0].mimeType).toBe("application/json");
|
|
880
|
+
const catalog = JSON.parse(contents[0].text);
|
|
881
|
+
expect(catalog.map((c: { lexicon: string; name: string }) => `${c.lexicon}:${c.name}`)).toEqual([
|
|
882
|
+
"aws:LambdaSqs", "aws:VpcDefault", "k8s:WebApp",
|
|
883
|
+
]);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test("chant://composites is an empty list with no plugins", async () => {
|
|
887
|
+
const read = await new McpServer().handleRequest({
|
|
888
|
+
jsonrpc: "2.0",
|
|
889
|
+
id: 1,
|
|
890
|
+
method: "resources/read",
|
|
891
|
+
params: { uri: "chant://composites" },
|
|
892
|
+
});
|
|
893
|
+
const contents = (read.result as { contents: Array<{ text: string }> }).contents;
|
|
894
|
+
expect(JSON.parse(contents[0].text)).toEqual([]);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test("search finds a resource type and the composite that bundles it", async () => {
|
|
898
|
+
const s = new McpServer([awsLike, k8sLike]);
|
|
899
|
+
const parsed = await call(s, "search", { query: "queue" });
|
|
900
|
+
expect(parsed.total).toBe(2);
|
|
901
|
+
const kinds = parsed.results.map((r: { kind: string }) => r.kind).sort();
|
|
902
|
+
expect(kinds).toEqual(["composite", "resource"]);
|
|
903
|
+
const composite = parsed.results.find((r: { kind: string }) => r.kind === "composite");
|
|
904
|
+
expect(composite).toMatchObject({ name: "LambdaSqs", lexicon: "aws", bundles: expect.arrayContaining(["Queue"]) });
|
|
905
|
+
expect(composite.params).toBeUndefined();
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
test("search's lexicon filter applies to composites too", async () => {
|
|
909
|
+
const s = new McpServer([awsLike, k8sLike]);
|
|
910
|
+
const parsed = await call(s, "search", { query: "deployment", lexicon: "aws" });
|
|
911
|
+
expect(parsed.total).toBe(0);
|
|
912
|
+
const k8s = await call(s, "search", { query: "deployment", lexicon: "k8s" });
|
|
913
|
+
expect(k8s.results.map((r: { name: string }) => r.name)).toEqual(["WebApp"]);
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
|
|
781
917
|
// -----------------------------------------------------------------------
|
|
782
918
|
// Scaffold tool with plugins
|
|
783
919
|
// -----------------------------------------------------------------------
|
|
@@ -1466,23 +1602,23 @@ describe("McpServer", () => {
|
|
|
1466
1602
|
|
|
1467
1603
|
const toolsRes = await s.handleRequest({ jsonrpc: "2.0", id: 2, method: "tools/list" });
|
|
1468
1604
|
const tools = (toolsRes.result as { tools: Array<{ name: string }> }).tools;
|
|
1469
|
-
expect(tools).toHaveLength(
|
|
1605
|
+
expect(tools).toHaveLength(14);
|
|
1470
1606
|
expect(tools.map((t) => t.name).sort()).toEqual([
|
|
1471
|
-
"build", "explain", "import", "lifecycle-diff", "lifecycle-snapshot", "lint",
|
|
1607
|
+
"build", "composites", "explain", "import", "lifecycle-diff", "lifecycle-snapshot", "lint",
|
|
1472
1608
|
"op-approve", "op-list", "op-report", "op-run", "op-status",
|
|
1473
1609
|
"scaffold", "search",
|
|
1474
1610
|
]);
|
|
1475
1611
|
|
|
1476
1612
|
const resourcesRes = await s.handleRequest({ jsonrpc: "2.0", id: 3, method: "resources/list" });
|
|
1477
1613
|
const resources = (resourcesRes.result as { resources: Array<{ uri: string }> }).resources;
|
|
1478
|
-
expect(resources).toHaveLength(
|
|
1614
|
+
expect(resources).toHaveLength(9);
|
|
1479
1615
|
});
|
|
1480
1616
|
|
|
1481
1617
|
test("server with empty plugins array works", async () => {
|
|
1482
1618
|
const s = new McpServer([]);
|
|
1483
1619
|
const toolsRes = await s.handleRequest({ jsonrpc: "2.0", id: 1, method: "tools/list" });
|
|
1484
1620
|
const tools = (toolsRes.result as { tools: Array<{ name: string }> }).tools;
|
|
1485
|
-
expect(tools).toHaveLength(
|
|
1621
|
+
expect(tools).toHaveLength(14);
|
|
1486
1622
|
});
|
|
1487
1623
|
});
|
|
1488
1624
|
});
|
package/src/cli/mcp/server.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { importTool, handleImport } from "./tools/import";
|
|
|
6
6
|
import { explainTool, handleExplain } from "./tools/explain";
|
|
7
7
|
import { scaffoldTool, createScaffoldHandler } from "./tools/scaffold";
|
|
8
8
|
import { searchTool, createSearchHandler } from "./tools/search";
|
|
9
|
+
import { compositesTool, createCompositesHandler } from "./tools/composites";
|
|
9
10
|
import type { LexiconPlugin } from "../../lexicon";
|
|
10
11
|
import type { McpRequest, McpResponse, McpRequestMeta, ToolDefinition, ToolHandler, ResourceDefinition } from "./types";
|
|
11
12
|
import { createSnapshotTool, createDiffTool } from "./lifecycle-tools";
|
|
@@ -90,8 +91,10 @@ export class McpServer {
|
|
|
90
91
|
private tools: Map<string, ToolDefinition> = new Map();
|
|
91
92
|
private toolHandlers: Map<string, ToolHandler> = new Map();
|
|
92
93
|
private pluginResources: Map<string, { definition: ResourceDefinition; handler: () => Promise<string> }> = new Map();
|
|
94
|
+
private plugins: LexiconPlugin[];
|
|
93
95
|
|
|
94
96
|
constructor(plugins?: LexiconPlugin[]) {
|
|
97
|
+
this.plugins = plugins ?? [];
|
|
95
98
|
// Register core tools
|
|
96
99
|
this.registerTool(buildTool, handleBuild);
|
|
97
100
|
this.registerTool(lintTool, handleLint);
|
|
@@ -99,6 +102,7 @@ export class McpServer {
|
|
|
99
102
|
this.registerTool(explainTool, handleExplain);
|
|
100
103
|
this.registerTool(scaffoldTool, createScaffoldHandler(plugins ?? []));
|
|
101
104
|
this.registerTool(searchTool, createSearchHandler(plugins ?? []));
|
|
105
|
+
this.registerTool(compositesTool, createCompositesHandler(plugins ?? []));
|
|
102
106
|
|
|
103
107
|
// Register state tools
|
|
104
108
|
const snapshot = createSnapshotTool(plugins ?? []);
|
|
@@ -252,7 +256,7 @@ export class McpServer {
|
|
|
252
256
|
return buildResourcesList(this.pluginResources);
|
|
253
257
|
|
|
254
258
|
case "resources/read":
|
|
255
|
-
return handleResourcesRead(params, this.pluginResources);
|
|
259
|
+
return handleResourcesRead(params, this.pluginResources, this.plugins);
|
|
256
260
|
|
|
257
261
|
default:
|
|
258
262
|
throw new Error(`Unknown method: ${method}`);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { CompositeEntry, LexiconPlugin } from "../../../lexicon";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `composites` tool (#2662): what composites the loaded lexicons export,
|
|
5
|
+
* what each one bundles and what it takes. "What composites do you have for
|
|
6
|
+
* aws?" is `{ lexicon: "aws" }`.
|
|
7
|
+
*
|
|
8
|
+
* Everything here reads `LexiconPlugin.composites()`, which is static catalog
|
|
9
|
+
* data. No composite is loaded or called and no provider is asked anything.
|
|
10
|
+
*/
|
|
11
|
+
export const compositesTool = {
|
|
12
|
+
name: "composites",
|
|
13
|
+
description:
|
|
14
|
+
"List the composites the loaded lexicons export: what each builds, the resource kinds it bundles, and its parameters",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object" as const,
|
|
17
|
+
properties: {
|
|
18
|
+
lexicon: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Only composites from this lexicon (e.g. 'aws', 'k8s')",
|
|
21
|
+
},
|
|
22
|
+
query: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Keyword matched against the composite's name, description, bundled resource kinds and parameter names",
|
|
25
|
+
},
|
|
26
|
+
limit: {
|
|
27
|
+
type: "number",
|
|
28
|
+
description: "Maximum number of composites to return (default: 50)",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Every composite the plugins contribute, in plugin order. A plugin whose
|
|
36
|
+
* `composites()` throws contributes nothing rather than failing the listing.
|
|
37
|
+
*/
|
|
38
|
+
export function collectComposites(plugins: LexiconPlugin[]): CompositeEntry[] {
|
|
39
|
+
const entries: CompositeEntry[] = [];
|
|
40
|
+
for (const plugin of plugins) {
|
|
41
|
+
let contributed: CompositeEntry[] = [];
|
|
42
|
+
try {
|
|
43
|
+
contributed = plugin.composites?.() ?? [];
|
|
44
|
+
} catch {
|
|
45
|
+
contributed = [];
|
|
46
|
+
}
|
|
47
|
+
for (const entry of contributed) entries.push({ ...entry, lexicon: entry.lexicon || plugin.name });
|
|
48
|
+
}
|
|
49
|
+
return entries;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* How well an entry matches a lowercased keyword: 0 for no match, higher for
|
|
54
|
+
* better. A name match beats a bundled kind, which beats a word in the
|
|
55
|
+
* description or a parameter name, so `queue` lists `LambdaSqs` ahead of a
|
|
56
|
+
* composite that merely mentions queues.
|
|
57
|
+
*/
|
|
58
|
+
export function compositeMatchScore(entry: CompositeEntry, lowerQuery: string): number {
|
|
59
|
+
const name = entry.name.toLowerCase();
|
|
60
|
+
if (name.startsWith(lowerQuery)) return 4;
|
|
61
|
+
if (name.includes(lowerQuery)) return 3;
|
|
62
|
+
if (entry.bundles.some((b) => b.toLowerCase().includes(lowerQuery))) return 2;
|
|
63
|
+
if (entry.description.toLowerCase().includes(lowerQuery)) return 1;
|
|
64
|
+
if (entry.params.some((p) => p.name.toLowerCase().includes(lowerQuery))) return 1;
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createCompositesHandler(
|
|
69
|
+
plugins: LexiconPlugin[],
|
|
70
|
+
): (params: Record<string, unknown>) => Promise<unknown> {
|
|
71
|
+
return async (params) => {
|
|
72
|
+
const lexicon = typeof params.lexicon === "string" && params.lexicon !== "" ? params.lexicon : undefined;
|
|
73
|
+
const query = typeof params.query === "string" && params.query !== "" ? params.query : undefined;
|
|
74
|
+
const limit = typeof params.limit === "number" ? params.limit : 50;
|
|
75
|
+
|
|
76
|
+
let entries = collectComposites(plugins);
|
|
77
|
+
if (lexicon) entries = entries.filter((e) => e.lexicon === lexicon);
|
|
78
|
+
|
|
79
|
+
if (query) {
|
|
80
|
+
const lowerQuery = query.toLowerCase();
|
|
81
|
+
entries = entries
|
|
82
|
+
.map((entry) => ({ entry, score: compositeMatchScore(entry, lowerQuery) }))
|
|
83
|
+
.filter(({ score }) => score > 0)
|
|
84
|
+
.sort((a, b) => b.score - a.score || a.entry.name.localeCompare(b.entry.name))
|
|
85
|
+
.map(({ entry }) => entry);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...(lexicon ? { lexicon } : {}),
|
|
90
|
+
...(query ? { query } : {}),
|
|
91
|
+
// The loaded lexicons, so an empty answer for `lexicon: "cedar"` reads
|
|
92
|
+
// as "cedar exports none" when cedar is listed, and "not loaded" when not.
|
|
93
|
+
lexicons: plugins.map((p) => p.name),
|
|
94
|
+
total: entries.length,
|
|
95
|
+
composites: entries.slice(0, limit),
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { LexiconPlugin } from "../../../lexicon";
|
|
2
|
+
import { collectComposites, compositeMatchScore } from "./composites";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Search tool definition for MCP
|
|
5
6
|
*/
|
|
6
7
|
export const searchTool = {
|
|
7
8
|
name: "search",
|
|
8
|
-
description:
|
|
9
|
+
description:
|
|
10
|
+
"Search the resource catalog and the composite catalog across loaded lexicons by keyword; a composite comes back with kind 'composite'",
|
|
9
11
|
inputSchema: {
|
|
10
12
|
type: "object" as const,
|
|
11
13
|
properties: {
|
|
12
14
|
query: {
|
|
13
15
|
type: "string",
|
|
14
|
-
description:
|
|
16
|
+
description:
|
|
17
|
+
"Search query: matches a resource's type, class name and kind, and a composite's name, description and the resource kinds it bundles",
|
|
15
18
|
},
|
|
16
19
|
lexicon: {
|
|
17
20
|
type: "string",
|
|
@@ -32,6 +35,20 @@ interface CatalogEntry {
|
|
|
32
35
|
kind?: string;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
/** A composite in search results (#2662): `kind` is always `"composite"`. */
|
|
39
|
+
interface CompositeResult {
|
|
40
|
+
kind: "composite";
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
bundles: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type SearchResult = (CatalogEntry | CompositeResult) & { lexicon: string; score: number };
|
|
47
|
+
|
|
48
|
+
function sortKey(result: SearchResult): string {
|
|
49
|
+
return "resourceType" in result ? result.resourceType ?? "" : result.name;
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
/**
|
|
36
53
|
* Create a search handler with access to loaded plugins
|
|
37
54
|
*/
|
|
@@ -44,7 +61,7 @@ export function createSearchHandler(
|
|
|
44
61
|
const limit = (params.limit as number) ?? 20;
|
|
45
62
|
|
|
46
63
|
const lowerQuery = query.toLowerCase();
|
|
47
|
-
const results:
|
|
64
|
+
const results: SearchResult[] = [];
|
|
48
65
|
|
|
49
66
|
const candidates = lexiconFilter
|
|
50
67
|
? plugins.filter((p) => p.name === lexiconFilter)
|
|
@@ -86,10 +103,35 @@ export function createSearchHandler(
|
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
//
|
|
106
|
+
// Composites (#2662), so one query finds both a resource type and the
|
|
107
|
+
// composite that bundles it. A name or bundled-kind prefix ranks with the
|
|
108
|
+
// resource prefix matches. The full entry (params included) is the
|
|
109
|
+
// `composites` tool's answer; search keeps a result short.
|
|
110
|
+
for (const entry of collectComposites(candidates)) {
|
|
111
|
+
const score = compositeMatchScore(entry, lowerQuery);
|
|
112
|
+
if (score === 0) continue;
|
|
113
|
+
const isPrefix =
|
|
114
|
+
entry.name.toLowerCase().startsWith(lowerQuery) ||
|
|
115
|
+
entry.bundles.some((b) => b.toLowerCase().startsWith(lowerQuery));
|
|
116
|
+
results.push({
|
|
117
|
+
kind: "composite",
|
|
118
|
+
name: entry.name,
|
|
119
|
+
description: entry.description,
|
|
120
|
+
bundles: entry.bundles,
|
|
121
|
+
lexicon: entry.lexicon,
|
|
122
|
+
score: isPrefix ? 1 : 0,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Sort: prefix matches first; within a tier composites ahead of resource
|
|
127
|
+
// types, since a handful of composites would otherwise sort behind every
|
|
128
|
+
// `AWS::...` type the same word prefixes; then alphabetical.
|
|
90
129
|
results.sort((a, b) => {
|
|
91
130
|
if (a.score !== b.score) return b.score - a.score;
|
|
92
|
-
|
|
131
|
+
const aComposite = a.kind === "composite" ? 0 : 1;
|
|
132
|
+
const bComposite = b.kind === "composite" ? 0 : 1;
|
|
133
|
+
if (aComposite !== bComposite) return aComposite - bComposite;
|
|
134
|
+
return sortKey(a).localeCompare(sortKey(b));
|
|
93
135
|
});
|
|
94
136
|
|
|
95
137
|
const limited = results.slice(0, limit);
|
package/src/cli/registry.ts
CHANGED
|
@@ -285,10 +285,22 @@ export interface ParsedArgs {
|
|
|
285
285
|
kind?: string;
|
|
286
286
|
/** Every `--kind` given, in order: `chant workspace graph --intent` reads each (#2651). */
|
|
287
287
|
kinds?: string[];
|
|
288
|
+
/** `chant workspace graph --composites` (#2662): print each composite instance with the components that can deploy it. */
|
|
289
|
+
composites?: boolean;
|
|
288
290
|
/** `chant workspace graph --intent <path[:start-end]>` (#2651): the region the intent graph is over. */
|
|
289
291
|
intent?: string;
|
|
290
292
|
/** `chant workspace records --current` (#2546): leave out records a closed record supersedes. */
|
|
291
293
|
current?: boolean;
|
|
294
|
+
/** `chant workspace records amend <id> --set <file|->` (#2670): the JSON fields to set. */
|
|
295
|
+
set?: string;
|
|
296
|
+
/** `chant workspace records review <id> --verdict <v>` (#2670): agree, dissent or abstain. */
|
|
297
|
+
verdict?: string;
|
|
298
|
+
/** `chant workspace records review <id> --by <principal>` (#2670): the reviewer, as the caller names them. */
|
|
299
|
+
by?: string;
|
|
300
|
+
/** `chant workspace records review <id> --session <id>` (#2670): the review session the verdict was given in. */
|
|
301
|
+
session?: string;
|
|
302
|
+
/** `chant workspace records new <kind> --prefix <prefix>` (#2670): the id prefix to allocate under. */
|
|
303
|
+
prefix?: string;
|
|
292
304
|
/** `chant workspace records|verify --require attested` (#2547): the provenance level a gate requires. */
|
|
293
305
|
require?: string;
|
|
294
306
|
/**
|
|
@@ -414,7 +426,7 @@ export interface ParsedArgs {
|
|
|
414
426
|
plan?: string;
|
|
415
427
|
/** `chant operator log --op <name>` (#2029) — restrict the tick history to one ConvergeOp by name. Omitted, every discovered ConvergeOp's ticks are merged into one timeline. */
|
|
416
428
|
op?: string;
|
|
417
|
-
/** `chant operator log --since <iso>` (#2029) — only entries at or after this ISO-8601 instant. */
|
|
429
|
+
/** `chant operator log --since <iso>` (#2029) — only entries at or after this ISO-8601 instant. `chant workspace records --since <rev>` (#2673) — the revision to compare the records with. */
|
|
418
430
|
since?: string;
|
|
419
431
|
/** `chant operator log --limit <n>` (#2029) — keep only the newest n entries (still printed oldest-first). */
|
|
420
432
|
limit?: number;
|