@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
|
@@ -43,6 +43,11 @@
|
|
|
43
43
|
"integrity": { "type": ["string", "null"] }
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
},
|
|
47
|
+
"records": {
|
|
48
|
+
"description": "Added in contract 1 by #2680. The workspace's own record kinds, from the declaration's top-level records, in file order.",
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": { "$ref": "#/$defs/recordKind" }
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
},
|
|
@@ -89,6 +94,11 @@
|
|
|
89
94
|
"upstream": { "type": ["string", "null"] },
|
|
90
95
|
"because": { "type": ["string", "null"] },
|
|
91
96
|
"readable": { "type": "boolean", "description": "True exactly when reason is null." },
|
|
97
|
+
"records": {
|
|
98
|
+
"description": "Added in contract 1 by #2680. The record kinds the member declares, in file order. A kind that can't be loaded does not make the member unreadable.",
|
|
99
|
+
"type": "array",
|
|
100
|
+
"items": { "$ref": "#/$defs/recordKind" }
|
|
101
|
+
},
|
|
92
102
|
"reason": {
|
|
93
103
|
"oneOf": [
|
|
94
104
|
{ "type": "null" },
|
|
@@ -107,6 +117,30 @@
|
|
|
107
117
|
"then": { "type": "object", "properties": { "reason": { "type": "null" } } },
|
|
108
118
|
"else": { "type": "object", "properties": { "reason": { "type": "object" } } }
|
|
109
119
|
},
|
|
120
|
+
"recordKind": {
|
|
121
|
+
"description": "A record kind the declaration names (#2680). chant workspace records and graph --intent read every one of them when --kind is not given.",
|
|
122
|
+
"type": "object",
|
|
123
|
+
"required": ["name", "path", "kind", "reason"],
|
|
124
|
+
"properties": {
|
|
125
|
+
"name": { "type": ["string", "null"], "description": "The name the declaration gives the kind, else the kind file's own recordKind.name, else null when the file can't be loaded." },
|
|
126
|
+
"path": { "type": "string", "description": "The kind file, relative to the workspace root, with / separators." },
|
|
127
|
+
"kind": { "type": ["string", "null"], "description": "The kind file's recordKind.name, or null when it can't be loaded." },
|
|
128
|
+
"reason": {
|
|
129
|
+
"description": "Why the kind file can't be loaded, or null. The file is looked for in the tree read, the revision under --at, and loaded from the working tree.",
|
|
130
|
+
"oneOf": [
|
|
131
|
+
{ "type": "null" },
|
|
132
|
+
{
|
|
133
|
+
"type": "object",
|
|
134
|
+
"required": ["code", "message"],
|
|
135
|
+
"properties": {
|
|
136
|
+
"code": { "enum": ["kind-unreadable", "kind-invalid", "schema-unreadable", "schema-id-mismatch"] },
|
|
137
|
+
"message": { "type": "string" }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
110
144
|
"group": {
|
|
111
145
|
"type": "object",
|
|
112
146
|
"required": ["name", "kind", "glob", "matches", "skipped", "reason"],
|
package/src/workspace/ls.ts
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* The `--json` output is part of the read contract, versioned like
|
|
10
10
|
* `workspace records` and described by `ls.schema.json` beside this file.
|
|
11
|
+
*
|
|
12
|
+
* The record kinds the declaration names are listed too, the workspace's own
|
|
13
|
+
* and each member's, with each kind file's own name when it loads (#2680).
|
|
14
|
+
* Loading one imports it, so {@link listWorkspace} lists them unloaded and
|
|
15
|
+
* {@link listWorkspaceWithKinds}, which the command runs, loads them.
|
|
11
16
|
*/
|
|
12
17
|
|
|
13
18
|
import { existsSync } from "node:fs";
|
|
@@ -28,6 +33,7 @@ import type { ReasonCode } from "./reason-codes";
|
|
|
28
33
|
import { loadKindRegistry, probeKind, type KindRegistry } from "./kinds";
|
|
29
34
|
import type { WorkspaceTree } from "./tree";
|
|
30
35
|
import { handToRootChant, locateWorkspace } from "./which-chant";
|
|
36
|
+
import type { DeclaredKindReasonCode } from "./declared-kinds";
|
|
31
37
|
|
|
32
38
|
/** The version of the `ls` output this chant writes. */
|
|
33
39
|
export const LS_CONTRACT_VERSION = 1;
|
|
@@ -58,6 +64,18 @@ export const GROUP_REASON_CODES = [
|
|
|
58
64
|
] as const satisfies readonly ReasonCode[];
|
|
59
65
|
export type GroupReasonCode = (typeof GROUP_REASON_CODES)[number];
|
|
60
66
|
|
|
67
|
+
/** A record kind the declaration names (#2680). */
|
|
68
|
+
export interface LsRecordKind {
|
|
69
|
+
/** The name the declaration gives it, else the kind file's own, else null when the file wasn't loaded. */
|
|
70
|
+
name: string | null;
|
|
71
|
+
/** The kind file from the workspace root. */
|
|
72
|
+
path: string;
|
|
73
|
+
/** The kind file's `recordKind.name`, or null when it wasn't loaded. */
|
|
74
|
+
kind: string | null;
|
|
75
|
+
/** Why the kind file can't be loaded, or null. */
|
|
76
|
+
reason: { code: DeclaredKindReasonCode; message: string } | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
export interface LsMember {
|
|
62
80
|
name: string;
|
|
63
81
|
dir: string;
|
|
@@ -67,6 +85,8 @@ export interface LsMember {
|
|
|
67
85
|
because: string | null;
|
|
68
86
|
readable: boolean;
|
|
69
87
|
reason: { code: MemberReasonCode; message: string } | null;
|
|
88
|
+
/** The record kinds the member declares, in file order (#2680). */
|
|
89
|
+
records: LsRecordKind[];
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
export interface LsGroup {
|
|
@@ -92,6 +112,8 @@ export type LsDocument =
|
|
|
92
112
|
schema: number;
|
|
93
113
|
minReader: string | null;
|
|
94
114
|
pins: Declaration["pins"];
|
|
115
|
+
/** The workspace's own record kinds, from the top-level `records` (#2680). */
|
|
116
|
+
records: LsRecordKind[];
|
|
95
117
|
};
|
|
96
118
|
members: LsMember[];
|
|
97
119
|
groups: LsGroup[];
|
|
@@ -126,8 +148,36 @@ export function memberReason(member: Member, tree: WorkspaceTree, kinds: KindReg
|
|
|
126
148
|
return null;
|
|
127
149
|
}
|
|
128
150
|
|
|
129
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* Find the workspace, read it and build the document `--json` prints, with
|
|
153
|
+
* the declared record kinds unloaded: `kind` null and no reason. Never throws
|
|
154
|
+
* a {@link WorkspaceReadError}.
|
|
155
|
+
*/
|
|
130
156
|
export function listWorkspace(query: LsQuery): LsDocument {
|
|
157
|
+
return readListing(query).doc;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** {@link listWorkspace}, with each declared record kind loaded for its name, or the reason it can't be (#2680). */
|
|
161
|
+
export async function listWorkspaceWithKinds(query: LsQuery): Promise<LsDocument> {
|
|
162
|
+
const { doc, declaration, tree, rootOnDisk } = readListing(query);
|
|
163
|
+
if (!declaration || "error" in doc) return doc;
|
|
164
|
+
const { loadDeclaredKinds } = await import("./declared-kinds");
|
|
165
|
+
const loaded = await loadDeclaredKinds(declaration, tree!, rootOnDisk!);
|
|
166
|
+
const byPath = new Map(loaded.map((k) => [k.declared.path, k]));
|
|
167
|
+
const fill = (r: LsRecordKind): LsRecordKind => {
|
|
168
|
+
const k = byPath.get(r.path)!;
|
|
169
|
+
return { ...r, name: r.name ?? k.kind, kind: k.kind, reason: k.reason };
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
...doc,
|
|
173
|
+
workspace: { ...doc.workspace, records: doc.workspace.records.map(fill) },
|
|
174
|
+
members: doc.members.map((m) => ({ ...m, records: m.records.map(fill) })),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const unloaded = (records: Declaration["records"]): LsRecordKind[] => records.map((r) => ({ name: r.name, path: r.path, kind: null, reason: null }));
|
|
179
|
+
|
|
180
|
+
function readListing(query: LsQuery): { doc: LsDocument; declaration?: Declaration; tree?: WorkspaceTree; rootOnDisk?: string } {
|
|
131
181
|
const chant = readerVersion();
|
|
132
182
|
const head = { $schema: LS_OUTPUT_SCHEMA_ID, contract: LS_CONTRACT_VERSION, chant };
|
|
133
183
|
try {
|
|
@@ -149,6 +199,7 @@ export function listWorkspace(query: LsQuery): LsDocument {
|
|
|
149
199
|
because: m.because,
|
|
150
200
|
readable: reason === null,
|
|
151
201
|
reason,
|
|
202
|
+
records: unloaded(m.records),
|
|
152
203
|
};
|
|
153
204
|
});
|
|
154
205
|
const lsGroups: LsGroup[] = groups.map((g) => ({
|
|
@@ -162,7 +213,7 @@ export function listWorkspace(query: LsQuery): LsDocument {
|
|
|
162
213
|
? { code: "no-matches", message: `no directory ${g.group.globs.join(" or ")} matches${tree.label} holds a chant project` }
|
|
163
214
|
: null,
|
|
164
215
|
}));
|
|
165
|
-
|
|
216
|
+
const doc: LsDocument = {
|
|
166
217
|
...head,
|
|
167
218
|
at,
|
|
168
219
|
workspace: {
|
|
@@ -172,6 +223,7 @@ export function listWorkspace(query: LsQuery): LsDocument {
|
|
|
172
223
|
schema: declaration.schema,
|
|
173
224
|
minReader: declaration.minReader,
|
|
174
225
|
pins: declaration.pins,
|
|
226
|
+
records: unloaded(declaration.records),
|
|
175
227
|
},
|
|
176
228
|
members,
|
|
177
229
|
groups: lsGroups,
|
|
@@ -182,9 +234,10 @@ export function listWorkspace(query: LsQuery): LsDocument {
|
|
|
182
234
|
matches: lsGroups.reduce((n, g) => n + g.matches.length, 0),
|
|
183
235
|
},
|
|
184
236
|
};
|
|
237
|
+
return { doc, declaration, tree, rootOnDisk };
|
|
185
238
|
} catch (err) {
|
|
186
239
|
if (!(err instanceof WorkspaceReadError)) throw err;
|
|
187
|
-
return { ...head, error: { code: err.code, message: err.message, location: err.location ?? null } };
|
|
240
|
+
return { doc: { ...head, error: { code: err.code, message: err.message, location: err.location ?? null } } };
|
|
188
241
|
}
|
|
189
242
|
}
|
|
190
243
|
|
|
@@ -198,7 +251,7 @@ export async function runWorkspaceLs(ctx: CommandContext): Promise<number> {
|
|
|
198
251
|
// The root's chant reads the declaration (ws-021).
|
|
199
252
|
const handed = await handToRootChant(cwd, args.at);
|
|
200
253
|
if (handed !== undefined) return handed;
|
|
201
|
-
const doc =
|
|
254
|
+
const doc = await listWorkspaceWithKinds({ cwd, at: args.at });
|
|
202
255
|
if (args.json) {
|
|
203
256
|
console.log(JSON.stringify(doc, null, 2));
|
|
204
257
|
} else if ("error" in doc) {
|
|
@@ -233,6 +286,18 @@ function formatLs(doc: Extract<LsDocument, { members: unknown }>): string {
|
|
|
233
286
|
if (m.reason) lines.push(` ${m.reason.code}: ${m.reason.message}`);
|
|
234
287
|
});
|
|
235
288
|
}
|
|
289
|
+
const kinds = [...w.records.map((r) => ({ owner: "(workspace)", r })), ...doc.members.flatMap((m) => m.records.map((r) => ({ owner: m.name, r })))];
|
|
290
|
+
if (kinds.length > 0) {
|
|
291
|
+
lines.push("");
|
|
292
|
+
const rows = [["RECORDS", "KIND", "MEMBER", "FILE"]];
|
|
293
|
+
for (const { owner, r } of kinds) rows.push([r.name ?? "-", r.kind ?? "-", owner, r.path]);
|
|
294
|
+
const rendered = table(rows);
|
|
295
|
+
lines.push(rendered[0]);
|
|
296
|
+
kinds.forEach(({ r }, i) => {
|
|
297
|
+
lines.push(rendered[i + 1]);
|
|
298
|
+
if (r.reason) lines.push(` ${r.reason.code}: ${r.reason.message}`);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
236
301
|
if (doc.groups.length > 0) {
|
|
237
302
|
lines.push("");
|
|
238
303
|
const rows = [["GROUP", "KIND", "GLOB", "PROJECTS"]];
|
|
@@ -333,9 +333,12 @@ export function parseMemberRunOutput(stdout: string): { chant: string; results:
|
|
|
333
333
|
return header ? { chant: header.chant, results, stray: stray.join("\n") } : undefined;
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
|
|
336
|
+
/** A command line to run in each member instead of the verb's own, such as `graph --components` (#2662). */
|
|
337
|
+
export type MemberArgv = (unit: RunUnit) => string[];
|
|
338
|
+
|
|
339
|
+
async function runGroup(verb: WorkspaceVerb, group: ToolchainGroup, root: string, args: ParsedArgs, argvFor?: MemberArgv): Promise<UnitResult[]> {
|
|
337
340
|
const { toolchain, units } = group;
|
|
338
|
-
const argvs = new Map(units.map((u) => [u.id, memberArgv(verb, u, args)]));
|
|
341
|
+
const argvs = new Map(units.map((u) => [u.id, argvFor ? argvFor(u) : memberArgv(verb, u, args)]));
|
|
339
342
|
for (const u of units) {
|
|
340
343
|
const o = argvs.get(u.id)!;
|
|
341
344
|
const i = o.indexOf("--output");
|
|
@@ -365,9 +368,12 @@ async function runGroup(verb: WorkspaceVerb, group: ToolchainGroup, root: string
|
|
|
365
368
|
return out;
|
|
366
369
|
}
|
|
367
370
|
|
|
368
|
-
/**
|
|
369
|
-
|
|
370
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Run every group of the plan, one process per toolchain at a time each, and
|
|
373
|
+
* return the results in plan order. `argvFor` replaces the verb's command line.
|
|
374
|
+
*/
|
|
375
|
+
export async function executePlan(plan: MemberPlan, args: ParsedArgs, argvFor?: MemberArgv): Promise<UnitResult[]> {
|
|
376
|
+
const perGroup = await Promise.all(plan.groups.map((g) => runGroup(plan.verb, g, plan.workspace.root, args, argvFor)));
|
|
371
377
|
const byId = new Map(perGroup.flat().map((r) => [r.id, r]));
|
|
372
378
|
const order = plan.groups.flatMap((g) => g.units);
|
|
373
379
|
return order.map((u) => byId.get(u.id)!);
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* `chant` member (`delivery`), three `other` members and decision records.
|
|
7
7
|
* Each read-contract command reads it here, and its output must validate
|
|
8
8
|
* against the command's schema: `ls`, `graph` (running delivery's real
|
|
9
|
-
* `chant graph`), `check`, `status
|
|
10
|
-
* for `ls`, `graph` and `check`,
|
|
9
|
+
* `chant graph`), `graph --composites`, `check`, `status`, `records` and
|
|
10
|
+
* `records --since`, in the working tree and, for `ls`, `graph` and `check`,
|
|
11
|
+
* at `HEAD` through `--at`.
|
|
11
12
|
*
|
|
12
13
|
* The schemas themselves are checked here too: each is a draft 2020-12
|
|
13
14
|
* document under `https://intentius.io/chant/schemas/workspace/<command>/v1/`,
|
|
@@ -20,6 +21,8 @@ import { pathToFileURL } from "node:url";
|
|
|
20
21
|
import { describe, expect, test } from "vitest";
|
|
21
22
|
import { contract, git, REPO, validSchema } from "./__fixtures__/contract-repo";
|
|
22
23
|
import checkSchema from "./check.schema.json";
|
|
24
|
+
import { workspaceComposites } from "./composites";
|
|
25
|
+
import compositesSchema from "./composites.schema.json";
|
|
23
26
|
import { workspaceGraph } from "./graph-cli";
|
|
24
27
|
import graphSchema from "./graph.schema.json";
|
|
25
28
|
import { intentGraph } from "./intent";
|
|
@@ -31,13 +34,15 @@ import { readerBin, type Toolchain } from "./member-commands";
|
|
|
31
34
|
import { READ_CONTRACT_FLOOR, READ_CONTRACT_VERSION } from "./reason-codes";
|
|
32
35
|
import { queryRecords } from "./records-cli";
|
|
33
36
|
import recordsSchema from "./records.schema.json";
|
|
37
|
+
import { queryRecordsSince } from "./records-since";
|
|
38
|
+
import recordsSinceSchema from "./records-since.schema.json";
|
|
34
39
|
import { workspaceStatus } from "./status";
|
|
35
40
|
import statusSchema from "./status.schema.json";
|
|
36
41
|
|
|
37
42
|
const FIXTURE = join(REPO, "reference-workspace");
|
|
38
43
|
const TIMEOUT = 240_000;
|
|
39
44
|
|
|
40
|
-
const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema, intent: intentSchema };
|
|
45
|
+
const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema, "records-since": recordsSinceSchema, intent: intentSchema, composites: compositesSchema };
|
|
41
46
|
|
|
42
47
|
/** This checkout's chant, started the way the CLI starts it, for members with no toolchain of their own. */
|
|
43
48
|
const reader: Toolchain = {
|
|
@@ -131,6 +136,36 @@ describe("every schema against the reference workspace (#2543)", () => {
|
|
|
131
136
|
}
|
|
132
137
|
});
|
|
133
138
|
|
|
139
|
+
test(
|
|
140
|
+
"graph --composites runs delivery's own component graph, and lists its app with the component that deploys it (#2662)",
|
|
141
|
+
async () => {
|
|
142
|
+
const { expectValid } = contract(compositesSchema);
|
|
143
|
+
for (const at of [undefined, "HEAD"]) {
|
|
144
|
+
const { doc, failed } = await workspaceComposites({ cwd: FIXTURE, at, reader });
|
|
145
|
+
expectValid(doc);
|
|
146
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
147
|
+
expect(failed, JSON.stringify(doc.members)).toBe(false);
|
|
148
|
+
expect(doc.at).toBe(at ? head : null);
|
|
149
|
+
expect(doc.members.map((m) => [m.name, m.status])).toEqual([
|
|
150
|
+
["app", "skipped"],
|
|
151
|
+
["delivery", "read"],
|
|
152
|
+
["design-client", "skipped"],
|
|
153
|
+
["design", "skipped"],
|
|
154
|
+
]);
|
|
155
|
+
// delivery declares the app as a DockerWebService, and its app component names that kind.
|
|
156
|
+
expect(doc.composites.map((c) => [c.id, c.kinds, c.components.map((m) => [m.component, m.by, m.via])])).toEqual([
|
|
157
|
+
["delivery/app", ["DockerWebService"], [["delivery/app", "composites", "member"]]],
|
|
158
|
+
]);
|
|
159
|
+
expect(doc.components.map((c) => [c.id, c.archetype])).toEqual([["delivery/app", "service"]]);
|
|
160
|
+
// #2674: the reference config's lexicons host no component runs, so local is the only runtime, read at HEAD too.
|
|
161
|
+
expect(doc.components[0].runtimes).toEqual([{ name: "local", lexicon: null, default: true, command: "chant run --components app" }]);
|
|
162
|
+
expect(doc.members.find((m) => m.name === "delivery")!.runtimeReasons).toEqual([]);
|
|
163
|
+
expect(doc.reasons).toEqual([]);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
TIMEOUT,
|
|
167
|
+
);
|
|
168
|
+
|
|
134
169
|
test("records, in the working tree and at HEAD", async () => {
|
|
135
170
|
const { expectValid } = contract(recordsSchema);
|
|
136
171
|
for (const at of [undefined, "HEAD"]) {
|
|
@@ -140,5 +175,19 @@ describe("every schema against the reference workspace (#2543)", () => {
|
|
|
140
175
|
expect(doc.summary.invalid).toBe(0);
|
|
141
176
|
expect(doc.records.map((r) => r.id)).toContain("ref-001");
|
|
142
177
|
}
|
|
178
|
+
const sessions = await queryRecords({ kind: "design/sessions/session.kind.mjs", cwd: FIXTURE });
|
|
179
|
+
expectValid(sessions);
|
|
180
|
+
if ("error" in sessions) throw new Error(sessions.error.message);
|
|
181
|
+
expect(sessions.summary.invalid).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("records --since HEAD, for decisions and sessions (#2673)", async () => {
|
|
185
|
+
const { expectValid } = contract(recordsSinceSchema);
|
|
186
|
+
for (const kind of ["decisions/decision.kind.mjs", "design/sessions/session.kind.mjs"]) {
|
|
187
|
+
const doc = await queryRecordsSince({ kind, since: "HEAD", cwd: FIXTURE });
|
|
188
|
+
expectValid(doc);
|
|
189
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
190
|
+
expect(doc.since).toBe(head);
|
|
191
|
+
}
|
|
143
192
|
});
|
|
144
193
|
});
|
|
@@ -10,14 +10,20 @@ import { join } from "node:path";
|
|
|
10
10
|
import { describe, expect, test } from "vitest";
|
|
11
11
|
import { REPO } from "./__fixtures__/contract-repo";
|
|
12
12
|
import { MEMBER_RUN_REASON_CODES } from "./compose-graph";
|
|
13
|
+
import { COMPOSITES_ERROR_CODES, COMPOSITES_REASON_CODES, COMPOSITES_RUNTIME_REASON_CODES } from "./composites";
|
|
13
14
|
import { WORKSPACE_ERROR_CODES } from "./declaration";
|
|
14
15
|
import { GRAPH_ERROR_CODES } from "./graph-cli";
|
|
15
16
|
import { INTENT_ERROR_CODES, INTENT_FINDING_CODES, INTENT_REASON_CODES } from "./intent";
|
|
16
17
|
import { CHECK_CODES, CHECK_ERROR_CODES } from "./lineage-check";
|
|
17
18
|
import { GROUP_REASON_CODES, MEMBER_REASON_CODES } from "./ls";
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import intentSchema from "./intent.schema.json";
|
|
20
|
+
import { isPluginCode, isReasonCode, REASON_CODES, REASONS } from "./reason-codes";
|
|
21
|
+
import { contract } from "./__fixtures__/contract-repo";
|
|
22
|
+
import { READ_ERROR_CODES, RECORD_REASON_CODES, RECORD_WARNING_CODES, REVIEW_REASON_CODES } from "./records";
|
|
23
|
+
import { AMEND_ERROR_CODES, NEW_ERROR_CODES, REVIEW_ERROR_CODES } from "./records-write";
|
|
24
|
+
import { RECORDS_SINCE_ERROR_CODES } from "./records-since";
|
|
25
|
+
import { STATUS_ERROR_CODES, STATUS_GATE_REASON_CODES, STATUS_REASON_CODES } from "./status";
|
|
26
|
+
import { WORK_WARNING_CODES } from "./work";
|
|
21
27
|
|
|
22
28
|
const HERE = import.meta.dirname;
|
|
23
29
|
|
|
@@ -31,12 +37,22 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
31
37
|
CHECK_ERROR_CODES,
|
|
32
38
|
STATUS_REASON_CODES,
|
|
33
39
|
STATUS_ERROR_CODES,
|
|
40
|
+
STATUS_GATE_REASON_CODES,
|
|
34
41
|
RECORD_REASON_CODES,
|
|
35
42
|
RECORD_WARNING_CODES,
|
|
43
|
+
REVIEW_REASON_CODES,
|
|
44
|
+
WORK_WARNING_CODES,
|
|
36
45
|
READ_ERROR_CODES,
|
|
46
|
+
NEW_ERROR_CODES,
|
|
47
|
+
AMEND_ERROR_CODES,
|
|
48
|
+
REVIEW_ERROR_CODES,
|
|
49
|
+
RECORDS_SINCE_ERROR_CODES,
|
|
37
50
|
INTENT_ERROR_CODES,
|
|
38
51
|
INTENT_FINDING_CODES,
|
|
39
52
|
INTENT_REASON_CODES,
|
|
53
|
+
COMPOSITES_ERROR_CODES,
|
|
54
|
+
COMPOSITES_REASON_CODES,
|
|
55
|
+
COMPOSITES_RUNTIME_REASON_CODES,
|
|
40
56
|
};
|
|
41
57
|
|
|
42
58
|
/** Every string in an `enum` under a property named `code`, anywhere in a schema. */
|
|
@@ -98,7 +114,7 @@ describe("the closed list of reason codes", () => {
|
|
|
98
114
|
});
|
|
99
115
|
|
|
100
116
|
test("no source file emits a code outside the list", () => {
|
|
101
|
-
const emitted = /(?:\bcode:\s*|(?:WorkspaceReadError|RecordReadError|StatusError|IntentError)\(\s*)"([a-z0-9-]+)"/g;
|
|
117
|
+
const emitted = /(?:\bcode:\s*|(?:WorkspaceReadError|RecordReadError|RecordWriteError|StatusError|IntentError)\(\s*)"([a-z0-9-]+)"/g;
|
|
102
118
|
let seen = 0;
|
|
103
119
|
for (const file of sourceFiles(HERE)) {
|
|
104
120
|
const text = readFileSync(file, "utf-8");
|
|
@@ -111,6 +127,34 @@ describe("the closed list of reason codes", () => {
|
|
|
111
127
|
expect(seen).toBeGreaterThan(30);
|
|
112
128
|
});
|
|
113
129
|
|
|
130
|
+
test("a plugin's finding codes are in its own namespace, outside the list, and the intent schema accepts them (#2656)", () => {
|
|
131
|
+
expect(isPluginCode("plugin:chud:contract-criteria-changed")).toBe(true);
|
|
132
|
+
expect(isPluginCode("plugin:chud:contract-criteria-changed", "chud")).toBe(true);
|
|
133
|
+
expect(isPluginCode("plugin:chud:contract-criteria-changed", "units")).toBe(false);
|
|
134
|
+
for (const bad of ["plugin:chud", "plugin::x", "plugin:chud:Upper", "plugin:chud:a:b", "intent-commit-bare", 7]) expect(isPluginCode(bad), String(bad)).toBe(false);
|
|
135
|
+
expect(isReasonCode("plugin:chud:contract-criteria-changed")).toBe(false);
|
|
136
|
+
const { validate } = contract(intentSchema);
|
|
137
|
+
const finding = (code: string) => ({ id: `finding:${code}:1`, kind: "finding", code, message: "m", concerns: [] });
|
|
138
|
+
const doc = (code: string) => ({
|
|
139
|
+
$schema: intentSchema.$id,
|
|
140
|
+
contract: 1,
|
|
141
|
+
chant: "0.0.0",
|
|
142
|
+
at: null,
|
|
143
|
+
workspace: { name: "w", root: "." },
|
|
144
|
+
region: "region:.",
|
|
145
|
+
history: { rev: null, follows: "directory", shallow: false },
|
|
146
|
+
kinds: [],
|
|
147
|
+
nodes: [finding(code)],
|
|
148
|
+
edges: [],
|
|
149
|
+
reasons: [],
|
|
150
|
+
summary: { commits: 0, decisions: 0, artifacts: 0, findings: 1 },
|
|
151
|
+
});
|
|
152
|
+
expect(validate(doc("plugin:chud:contract-criteria-changed"))).toBe(true);
|
|
153
|
+
expect(validate(doc("intent-commit-bare"))).toBe(true);
|
|
154
|
+
expect(validate(doc("plugin:chud:Nope"))).toBe(false);
|
|
155
|
+
expect(validate(doc("made-up-code"))).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
114
158
|
test("the read-contract page documents every code", () => {
|
|
115
159
|
const page = readFileSync(join(REPO, "docs", "src", "content", "docs", "reference", "workspace-read-contract.mdx"), "utf-8");
|
|
116
160
|
for (const c of REASON_CODES) expect(page, c).toContain(`| \`${c}\` |`);
|
|
@@ -53,17 +53,40 @@ export const REASONS = {
|
|
|
53
53
|
// A ledger status can't fully read.
|
|
54
54
|
"ledger-unreadable": "Reading the ledger failed, so nothing from it is listed.",
|
|
55
55
|
"ledger-malformed": "Some lines of the ledger aren't release records; the rest are listed.",
|
|
56
|
+
// A member's gates status can't list (status, #2674).
|
|
57
|
+
"gates-no-ledger": "The checkout has no chant/lifecycle branch, so there is no gate ledger to read.",
|
|
58
|
+
"gates-no-gate-ledger": "The branch has no gate ledger for the member: no run of it has reached a gate.",
|
|
59
|
+
"gates-ledger-unreadable": "Reading the member's gate ledger failed, so no gate is listed.",
|
|
56
60
|
// A record that isn't valid (records).
|
|
57
|
-
"record-unparseable": "No front matter, a YAML error
|
|
58
|
-
"record-schema-invalid": "The front matter does not match the kind's schema.",
|
|
61
|
+
"record-unparseable": "No front matter, a YAML error or a value outside the JSON subset of YAML, or for a JSON kind a file that is not one object or repeats a member name.",
|
|
62
|
+
"record-schema-invalid": "The record's front matter, or its JSON object, does not match the kind's schema.",
|
|
59
63
|
"record-id-duplicate": "Another record earlier in path order has the same id.",
|
|
60
64
|
"record-supersedes-unknown": "A supersedes link names an id no record has.",
|
|
61
65
|
"record-supersedes-conflict": "A second closed record supersedes a record another one already superseded.",
|
|
66
|
+
// A review session that isn't valid (records, #2673).
|
|
67
|
+
"session-seal-mismatch": "A closed session's seal is not the digest of its text: the session changed after it closed.",
|
|
68
|
+
"session-verdict-unknown-record": "A session's verdict names a record that none of the session kind's subject records has.",
|
|
62
69
|
// A record that is valid but warned about (records, #2549).
|
|
63
70
|
"asset-drift": "A file the record pins by hash has changed: its bytes no longer hash to the pinned sha256.",
|
|
64
71
|
"asset-missing": "A file the record pins by hash does not exist in the tree read.",
|
|
65
72
|
"asset-stale": "A file the record pins is unchanged at the hash a record it supersedes pinned: the decision changed and the artifact did not follow.",
|
|
66
73
|
"record-supersedes-pending": "A supersedes link from a record whose state is weaker than the record it names, so the link has no effect yet.",
|
|
74
|
+
"record-no-evidence": "The record's evidence list is empty: it cites nothing and pins no file. Information for a reviewer, never an error.",
|
|
75
|
+
"review-undigested": "A verdict names no digest of the text it judged. It still counts, and an amendment does not stop it counting.",
|
|
76
|
+
// A work record that is valid but warned about (records and graph --intent, #2683).
|
|
77
|
+
"work-needs-unknown": "A work record's needs list names a work id no record has, so the item stays blocked.",
|
|
78
|
+
"work-implements-unknown": "A work record's implements list names a decision id no decision has.",
|
|
79
|
+
"work-needs-cycle": "A work record needs itself through its needs links, so it can never be ready.",
|
|
80
|
+
"work-implements-undecided": "A work record implements a decision whose state is not approved, such as proposed.",
|
|
81
|
+
"work-done-unpinned": "A work record is done and its evidence list is empty: nothing shows the work was done.",
|
|
82
|
+
"work-closed-without-date": "A work record is done or dropped and has no closing date.",
|
|
83
|
+
"work-done-gap-open": "A work record is done, and the finding it came from still fires on its region. Only graph --intent raises it.",
|
|
84
|
+
// A verdict the quorum does not count (records, #2671, #2672).
|
|
85
|
+
"review-decider": "The verdict is the decider's own, and the quorum counts verdicts besides the decider's.",
|
|
86
|
+
"review-agent": "The reviewer holds the agent role in the trust policy at base.",
|
|
87
|
+
"review-duplicate": "A later verdict by the same principal replaces this one. Names are compared after NFKC, trimming and lower-casing.",
|
|
88
|
+
"review-older-digest": "The verdict names a digest other than the record's text now: the record changed after the verdict.",
|
|
89
|
+
"review-unattested": "An attestation policy is active at base, and the verdict carries no seal.",
|
|
67
90
|
// A records read that fails (records).
|
|
68
91
|
"kind-unreadable": "The record kind file is missing or could not be imported.",
|
|
69
92
|
"kind-invalid": "The record kind file exports no recordKind, or its shape is wrong.",
|
|
@@ -71,6 +94,20 @@ export const REASONS = {
|
|
|
71
94
|
"schema-id-mismatch": "The schema's $id differs from the id the record kind names.",
|
|
72
95
|
"schema-invalid": "The record schema itself does not compile.",
|
|
73
96
|
"location-missing": "The records directory does not exist, in the tree or at the revision.",
|
|
97
|
+
// A records write that is refused (records new, amend and review, #2670). Nothing is written.
|
|
98
|
+
"write-usage-invalid": "The command line lacks a value the write needs, or gives one it does not take.",
|
|
99
|
+
"write-input-invalid": "The fields given with --from or --set can't be read, are not JSON, or are not a JSON object.",
|
|
100
|
+
"record-not-found": "No record of the kind has the id given.",
|
|
101
|
+
"record-id-taken": "The id given for a new record is already used, by a record or a file name.",
|
|
102
|
+
"record-id-unallocatable": "No id was given and none can be allocated: the records share no single prefix and --prefix names none.",
|
|
103
|
+
"record-path-unmatched": "The file name made from the record's id and title does not match the kind's location.",
|
|
104
|
+
"record-closed": "The record is in a closed state, so nothing in it changes; a new record supersedes it instead.",
|
|
105
|
+
"amend-id-immutable": "An amendment changes the record's id, and ids are never renumbered.",
|
|
106
|
+
"amend-supersede-instead": "The record is approved, and the amendment changes a field the approval rule does not let change in place; a new record supersedes it instead.",
|
|
107
|
+
"review-unsupported": "The kind's schema has no reviews field, so its records take no review.",
|
|
108
|
+
"review-note-required": "A dissent was given with no note: a dissent needs a reason.",
|
|
109
|
+
// records --since that fails (#2673).
|
|
110
|
+
"since-rev-unknown": "--since names no commit.",
|
|
74
111
|
// The intent graph (graph --intent, #2651): a read that fails.
|
|
75
112
|
"intent-region-invalid": "The region's path, or its line range, does not exist in the tree read.",
|
|
76
113
|
// The intent graph: part of the walk that can't be read. The document is still printed.
|
|
@@ -89,6 +126,16 @@ export const REASONS = {
|
|
|
89
126
|
"intent-evidence-unpinned": "A decision's evidence has no hash: a URL, or a path with no sha256.",
|
|
90
127
|
"intent-trailer-unverified": "A commit carries a trailer a plugin says claims authorship, and the commit is not attested.",
|
|
91
128
|
"intent-region-unconstrained": "No decision constrains the region at any granularity.",
|
|
129
|
+
"intent-decision-unimplemented": "A decided decision constrains the region, no work item that is not dropped implements it, and no commit falls in its window.",
|
|
130
|
+
"intent-work-blocked": "A work item constraining the region has commits in its window while a work item it needs is not done.",
|
|
131
|
+
"intent-work-open-decided-code": "Commits in the region are a decision's own work while the work item implementing that decision is still open.",
|
|
132
|
+
// Composite instances joined to components (graph --composites, #2662): why the list is empty or has no component.
|
|
133
|
+
"composites-no-chant-member": "No member of kind chant was read, so nothing declares a composite instance or a component.",
|
|
134
|
+
"composites-none-declared": "The members read declare no composite instance.",
|
|
135
|
+
"composites-no-component": "The members read declare no component, so no composite instance has one.",
|
|
136
|
+
// The runtimes a member's components can deploy on (graph --composites, #2674).
|
|
137
|
+
"runtimes-config-unreadable": "The member's chant.config.ts could not be read, so only the built-in local runtime is listed.",
|
|
138
|
+
"runtimes-lexicon-unreadable": "A lexicon the member's config lists could not be loaded, so it is not listed as a runtime.",
|
|
92
139
|
// The lineage lock (check).
|
|
93
140
|
"lock-invalid": "The lineage lock can't be read.",
|
|
94
141
|
"manual-step-open": "A scope in the lineage lock has an open manual step.",
|
|
@@ -102,3 +149,21 @@ export const REASON_CODES = Object.keys(REASONS) as ReasonCode[];
|
|
|
102
149
|
export function isReasonCode(value: unknown): value is ReasonCode {
|
|
103
150
|
return typeof value === "string" && Object.prototype.hasOwnProperty.call(REASONS, value);
|
|
104
151
|
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A finding code a plugin contributes to the intent graph through its
|
|
155
|
+
* `commitJoins` (#2656): `plugin:<name>:<code>`, where `<name>` is the kind's
|
|
156
|
+
* name, or the kind file's `commitJoinsName` when it has one (#2663), and
|
|
157
|
+
* `<code>` is lower case words joined by dashes. These are outside the
|
|
158
|
+
* closed list: the plugin owns its namespace, and core only carries them.
|
|
159
|
+
*/
|
|
160
|
+
export type PluginCode = `plugin:${string}:${string}`;
|
|
161
|
+
|
|
162
|
+
export const PLUGIN_CODE = /^plugin:([^:\s]+):([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
163
|
+
|
|
164
|
+
/** Whether `value` is a plugin code, and, given `name` (a kind's name or its `commitJoinsName`), one in that namespace. */
|
|
165
|
+
export function isPluginCode(value: unknown, name?: string): value is PluginCode {
|
|
166
|
+
if (typeof value !== "string") return false;
|
|
167
|
+
const m = value.match(PLUGIN_CODE);
|
|
168
|
+
return !!m && (name === undefined || m[1] === name);
|
|
169
|
+
}
|
|
@@ -17,6 +17,7 @@ import { runChecks } from "./lineage-check";
|
|
|
17
17
|
import { constraintCovers, isWorkspacePath, memberHolding, WORKSPACE_PATH_PATTERN } from "./record-assets";
|
|
18
18
|
import { pinFile, queryRecords, type RecordsDocument } from "./records-cli";
|
|
19
19
|
import { parseFrontMatter, RECORD_WARNING_CODES } from "./records";
|
|
20
|
+
import { WORK_WARNING_CODES } from "./work";
|
|
20
21
|
import { workspaceGraph } from "./graph-cli";
|
|
21
22
|
import graphSchema from "./graph.schema.json";
|
|
22
23
|
import recordsSchema from "./records.schema.json";
|
|
@@ -168,7 +169,8 @@ describe("chant workspace records checks each pin", () => {
|
|
|
168
169
|
});
|
|
169
170
|
|
|
170
171
|
test("the schema lists exactly the warning codes", () => {
|
|
171
|
-
|
|
172
|
+
// A work kind's records carry the work warnings too, except work-done-gap-open, which only graph --intent raises (#2683).
|
|
173
|
+
expect(recordsSchema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES.filter((c) => c !== "work-done-gap-open")]);
|
|
172
174
|
});
|
|
173
175
|
|
|
174
176
|
test("records pin <path> prints the entry's path from the workspace root and the file's hash", () => {
|