@intentius/chant 0.86.0 → 0.88.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/handlers/misc.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +5 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/version.d.ts +8 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/sessions.d.ts +9 -0
- package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +13 -0
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/environments.d.ts +80 -0
- package/dist/workspace/environments.d.ts.map +1 -0
- package/dist/workspace/intent.d.ts +6 -1
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +20 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +12 -2
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +41 -0
- package/dist/workspace/records-close.d.ts.map +1 -0
- package/dist/workspace/records-since.d.ts +40 -1
- package/dist/workspace/records-since.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +117 -12
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +84 -14
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runtimes.d.ts +6 -0
- package/dist/workspace/runtimes.d.ts.map +1 -1
- package/dist/workspace/session-kinds.d.ts +28 -0
- package/dist/workspace/session-kinds.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +2 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/trust/seal.d.ts +127 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +7 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/work.d.ts +3 -3
- package/package.json +1 -1
- package/src/cli/handlers/misc.ts +1 -9
- package/src/cli/main.ts +29 -10
- package/src/cli/mcp/server.test.ts +14 -1
- package/src/cli/mcp/server.ts +3 -1
- package/src/cli/registry.ts +5 -0
- package/src/cli/version.ts +15 -0
- package/src/workspace/__fixtures__/sessions.ts +41 -0
- package/src/workspace/composites.schema.json +68 -3
- package/src/workspace/composites.test.ts +119 -5
- package/src/workspace/composites.ts +26 -7
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +23 -0
- package/src/workspace/environments.ts +165 -0
- package/src/workspace/intent-gaps.test.ts +217 -0
- package/src/workspace/intent.schema.json +23 -1
- package/src/workspace/intent.test.ts +2 -0
- package/src/workspace/intent.ts +35 -3
- package/src/workspace/read-contract.test.ts +3 -0
- package/src/workspace/reason-codes.test.ts +9 -3
- package/src/workspace/reason-codes.ts +24 -4
- package/src/workspace/record-assets.test.ts +4 -3
- package/src/workspace/records-amend.schema.json +30 -1
- package/src/workspace/records-cli.ts +113 -14
- package/src/workspace/records-close.schema.json +192 -0
- package/src/workspace/records-close.ts +129 -0
- package/src/workspace/records-contract.test.ts +4 -3
- package/src/workspace/records-new.schema.json +25 -0
- package/src/workspace/records-review.schema.json +81 -3
- package/src/workspace/records-sessions-write.test.ts +274 -0
- package/src/workspace/records-since.schema.json +27 -2
- package/src/workspace/records-since.ts +120 -6
- package/src/workspace/records-write-contract.test.ts +5 -1
- package/src/workspace/records-write.test.ts +4 -2
- package/src/workspace/records-write.ts +336 -43
- package/src/workspace/records.schema.json +37 -3
- package/src/workspace/records.ts +145 -25
- package/src/workspace/runtimes.ts +12 -3
- package/src/workspace/session-kinds.ts +79 -0
- package/src/workspace/status.ts +1 -1
- package/src/workspace/trust/record-seal.test.ts +315 -0
- package/src/workspace/trust/seal.test.ts +222 -0
- package/src/workspace/trust/seal.ts +289 -0
- package/src/workspace/trust/ssh-commit.ts +2 -2
- package/src/workspace/work.test.ts +3 -1
- package/src/workspace/work.ts +4 -4
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sealed verdicts (#2687, part of #2547): a review entry signed by the
|
|
3
|
+
* principal it names, so the quorum counts it only when that principal
|
|
4
|
+
* signed it.
|
|
5
|
+
*
|
|
6
|
+
* A seal is a detached ssh signature, the same mechanism as the ssh-commit
|
|
7
|
+
* attestor (./ssh-commit.ts): `ssh-keygen -Y sign` to make it, and
|
|
8
|
+
* `ssh-keygen -Y verify` against the signers read at base to check it. It is
|
|
9
|
+
* made in its own namespace, `chant-review`, so neither a commit signature
|
|
10
|
+
* nor a signer-set signature can stand in for one.
|
|
11
|
+
*
|
|
12
|
+
* The signed bytes are the record id, the verdict's digest, the verdict, the
|
|
13
|
+
* reviewer and the date, each on its own line with no final newline. The
|
|
14
|
+
* digest is the record's text digest (`recordTextDigest`), so the seal binds
|
|
15
|
+
* the verdict to the text it judged. The seal lives inside the reviews block,
|
|
16
|
+
* which the digest leaves out, so sealing a verdict never moves the digest.
|
|
17
|
+
*
|
|
18
|
+
* A record's author seal (#2688) works the same way, in its own namespace,
|
|
19
|
+
* `chant-record`. It sits in the record's top-level `seal` field, which the
|
|
20
|
+
* digest also leaves out, and signs the record id, the digest, the author
|
|
21
|
+
* (the kind's `reviews.decider` field, `decided_by` for decisions) and the
|
|
22
|
+
* state. An amendment moves the digest, so it has to be signed again.
|
|
23
|
+
*/
|
|
24
|
+
import type { SealCode } from "../records.js";
|
|
25
|
+
import { type TrustPolicy } from "./policy.js";
|
|
26
|
+
/** The ssh signature namespace of a verdict seal. */
|
|
27
|
+
export declare const REVIEW_SEAL_NAMESPACE = "chant-review";
|
|
28
|
+
/** The ssh signature namespace of a record's author seal (#2688). */
|
|
29
|
+
export declare const RECORD_SEAL_NAMESPACE = "chant-record";
|
|
30
|
+
/** A seal as a review entry holds it. */
|
|
31
|
+
export interface VerdictSeal {
|
|
32
|
+
/** The principal who signed: the reviewer, as the signers file names them. */
|
|
33
|
+
signer: string;
|
|
34
|
+
/** The signing key's fingerprint, such as `SHA256:...`. Reported, never trusted: the signature is the proof. */
|
|
35
|
+
key: string;
|
|
36
|
+
/** The armored ssh signature. */
|
|
37
|
+
signature: string;
|
|
38
|
+
}
|
|
39
|
+
/** What a verdict's seal establishes. */
|
|
40
|
+
export interface SealCheck {
|
|
41
|
+
/** true: the seal verifies for the reviewer against the signers at base. false: it is missing under an active policy, or it fails. null: nothing here can say. */
|
|
42
|
+
attested: boolean | null;
|
|
43
|
+
/** Why it is not attested. Absent when `attested` is true. */
|
|
44
|
+
code?: SealCode;
|
|
45
|
+
message: string;
|
|
46
|
+
/** The fingerprint of the key that made the signature, when the signature was checked. */
|
|
47
|
+
key?: string;
|
|
48
|
+
}
|
|
49
|
+
/** The verdict a seal covers. */
|
|
50
|
+
export interface SealedVerdict {
|
|
51
|
+
record: string | null;
|
|
52
|
+
reviewer: string;
|
|
53
|
+
verdict: string;
|
|
54
|
+
on: unknown;
|
|
55
|
+
digest: string | null;
|
|
56
|
+
seal: unknown;
|
|
57
|
+
}
|
|
58
|
+
/** The bytes a verdict seal signs. */
|
|
59
|
+
export declare function reviewSealPayload(record: string, digest: string, verdict: string, reviewer: string, on: string): Buffer;
|
|
60
|
+
/**
|
|
61
|
+
* The bytes a record's author seal signs (#2688): the id, the digest, the
|
|
62
|
+
* author and the state, joined by LF with no final newline. A kind without
|
|
63
|
+
* states signs an empty last line.
|
|
64
|
+
*/
|
|
65
|
+
export declare function recordSealPayload(record: string, digest: string, author: string, state: string | null): Buffer;
|
|
66
|
+
/** The record an author seal covers (#2688). */
|
|
67
|
+
export interface SealedRecord {
|
|
68
|
+
record: string | null;
|
|
69
|
+
digest: string;
|
|
70
|
+
/** The author as the record names them, or null when it names none. */
|
|
71
|
+
author: string | null;
|
|
72
|
+
/** The kind's field that names the author, for messages. */
|
|
73
|
+
authorField: string;
|
|
74
|
+
state: string | null;
|
|
75
|
+
seal: unknown;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Check a verdict's seal. With a signers file active at base, it verifies
|
|
79
|
+
* against the keys listed there for the reviewer. With none, a seal present
|
|
80
|
+
* is checked for integrity only (`ssh-keygen -Y check-novalidate`): nothing
|
|
81
|
+
* says whose key it is.
|
|
82
|
+
*/
|
|
83
|
+
export declare function checkVerdictSeal(policy: TrustPolicy, v: SealedVerdict): SealCheck;
|
|
84
|
+
/**
|
|
85
|
+
* Check a record's author seal (#2688), as a verdict's is checked, over
|
|
86
|
+
* {@link recordSealPayload} in the `chant-record` namespace. A record that
|
|
87
|
+
* names no author and carries no seal is `seal-missing` with `attested`
|
|
88
|
+
* null even under an active policy: it claims no author to attest.
|
|
89
|
+
*/
|
|
90
|
+
export declare function checkRecordSeal(policy: TrustPolicy, r: SealedRecord): SealCheck;
|
|
91
|
+
/** Why a seal could not be made. */
|
|
92
|
+
export declare class SealError extends Error {
|
|
93
|
+
constructor(message: string);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The key `--sign` names: the file given, or with none, git's
|
|
97
|
+
* `user.signingkey` when `gpg.format` is `ssh`, as `git commit -S` reads it.
|
|
98
|
+
* A literal public key (`key::ssh-...`, or `ssh-...`) signs through the ssh
|
|
99
|
+
* agent holding its private half. Returns the file to pass to ssh-keygen,
|
|
100
|
+
* and a cleanup for a temporary one.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveSigningKey(sign: string | true, cwd: string): {
|
|
103
|
+
file: string;
|
|
104
|
+
cleanup: () => void;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Seal a verdict with the key in `keyFile` (a private key, or a public key
|
|
108
|
+
* whose private half the ssh agent holds). Throws a {@link SealError}.
|
|
109
|
+
*/
|
|
110
|
+
export declare function sealVerdict(keyFile: string, v: {
|
|
111
|
+
record: string;
|
|
112
|
+
digest: string;
|
|
113
|
+
verdict: string;
|
|
114
|
+
reviewer: string;
|
|
115
|
+
on: string;
|
|
116
|
+
}): VerdictSeal;
|
|
117
|
+
/**
|
|
118
|
+
* Seal a record's author (#2688) with the key in `keyFile`, over
|
|
119
|
+
* {@link recordSealPayload}. Throws a {@link SealError}.
|
|
120
|
+
*/
|
|
121
|
+
export declare function sealRecord(keyFile: string, r: {
|
|
122
|
+
record: string;
|
|
123
|
+
digest: string;
|
|
124
|
+
author: string;
|
|
125
|
+
state: string | null;
|
|
126
|
+
}): VerdictSeal;
|
|
127
|
+
//# sourceMappingURL=seal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/seal.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,6 +25,13 @@ export interface SignedCommit {
|
|
|
25
25
|
* continuation lines, to get the bytes it signed.
|
|
26
26
|
*/
|
|
27
27
|
export declare function splitSignedCommit(raw: Buffer, header?: string): SignedCommit;
|
|
28
|
+
/** Run ssh-keygen; `missing` is true when it is not installed. Shared with the verdict seals (./seal.ts). */
|
|
29
|
+
export declare function sshKeygen(args: string[], input?: Buffer): {
|
|
30
|
+
status: number | null;
|
|
31
|
+
stdout: string;
|
|
32
|
+
stderr: string;
|
|
33
|
+
missing: boolean;
|
|
34
|
+
};
|
|
28
35
|
/**
|
|
29
36
|
* Check a detached ssh signature over `payload` in `namespace` against
|
|
30
37
|
* `signers`. Shared with the rotation check (#2553), which signs signer sets
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssh-commit.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/ssh-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAsC,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI7D,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,YAAY,CAsB9E;
|
|
1
|
+
{"version":3,"file":"ssh-commit.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/ssh-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAsC,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI7D,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,YAAY,CAsB9E;AAUD,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAIrI;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAuBjG;AAED,eAAO,MAAM,iBAAiB,EAAE,cAW/B,CAAC"}
|
package/dist/workspace/work.d.ts
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* - `implements`: each decision it names, with that decision's state;
|
|
18
18
|
*
|
|
19
19
|
* and each decision `implementedBy`, the work records naming it. The
|
|
20
|
-
* warnings are closed codes. `work-done-gap-open` is not raised here:
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* warnings are closed codes. `work-done-gap-open` is not raised here: it
|
|
21
|
+
* takes a walk of the item's region, which `graph --intent` makes and
|
|
22
|
+
* `records` asks it for (#2686). It never writes a record.
|
|
23
23
|
*/
|
|
24
24
|
import { type LoadedRecordKind, type ReadRecordsOptions, type RecordEntry } from "./records.js";
|
|
25
25
|
/** Why a work record carries a warning. Closed, like the record warning codes. */
|
package/package.json
CHANGED
package/src/cli/handlers/misc.ts
CHANGED
|
@@ -11,16 +11,8 @@ import { homedir } from "os";
|
|
|
11
11
|
import type { ResourceSelector } from "../../lexicon";
|
|
12
12
|
import { formatError, formatSuccess, formatWarning, formatBold } from "../format";
|
|
13
13
|
import type { CommandContext } from "../registry";
|
|
14
|
-
import { createRequire } from "module";
|
|
15
14
|
import { listComponents, describeComponent } from "../../components/cli-support";
|
|
16
|
-
|
|
17
|
-
const CHANT_VERSION: string = (() => {
|
|
18
|
-
try {
|
|
19
|
-
return createRequire(import.meta.url)("../../../package.json").version ?? "0.0.0";
|
|
20
|
-
} catch {
|
|
21
|
-
return "0.0.0";
|
|
22
|
-
}
|
|
23
|
-
})();
|
|
15
|
+
import { CHANT_VERSION } from "../version";
|
|
24
16
|
|
|
25
17
|
const AUDIT_FORMATS: AuditFormat[] = ["stylish", "json", "sarif", "markdown", "html"];
|
|
26
18
|
const AUDIT_TIERS: AuditTier[] = ["merge-worthy", "all"];
|
package/src/cli/main.ts
CHANGED
|
@@ -387,6 +387,12 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
387
387
|
// `chant workspace records review <id> --by <principal>` (#2670): whoever the caller says.
|
|
388
388
|
result.by = args[++i];
|
|
389
389
|
if (!result.by || result.by.startsWith("-")) throw new Error("--by needs the reviewer: --by <principal>");
|
|
390
|
+
} else if (arg === "--sign") {
|
|
391
|
+
// `chant workspace records review <id> --sign [<key file>]` (#2687): seal the verdict.
|
|
392
|
+
// `records new` and `records amend` take it too, to seal the record's author (#2688).
|
|
393
|
+
// With no key file, git's user.signingkey, as `git commit -S` reads it.
|
|
394
|
+
const next = args[i + 1];
|
|
395
|
+
result.sign = next !== undefined && !next.startsWith("-") ? args[++i] : true;
|
|
390
396
|
} else if (arg === "--session") {
|
|
391
397
|
// `chant workspace records review <id> --session <id>` (#2670)
|
|
392
398
|
result.session = args[++i];
|
|
@@ -752,32 +758,45 @@ Workspace (level 1, #2524):
|
|
|
752
758
|
--require attested exits 2 if any record is not
|
|
753
759
|
attested. A pinned file that changed is a warning,
|
|
754
760
|
asset-drift or asset-missing
|
|
755
|
-
workspace records [--kind <kind file>] --since <rev> [--at <rev>] [--json]
|
|
761
|
+
workspace records [--kind <kind file>] --since <rev|session id> [--at <rev>] [--json]
|
|
756
762
|
What changed in the records between <rev> and --at
|
|
757
763
|
(default: the working tree): new and removed records,
|
|
758
764
|
state transitions, new verdicts, new supersessions
|
|
759
|
-
and changed pins
|
|
765
|
+
and changed pins. A session id compares the commits
|
|
766
|
+
the session opened and closed at
|
|
760
767
|
workspace records pin <path>
|
|
761
768
|
Print the path from the workspace root and the
|
|
762
769
|
sha256 of a file, for a decision's evidence pin
|
|
763
|
-
workspace records new [<kind file>] --from <file|-> [--prefix <prefix>] [--dry-run]
|
|
770
|
+
workspace records new [<kind file>] --from <file|-> [--prefix <prefix>] [--sign [<key file>]] [--dry-run]
|
|
764
771
|
Write one new record in the kind's directory from
|
|
765
772
|
the JSON fields given, after validating them as
|
|
766
773
|
records would read them. Without a kind file, the one
|
|
767
774
|
kind the declaration names. Allocates the next id when
|
|
768
|
-
the fields hold none.
|
|
769
|
-
|
|
770
|
-
|
|
775
|
+
the fields hold none. --sign seals the record's author
|
|
776
|
+
(decided_by for decisions) with an ssh key. Prints
|
|
777
|
+
{path, id} as JSON and never commits
|
|
778
|
+
workspace records amend <id> [--kind <kind file>] --set <file|-> [--sign [<key file>]] [--dry-run]
|
|
771
779
|
Set top-level fields of one record. A closed record
|
|
772
780
|
never changes, and an approved one changes only its
|
|
773
781
|
state (upward), pins and reviews; anything else is
|
|
774
|
-
refused with amend-supersede-instead.
|
|
782
|
+
refused with amend-supersede-instead. --sign seals
|
|
783
|
+
the author again; without it an amendment removes
|
|
784
|
+
the author seal and says so. Prints
|
|
775
785
|
{path, id, changed}
|
|
776
|
-
workspace records review <id> [--kind <kind file>] --verdict agree|dissent|abstain --by <principal> [--note <text>] [--session <id>] [--dry-run]
|
|
786
|
+
workspace records review <id> [--kind <kind file>] --verdict agree|dissent|abstain --by <principal> [--note <text>] [--session <id>] [--sign [<key file>]] [--dry-run]
|
|
777
787
|
Append a review to one record, dated and bound to
|
|
778
788
|
the digest of the record text. A dissent needs
|
|
779
|
-
--note.
|
|
780
|
-
|
|
789
|
+
--note. --sign seals it with an ssh key (git's
|
|
790
|
+
user.signingkey without a file); under a signers
|
|
791
|
+
file at base only a sealed verdict counts. With
|
|
792
|
+
--session, the session must be open, and the verdict
|
|
793
|
+
is appended to its verdicts too. Prints
|
|
794
|
+
{path, id, review}
|
|
795
|
+
workspace records close <session id> [--kind <session kind file>] [--dry-run]
|
|
796
|
+
Close an open review session: its state, close time,
|
|
797
|
+
closing commit and seal, in one write. Without
|
|
798
|
+
--kind, the one session kind the declaration names.
|
|
799
|
+
Prints {path, id, changed, seal, closedRev}
|
|
781
800
|
workspace verify [--base <rev>] [--head <rev>] [--require attested]
|
|
782
801
|
Check the commits in base..head against the signers
|
|
783
802
|
and roles read from base. A change to the signers file
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { McpServer } from "./server";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
3
4
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { tmpdir } from "node:os";
|
|
6
7
|
import type { LexiconPlugin } from "../../lexicon";
|
|
7
8
|
import type { Serializer } from "../../serializer";
|
|
8
9
|
|
|
10
|
+
/** The version in `packages/core/package.json`, read here apart from the code under test. */
|
|
11
|
+
const CORE_VERSION: string = JSON.parse(readFileSync(join(import.meta.dirname, "..", "..", "..", "package.json"), "utf-8")).version;
|
|
12
|
+
|
|
9
13
|
function createMockPlugin(overrides?: Partial<LexiconPlugin>): LexiconPlugin {
|
|
10
14
|
return {
|
|
11
15
|
name: "mock",
|
|
@@ -51,7 +55,16 @@ describe("McpServer", () => {
|
|
|
51
55
|
expect(result.protocolVersion).toBe("2026-07-28");
|
|
52
56
|
expect(result.capabilities).toBeDefined();
|
|
53
57
|
expect((result.serverInfo as Record<string, unknown>).name).toBe("chant");
|
|
54
|
-
expect((result.serverInfo as Record<string, unknown>).version).toBe(
|
|
58
|
+
expect((result.serverInfo as Record<string, unknown>).version).toBe(CORE_VERSION);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("server info names the installed chant's version, from core's package.json (#2689)", async () => {
|
|
62
|
+
expect(CORE_VERSION).toMatch(/^\d+\.\d+\.\d+/);
|
|
63
|
+
expect(CORE_VERSION).not.toBe("0.1.0");
|
|
64
|
+
for (const params of [{}, { protocolVersion: "2024-11-05" }]) {
|
|
65
|
+
const response = await server.handleRequest({ jsonrpc: "2.0", id: 1, method: "initialize", params });
|
|
66
|
+
expect(((response.result as Record<string, unknown>).serverInfo as Record<string, unknown>).version).toBe(CORE_VERSION);
|
|
67
|
+
}
|
|
55
68
|
});
|
|
56
69
|
|
|
57
70
|
test("capabilities include tools and resources", async () => {
|
package/src/cli/mcp/server.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { createSnapshotTool, createDiffTool } from "./lifecycle-tools";
|
|
|
13
13
|
import { setGateOrigin } from "../../lifecycle/gate-origin";
|
|
14
14
|
import { createOpListTool, createOpRunTool, createOpStatusTool, createOpApproveTool, createOpReportTool } from "./op-tools";
|
|
15
15
|
import { buildResourcesList, handleResourcesRead } from "./resource-handlers";
|
|
16
|
+
import { CHANT_VERSION } from "../version";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Protocol versions this server understands, newest first. `initialize` and
|
|
@@ -223,7 +224,8 @@ export class McpServer {
|
|
|
223
224
|
return {
|
|
224
225
|
protocolVersion: negotiateProtocolVersion(protocolVersion),
|
|
225
226
|
capabilities: { tools: {}, resources: {} },
|
|
226
|
-
|
|
227
|
+
// The installed chant's version, so a client can tell which chant it talks to (#2689).
|
|
228
|
+
serverInfo: { name: "chant", version: CHANT_VERSION },
|
|
227
229
|
};
|
|
228
230
|
}
|
|
229
231
|
|
package/src/cli/registry.ts
CHANGED
|
@@ -297,6 +297,11 @@ export interface ParsedArgs {
|
|
|
297
297
|
verdict?: string;
|
|
298
298
|
/** `chant workspace records review <id> --by <principal>` (#2670): the reviewer, as the caller names them. */
|
|
299
299
|
by?: string;
|
|
300
|
+
/**
|
|
301
|
+
* `chant workspace records review <id> --sign [<key file>]` (#2687): the key that seals the verdict, or true for git's user.signingkey.
|
|
302
|
+
* On `records new` and `records amend`, the key that seals the record's author (#2688).
|
|
303
|
+
*/
|
|
304
|
+
sign?: string | true;
|
|
300
305
|
/** `chant workspace records review <id> --session <id>` (#2670): the review session the verdict was given in. */
|
|
301
306
|
session?: string;
|
|
302
307
|
/** `chant workspace records new <kind> --prefix <prefix>` (#2670): the id prefix to allocate under. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The installed chant's version, read from `@intentius/chant`'s own
|
|
5
|
+
* package.json, or "0.0.0" when it can't be read. The path is the same from
|
|
6
|
+
* `src/cli/` and `dist/cli/`, so it holds whether chant runs from source or
|
|
7
|
+
* from its build.
|
|
8
|
+
*/
|
|
9
|
+
export const CHANT_VERSION: string = (() => {
|
|
10
|
+
try {
|
|
11
|
+
return (createRequire(import.meta.url)("../../package.json") as { version?: string }).version ?? "0.0.0";
|
|
12
|
+
} catch {
|
|
13
|
+
return "0.0.0";
|
|
14
|
+
}
|
|
15
|
+
})();
|
|
@@ -64,3 +64,44 @@ export function reviewed(root: string, reviewers: string[], session: string, sta
|
|
|
64
64
|
.replace(/^reviews: .*$/m, reviewers.length ? `reviews:\n${reviews}` : "reviews: []"),
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Declare the fixture's two kinds in a chant.workspace.json (#2693): the
|
|
70
|
+
* decisions at the root and the session kind in the design member, as the
|
|
71
|
+
* reference workspace declares them, so review --session, close and
|
|
72
|
+
* --since <session id> find the session kind.
|
|
73
|
+
*/
|
|
74
|
+
export function declareSessions(root: string): void {
|
|
75
|
+
writeFileSync(
|
|
76
|
+
join(root, "chant.workspace.json"),
|
|
77
|
+
`${JSON.stringify(
|
|
78
|
+
{
|
|
79
|
+
name: "sessions",
|
|
80
|
+
schema: 1,
|
|
81
|
+
members: [{ name: "design", dir: "design", kind: "other", because: "the session fixture", records: [{ kind: "sessions/session.kind.mjs" }] }],
|
|
82
|
+
records: [{ kind: DECISIONS_KIND }],
|
|
83
|
+
pins: [],
|
|
84
|
+
},
|
|
85
|
+
null,
|
|
86
|
+
2,
|
|
87
|
+
)}\n`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The fields of a new open session, as a UI sends them to records new. */
|
|
92
|
+
export function newSessionFields(over: Record<string, unknown> = {}): string {
|
|
93
|
+
return JSON.stringify({
|
|
94
|
+
schema: 1,
|
|
95
|
+
title: "Second walk",
|
|
96
|
+
state: "open",
|
|
97
|
+
agenda: [{ record: "ref-001" }, { record: "ref-002" }],
|
|
98
|
+
attendance: [
|
|
99
|
+
{ principal: "lex00", class: "person" },
|
|
100
|
+
{ principal: "alice", class: "person" },
|
|
101
|
+
],
|
|
102
|
+
opened: "2026-09-25T09:00:00Z",
|
|
103
|
+
closed: null,
|
|
104
|
+
verdicts: [],
|
|
105
|
+
...over,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://intentius.io/chant/schemas/workspace/composites/v1/composites.schema.json",
|
|
4
4
|
"title": "chant workspace graph --composites output",
|
|
5
|
-
"description": "What `chant workspace graph --composites` prints: each composite instance the workspace's members declare, joined to the components whose contract can deploy it (#2662). Each member of kind chant is read through its own `chant graph --format ir` and `chant graph --components --format ir`, under its own toolchain. An instance with no component is listed with an empty `components` array. chant supplies the rows and never picks one: each match says how it was made and how it crosses members. Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists. Contract version 1 is written by chant 0.81.0 and newer, and this document was added to it by #2662; a chant without --composites refuses the flag. Each component lists the runtimes it can deploy on, read from its member's chant.config.ts (#2674). Every code is in the one closed list of `reason-codes.ts`.",
|
|
5
|
+
"description": "What `chant workspace graph --composites` prints: each composite instance the workspace's members declare, joined to the components whose contract can deploy it (#2662). Each member of kind chant is read through its own `chant graph --format ir` and `chant graph --components --format ir`, under its own toolchain. An instance with no component is listed with an empty `components` array. chant supplies the rows and never picks one: each match says how it was made and how it crosses members. Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists. Contract version 1 is written by chant 0.81.0 and newer, and this document was added to it by #2662; a chant without --composites refuses the flag. Each component lists the runtimes it can deploy on, read from its member's chant.config.ts (#2674), and the environments it may deploy to, from that config and the member's release ledger on chant/lifecycle (#2695). Every code is in the one closed list of `reason-codes.ts`.",
|
|
6
6
|
"oneOf": [
|
|
7
7
|
{
|
|
8
8
|
"$ref": "#/$defs/result"
|
|
@@ -131,7 +131,8 @@
|
|
|
131
131
|
"status",
|
|
132
132
|
"reason",
|
|
133
133
|
"chant",
|
|
134
|
-
"runtimeReasons"
|
|
134
|
+
"runtimeReasons",
|
|
135
|
+
"environmentReasons"
|
|
135
136
|
],
|
|
136
137
|
"properties": {
|
|
137
138
|
"name": {
|
|
@@ -210,6 +211,30 @@
|
|
|
210
211
|
}
|
|
211
212
|
}
|
|
212
213
|
}
|
|
214
|
+
},
|
|
215
|
+
"environmentReasons": {
|
|
216
|
+
"description": "Why the environments of the member's components are only local, or leave out one its ledger has: its chant.config.ts could not be read (the same read and code as runtimeReasons), declares no environments, or doesn't cover an environment the ledger has, or the ledger couldn't be listed. Empty for a member not of kind chant, and when nothing needs saying.",
|
|
217
|
+
"type": "array",
|
|
218
|
+
"items": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"required": [
|
|
221
|
+
"code",
|
|
222
|
+
"message"
|
|
223
|
+
],
|
|
224
|
+
"properties": {
|
|
225
|
+
"code": {
|
|
226
|
+
"enum": [
|
|
227
|
+
"runtimes-config-unreadable",
|
|
228
|
+
"environments-none-declared",
|
|
229
|
+
"environments-ledger-undeclared",
|
|
230
|
+
"environments-ledger-unreadable"
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"message": {
|
|
234
|
+
"type": "string"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
213
238
|
}
|
|
214
239
|
},
|
|
215
240
|
"if": {
|
|
@@ -351,7 +376,8 @@
|
|
|
351
376
|
"archetype",
|
|
352
377
|
"composites",
|
|
353
378
|
"file",
|
|
354
|
-
"runtimes"
|
|
379
|
+
"runtimes",
|
|
380
|
+
"environments"
|
|
355
381
|
],
|
|
356
382
|
"properties": {
|
|
357
383
|
"id": {
|
|
@@ -400,6 +426,14 @@
|
|
|
400
426
|
"items": {
|
|
401
427
|
"$ref": "#/$defs/runtime"
|
|
402
428
|
}
|
|
429
|
+
},
|
|
430
|
+
"environments": {
|
|
431
|
+
"description": "The environments the component may deploy to with chant run --components --env in its member: local first, the default, then each name the member's chant.config.ts declares in environments (patterns left out), in config order, then each environment with a release ledger for the member on chant/lifecycle that the config covers, sorted. The component contract declares no environments.",
|
|
432
|
+
"type": "array",
|
|
433
|
+
"minItems": 1,
|
|
434
|
+
"items": {
|
|
435
|
+
"$ref": "#/$defs/environment"
|
|
436
|
+
}
|
|
403
437
|
}
|
|
404
438
|
}
|
|
405
439
|
},
|
|
@@ -528,6 +562,37 @@
|
|
|
528
562
|
"description": "The command that deploys the component on this runtime, run in the member's directory: chant run --components <name>, with --on <runtime> for any runtime but local."
|
|
529
563
|
}
|
|
530
564
|
}
|
|
565
|
+
},
|
|
566
|
+
"environment": {
|
|
567
|
+
"type": "object",
|
|
568
|
+
"required": [
|
|
569
|
+
"name",
|
|
570
|
+
"default",
|
|
571
|
+
"source",
|
|
572
|
+
"command"
|
|
573
|
+
],
|
|
574
|
+
"properties": {
|
|
575
|
+
"name": {
|
|
576
|
+
"type": "string",
|
|
577
|
+
"description": "What --env takes."
|
|
578
|
+
},
|
|
579
|
+
"default": {
|
|
580
|
+
"type": "boolean",
|
|
581
|
+
"description": "True for the environment chant run --components deploys to without --env. That is local, since chant.config.ts names no default environment."
|
|
582
|
+
},
|
|
583
|
+
"source": {
|
|
584
|
+
"enum": [
|
|
585
|
+
"config",
|
|
586
|
+
"ledger",
|
|
587
|
+
"builtin"
|
|
588
|
+
],
|
|
589
|
+
"description": "Where the name came from, the first of these that names it: config for the member's chant.config.ts environments, ledger for a release ledger on chant/lifecycle, builtin for local when neither names it."
|
|
590
|
+
},
|
|
591
|
+
"command": {
|
|
592
|
+
"type": "string",
|
|
593
|
+
"description": "The command that deploys the component to this environment on its default runtime, run in the member's directory: chant run --components <name>, with --on <runtime> when the default runtime isn't local, and --env <env> for any environment but the default."
|
|
594
|
+
}
|
|
595
|
+
}
|
|
531
596
|
}
|
|
532
597
|
}
|
|
533
598
|
}
|