@intentius/chant 0.95.0 → 0.96.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.
Files changed (83) hide show
  1. package/dist/cli/main.d.ts.map +1 -1
  2. package/dist/cli/mcp/server.d.ts +10 -4
  3. package/dist/cli/mcp/server.d.ts.map +1 -1
  4. package/dist/cli/registry.d.ts +9 -1
  5. package/dist/cli/registry.d.ts.map +1 -1
  6. package/dist/lifecycle/run-ledger.d.ts +2 -0
  7. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  8. package/dist/op/activities/shell.d.ts.map +1 -1
  9. package/dist/op/index.d.ts +2 -0
  10. package/dist/op/index.d.ts.map +1 -1
  11. package/dist/op/local-executor.d.ts.map +1 -1
  12. package/dist/op/operator.d.ts.map +1 -1
  13. package/dist/op/run-live.d.ts +127 -0
  14. package/dist/op/run-live.d.ts.map +1 -0
  15. package/dist/op/runtime.d.ts +2 -0
  16. package/dist/op/runtime.d.ts.map +1 -1
  17. package/dist/workspace/checks/diagrams.d.ts +5 -0
  18. package/dist/workspace/checks/diagrams.d.ts.map +1 -1
  19. package/dist/workspace/declaration.d.ts +5 -5
  20. package/dist/workspace/declaration.d.ts.map +1 -1
  21. package/dist/workspace/declaration.schema.json +50 -7
  22. package/dist/workspace/intent-cli.d.ts +6 -0
  23. package/dist/workspace/intent-cli.d.ts.map +1 -1
  24. package/dist/workspace/intent-record.d.ts +145 -0
  25. package/dist/workspace/intent-record.d.ts.map +1 -0
  26. package/dist/workspace/intent.d.ts +46 -1
  27. package/dist/workspace/intent.d.ts.map +1 -1
  28. package/dist/workspace/ls.d.ts +2 -2
  29. package/dist/workspace/ls.d.ts.map +1 -1
  30. package/dist/workspace/patch.d.ts +122 -0
  31. package/dist/workspace/patch.d.ts.map +1 -0
  32. package/dist/workspace/points-cli.d.ts +8 -0
  33. package/dist/workspace/points-cli.d.ts.map +1 -1
  34. package/dist/workspace/reason-codes.d.ts +2 -0
  35. package/dist/workspace/reason-codes.d.ts.map +1 -1
  36. package/dist/workspace/record-decided.d.ts +36 -0
  37. package/dist/workspace/record-decided.d.ts.map +1 -0
  38. package/dist/workspace/records-cli.d.ts +8 -0
  39. package/dist/workspace/records-cli.d.ts.map +1 -1
  40. package/dist/workspace/status-stewards.d.ts +79 -12
  41. package/dist/workspace/status-stewards.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/cli/main.test.ts +16 -0
  44. package/src/cli/main.ts +34 -3
  45. package/src/cli/mcp/server.test.ts +14 -0
  46. package/src/cli/mcp/server.ts +10 -4
  47. package/src/cli/registry.ts +9 -1
  48. package/src/lifecycle/run-ledger.ts +7 -1
  49. package/src/op/activities/shell.ts +4 -1
  50. package/src/op/index.ts +2 -0
  51. package/src/op/local-executor.ts +51 -3
  52. package/src/op/operator.ts +2 -11
  53. package/src/op/run-live.test.ts +111 -0
  54. package/src/op/run-live.ts +305 -0
  55. package/src/op/runtime.ts +2 -0
  56. package/src/op/steward-points.test.ts +81 -0
  57. package/src/workspace/checks/diagrams.test.ts +24 -0
  58. package/src/workspace/checks/diagrams.ts +10 -3
  59. package/src/workspace/declaration.schema.json +50 -7
  60. package/src/workspace/declaration.test.ts +18 -0
  61. package/src/workspace/declaration.ts +7 -7
  62. package/src/workspace/intent-cli.ts +47 -1
  63. package/src/workspace/intent-record.schema.json +529 -0
  64. package/src/workspace/intent-record.test.ts +257 -0
  65. package/src/workspace/intent-record.ts +371 -0
  66. package/src/workspace/intent.schema.json +33 -1
  67. package/src/workspace/intent.ts +29 -12
  68. package/src/workspace/ls-contract.test.ts +14 -0
  69. package/src/workspace/ls.schema.json +3 -3
  70. package/src/workspace/ls.ts +3 -3
  71. package/src/workspace/patch.schema.json +321 -0
  72. package/src/workspace/patch.test.ts +198 -0
  73. package/src/workspace/patch.ts +377 -0
  74. package/src/workspace/points-cli.ts +18 -0
  75. package/src/workspace/read-contract.test.ts +29 -1
  76. package/src/workspace/reason-codes.test.ts +2 -0
  77. package/src/workspace/reason-codes.ts +3 -0
  78. package/src/workspace/record-decided.ts +107 -0
  79. package/src/workspace/records-cli.ts +12 -0
  80. package/src/workspace/records.schema.json +15 -0
  81. package/src/workspace/status-contract.test.ts +76 -2
  82. package/src/workspace/status-stewards.ts +149 -27
  83. package/src/workspace/status.schema.json +69 -6
@@ -542,6 +542,7 @@
542
542
  "provenance",
543
543
  "decided_by",
544
544
  "decided_on",
545
+ "decidedIn",
545
546
  "reviews",
546
547
  "supersededBy",
547
548
  "supersedes",
@@ -651,6 +652,10 @@
651
652
  "null"
652
653
  ]
653
654
  },
655
+ "decidedIn": {
656
+ "$ref": "#/$defs/decidedIn",
657
+ "description": "Its window opens at this commit, or, when null, at the commit that added the record."
658
+ },
654
659
  "reviews": {
655
660
  "type": "object",
656
661
  "required": [
@@ -1254,7 +1259,8 @@
1254
1259
  "schema-id-mismatch",
1255
1260
  "schema-invalid",
1256
1261
  "location-missing",
1257
- "intent-region-invalid"
1262
+ "intent-region-invalid",
1263
+ "intent-record-unknown"
1258
1264
  ]
1259
1265
  },
1260
1266
  "message": {
@@ -1442,6 +1448,32 @@
1442
1448
  "description": "The linked record's state, or null when no record has the id."
1443
1449
  }
1444
1450
  }
1451
+ },
1452
+ "decidedIn": {
1453
+ "description": "Added in contract 1. The commit that last moved the record into an approved state and kept it there: a state the kind's approval ranks above 0, or one of its closedStates when it has no approval. Read from the history of the record's file reachable from the revision read, or HEAD in the working tree. A record added approved was decided by the commit that added it. Null when the record is not approved in that history.",
1454
+ "type": [
1455
+ "object",
1456
+ "null"
1457
+ ],
1458
+ "required": [
1459
+ "sha",
1460
+ "date",
1461
+ "subject"
1462
+ ],
1463
+ "additionalProperties": false,
1464
+ "properties": {
1465
+ "sha": {
1466
+ "type": "string",
1467
+ "pattern": "^[0-9a-f]{40,64}$"
1468
+ },
1469
+ "date": {
1470
+ "type": "string",
1471
+ "description": "The author date, ISO 8601."
1472
+ },
1473
+ "subject": {
1474
+ "type": "string"
1475
+ }
1476
+ }
1445
1477
  }
1446
1478
  }
1447
1479
  }
@@ -44,6 +44,7 @@ import { loadKindRegistry } from "./kinds";
44
44
  import { resolveLinks, type LinkTableRow } from "./links";
45
45
  import { sourceMemberHandles } from "./member-handles";
46
46
  import { constraintCovers, isWorkspacePath, memberHolding } from "./record-assets";
47
+ import type { DecidedIn } from "./record-decided";
47
48
  import { importKindModule, loadRecordKind, parseFrontMatter, RecordReadError, supersedesTargets, type LoadedRecordKind } from "./records";
48
49
  import { queryRecords, type RecordView } from "./records-cli";
49
50
  import { isPluginCode, type PluginCode, type ReasonCode } from "./reason-codes";
@@ -120,6 +121,8 @@ export const INTENT_ERROR_CODES = [
120
121
  "location-missing",
121
122
  /** The region's path, or its line range, does not exist in the tree read. */
122
123
  "intent-region-invalid",
124
+ /** --record names an id no record of a decision kind read has. */
125
+ "intent-record-unknown",
123
126
  ] as const satisfies readonly ReasonCode[];
124
127
  export type IntentErrorCode = (typeof INTENT_ERROR_CODES)[number];
125
128
 
@@ -232,6 +235,13 @@ export interface DecisionNode {
232
235
  provenance: { level: ProvenanceLevel; commit: string | null; reason: string };
233
236
  decided_by: string | null;
234
237
  decided_on: string | null;
238
+ /**
239
+ * The commit that last moved the record into an approved state and kept it
240
+ * there, from the history read (`record-decided.ts`), or null when the
241
+ * record is not approved there. Its window opens at this commit, or at the
242
+ * commit that added the record when this is null.
243
+ */
244
+ decidedIn: DecidedIn | null;
235
245
  reviews: { agree: number; dissent: number; abstain: number; openConcerns: number };
236
246
  supersededBy: string | null;
237
247
  /** The ids of the records this one's supersedes links name. */
@@ -389,7 +399,7 @@ function git(top: string, args: string[], input?: string): string {
389
399
  return execFileSync("git", args, { cwd: top, encoding: "utf-8", input, stdio: [input === undefined ? "ignore" : "pipe", "pipe", "pipe"], maxBuffer: 512 * 1024 * 1024 });
390
400
  }
391
401
 
392
- function tryGit(top: string, args: string[], input?: string): string | undefined {
402
+ export function tryGit(top: string, args: string[], input?: string): string | undefined {
393
403
  try {
394
404
  return git(top, args, input);
395
405
  } catch {
@@ -409,7 +419,7 @@ function hunkRanges(patch: string): LineRange[] {
409
419
  }
410
420
 
411
421
  /** The commits that touched the region, newest first, with the lines each changed for a line range. */
412
- function regionHistory(top: string, rev: string, gitPath: string, type: "file" | "dir", lines: LineRange | null): { sha: string; lines: LineRange[] | null }[] {
422
+ export function regionHistory(top: string, rev: string, gitPath: string, type: "file" | "dir", lines: LineRange | null): { sha: string; lines: LineRange[] | null }[] {
413
423
  if (lines) {
414
424
  const out = tryGit(top, ["log", `-L${lines.start},${lines.end}:${gitPath}`, "--format=%x00%H", "--no-color", rev]);
415
425
  if (out === undefined) return [];
@@ -433,7 +443,7 @@ function regionHistory(top: string, rev: string, gitPath: string, type: "file" |
433
443
  }
434
444
 
435
445
  /** Subject, body, author, date and trailers of each commit. */
436
- function commitDetails(top: string, shas: string[]): Map<string, IntentCommit> {
446
+ export function commitDetails(top: string, shas: string[]): Map<string, IntentCommit> {
437
447
  const out = new Map<string, IntentCommit>();
438
448
  if (shas.length === 0) return out;
439
449
  const text = git(top, ["log", "--no-walk=unsorted", "--stdin", "--format=%x00%H%x1f%an%x1f%ae%x1f%aI%x1f%(trailers:only,unfold,separator=%x1e)%x1f%B"], `${shas.join("\n")}\n`);
@@ -462,7 +472,7 @@ function commitDetails(top: string, shas: string[]): Map<string, IntentCommit> {
462
472
  }
463
473
 
464
474
  /** The commit reachable from `rev` that last added `path` (from the repository root), or null. */
465
- function addingCommit(top: string, rev: string, path: string): string | null {
475
+ export function addingCommit(top: string, rev: string, path: string): string | null {
466
476
  const out = tryGit(top, ["log", "--diff-filter=A", "-1", "--format=%H", rev, "--", path]);
467
477
  return out?.trim() || null;
468
478
  }
@@ -473,7 +483,7 @@ function addingCommit(top: string, rev: string, path: string): string | null {
473
483
  * `rev` (#2683). A record added in a closed state closes in the commit that
474
484
  * added it.
475
485
  */
476
- function closingCommit(top: string, rev: string, path: string, stateField: string, closed: readonly string[]): string | null {
486
+ export function closingCommit(top: string, rev: string, path: string, stateField: string, closed: readonly string[]): string | null {
477
487
  const out = tryGit(top, ["log", "--format=%H", rev, "--", path]);
478
488
  let closing: string | null = null;
479
489
  for (const sha of (out ?? "").split("\n").map((l) => l.trim()).filter(Boolean)) {
@@ -486,8 +496,13 @@ function closingCommit(top: string, rev: string, path: string, stateField: strin
486
496
  return closing;
487
497
  }
488
498
 
499
+ /** The commit a decision record's window opens at: the one that decided it, or, when it is not approved, the one that added it. */
500
+ export function windowOpening(top: string, rev: string, view: Pick<RecordView, "path" | "decidedIn">): string | null {
501
+ return view.decidedIn?.sha ?? addingCommit(top, rev, view.path);
502
+ }
503
+
489
504
  /** `commit` and every commit between it and `rev` that has it as an ancestor. */
490
- function descendants(top: string, commit: string, rev: string): Set<string> {
505
+ export function descendants(top: string, commit: string, rev: string): Set<string> {
491
506
  const out = new Set<string>([commit]);
492
507
  const text = tryGit(top, ["rev-list", "--ancestry-path", `${commit}..${rev}`]);
493
508
  for (const line of (text ?? "").split("\n")) if (line.trim()) out.add(line.trim());
@@ -592,7 +607,7 @@ async function resolveNodeFromGraph(cwd: string, at: string | undefined, member:
592
607
 
593
608
  // ── The walk ─────────────────────────────────────────────────────────────────
594
609
 
595
- interface LoadedKind {
610
+ export interface LoadedKind {
596
611
  file: string;
597
612
  /** Relative to the repository root, for the document. */
598
613
  display: string;
@@ -602,7 +617,7 @@ interface LoadedKind {
602
617
  joins?: CommitJoins;
603
618
  }
604
619
 
605
- async function loadKinds(query: IntentQuery, top: string): Promise<LoadedKind[]> {
620
+ export async function loadKinds(query: IntentQuery, top: string): Promise<LoadedKind[]> {
606
621
  const out: LoadedKind[] = [];
607
622
  for (const k of query.kinds ?? []) {
608
623
  const file = resolve(query.cwd, k);
@@ -822,6 +837,7 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
822
837
  provenance: { level: v.provenance.level, commit: v.provenance.commit, reason: v.provenance.reason },
823
838
  decided_by: stringOr(v.data?.decided_by),
824
839
  decided_on: stringOr(v.data?.decided_on),
840
+ decidedIn: v.decidedIn ?? null,
825
841
  reviews: reviewSummary(v.data),
826
842
  supersededBy: v.supersededBy,
827
843
  supersedes,
@@ -988,14 +1004,15 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
988
1004
  }
989
1005
 
990
1006
  // Which decisions covered the region at each commit's time: from the commit
991
- // that added the record until the commit that added the record superseding it.
1007
+ // that decided the record, or added it when it is not approved, until the
1008
+ // commit that opened the window of the record superseding it.
992
1009
  const windows = new Map<string, { from: Set<string>; until: Set<string> | null }>();
993
1010
  if (rev) {
994
1011
  for (const c of covering) {
995
- const added = addingCommit(top, rev, c.view.path);
1012
+ const opened = windowOpening(top, rev, c.view);
996
1013
  const successor = c.view.supersededBy ? decisionById.get(`${c.node.recordKind}/${c.view.supersededBy}`) : undefined;
997
- const replaced = successor ? addingCommit(top, rev, successor.view.path) : null;
998
- windows.set(c.node.id, { from: added ? descendants(top, added, rev) : new Set(), until: replaced ? descendants(top, replaced, rev) : null });
1014
+ const replaced = successor ? windowOpening(top, rev, successor.view) : null;
1015
+ windows.set(c.node.id, { from: opened ? descendants(top, opened, rev) : new Set(), until: replaced ? descendants(top, replaced, rev) : null });
999
1016
  }
1000
1017
  }
1001
1018
  // Work items (#2683): the records of each work kind whose constrains cover
@@ -166,6 +166,12 @@ describe("chant workspace ls on built workspaces", () => {
166
166
  render: "docs/diagrams/boundary.svg",
167
167
  renderer: { tool: "graphviz", version: "9.0.0", args: [] },
168
168
  },
169
+ {
170
+ name: "flow",
171
+ title: "Request flow",
172
+ source: "docs/diagrams/flow.mmd",
173
+ renderer: { tool: "mermaid", version: "11.4.1" },
174
+ },
169
175
  ],
170
176
  }),
171
177
  "docs/README.md": "",
@@ -181,6 +187,14 @@ describe("chant workspace ls on built workspaces", () => {
181
187
  renderer: { tool: "graphviz", version: "9.0.0", args: [] },
182
188
  member: null,
183
189
  },
190
+ {
191
+ name: "flow",
192
+ title: "Request flow",
193
+ source: "docs/diagrams/flow.mmd",
194
+ render: null,
195
+ renderer: { tool: "mermaid", version: "11.4.1", args: [] },
196
+ member: null,
197
+ },
184
198
  {
185
199
  name: "architecture",
186
200
  title: "Studio architecture",
@@ -173,13 +173,13 @@
173
173
  "name": { "type": "string" },
174
174
  "title": { "type": "string" },
175
175
  "source": { "type": ["string", "null"], "description": "From the workspace root, with / separators. Null for an SVG with no source." },
176
- "render": { "type": "string", "description": "From the workspace root, with / separators." },
176
+ "render": { "type": ["string", "null"], "description": "From the workspace root, with / separators. Null for a mermaid or excalidraw diagram with no committed render: a reader draws it from its source." },
177
177
  "renderer": {
178
178
  "type": "object",
179
179
  "required": ["tool", "version", "args"],
180
180
  "properties": {
181
- "tool": { "enum": ["d2", "mermaid", "graphviz"] },
182
- "version": { "type": "string", "description": "The exact release the render was made with, such as 0.9.0." },
181
+ "tool": { "enum": ["d2", "mermaid", "graphviz", "excalidraw"] },
182
+ "version": { "type": "string", "description": "The exact release the render was made with, such as 0.9.0. For mermaid or excalidraw, the release of the library a reader draws the source with." },
183
183
  "args": { "type": "array", "items": { "type": "string" }, "description": "Passed before the input and output paths, in order." }
184
184
  }
185
185
  },
@@ -122,8 +122,8 @@ export interface LsDiagram {
122
122
  title: string;
123
123
  /** From the workspace root, with / separators. Null for an SVG with no source. */
124
124
  source: string | null;
125
- /** From the workspace root, with / separators. */
126
- render: string;
125
+ /** From the workspace root, with / separators. Null for a mermaid or excalidraw diagram with no committed render: a reader draws it from its source. */
126
+ render: string | null;
127
127
  renderer: DiagramDeclaration["renderer"];
128
128
  /** The member that declares it, or null for the workspace's own. */
129
129
  member: string | null;
@@ -354,7 +354,7 @@ function formatLs(doc: Extract<LsDocument, { members: unknown }>): string {
354
354
  if (doc.diagrams.length > 0) {
355
355
  lines.push("");
356
356
  const rows = [["DIAGRAM", "MEMBER", "SOURCE", "RENDER", "RENDERER"]];
357
- for (const d of doc.diagrams) rows.push([d.name, d.member ?? "(workspace)", d.source ?? "-", d.render, `${d.renderer.tool} ${d.renderer.version}`]);
357
+ for (const d of doc.diagrams) rows.push([d.name, d.member ?? "(workspace)", d.source ?? "-", d.render ?? "-", `${d.renderer.tool} ${d.renderer.version}`]);
358
358
  lines.push(...table(rows));
359
359
  }
360
360
  if (doc.groups.length > 0) {
@@ -0,0 +1,321 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://intentius.io/chant/schemas/workspace/patch/v1/patch.schema.json",
4
+ "title": "chant workspace patch output",
5
+ "description": "What `chant workspace patch <range> --json` prints: each file a diff changes under the workspace root, with its line counts and hunks, cut to a size limit. The range is <base>..<head>, <base>...<head> (from the merge base), or one commit against its first parent, or against the empty tree for a root commit. Contract version 1 is written by chant 0.81.0 and newer, and this document was added to it by workspace patch; a chant without the command refuses it. Every code is in the one closed list of `reason-codes.ts`.",
6
+ "oneOf": [
7
+ {
8
+ "$ref": "#/$defs/result"
9
+ },
10
+ {
11
+ "$ref": "#/$defs/failure"
12
+ }
13
+ ],
14
+ "$defs": {
15
+ "result": {
16
+ "description": "The diff was read. Exit code 0.",
17
+ "type": "object",
18
+ "required": [
19
+ "$schema",
20
+ "contract",
21
+ "chant",
22
+ "workspace",
23
+ "range",
24
+ "paths",
25
+ "limits",
26
+ "files",
27
+ "summary"
28
+ ],
29
+ "properties": {
30
+ "$schema": {
31
+ "const": "https://intentius.io/chant/schemas/workspace/patch/v1/patch.schema.json"
32
+ },
33
+ "contract": {
34
+ "const": 1
35
+ },
36
+ "chant": {
37
+ "type": "string",
38
+ "description": "The chant that wrote the document."
39
+ },
40
+ "workspace": {
41
+ "type": "object",
42
+ "required": [
43
+ "name",
44
+ "root"
45
+ ],
46
+ "properties": {
47
+ "name": {
48
+ "type": "string"
49
+ },
50
+ "root": {
51
+ "type": "string",
52
+ "description": "The workspace root relative to the git root, \".\" for the git root."
53
+ }
54
+ }
55
+ },
56
+ "range": {
57
+ "type": "object",
58
+ "required": [
59
+ "spec",
60
+ "form",
61
+ "base",
62
+ "head"
63
+ ],
64
+ "properties": {
65
+ "spec": {
66
+ "type": "string",
67
+ "description": "The range as given."
68
+ },
69
+ "form": {
70
+ "enum": [
71
+ "range",
72
+ "merge-base",
73
+ "commit",
74
+ "worktree"
75
+ ],
76
+ "description": "range: <base>..<head>. merge-base: <base>...<head>, so base is their merge base. commit: one commit, so base is its first parent, or the empty tree. worktree: --worktree, the working tree against one commit (HEAD unless given), untracked files that git does not ignore as added."
77
+ },
78
+ "base": {
79
+ "type": "string",
80
+ "pattern": "^[0-9a-f]{40,64}$",
81
+ "description": "The commit, or for a root commit the empty tree, the diff starts from."
82
+ },
83
+ "head": {
84
+ "type": [
85
+ "string",
86
+ "null"
87
+ ],
88
+ "pattern": "^[0-9a-f]{40,64}$",
89
+ "description": "The commit the diff ends at; null for the working tree (worktree)."
90
+ }
91
+ }
92
+ },
93
+ "paths": {
94
+ "type": "array",
95
+ "items": {
96
+ "type": "string"
97
+ },
98
+ "description": "The --path filters, from the workspace root. Empty for the whole workspace."
99
+ },
100
+ "limits": {
101
+ "type": "object",
102
+ "required": [
103
+ "fileBytes",
104
+ "totalBytes"
105
+ ],
106
+ "properties": {
107
+ "fileBytes": {
108
+ "type": "integer",
109
+ "minimum": 1,
110
+ "description": "The hunk text, headers included, one file gets: --max-bytes, or 65536."
111
+ },
112
+ "totalBytes": {
113
+ "type": "integer",
114
+ "minimum": 1,
115
+ "description": "The hunk text every file gets together: 16 times fileBytes."
116
+ }
117
+ }
118
+ },
119
+ "files": {
120
+ "type": "array",
121
+ "items": {
122
+ "$ref": "#/$defs/file"
123
+ },
124
+ "description": "In the order git lists them."
125
+ },
126
+ "summary": {
127
+ "type": "object",
128
+ "required": [
129
+ "files",
130
+ "additions",
131
+ "deletions",
132
+ "truncated"
133
+ ],
134
+ "properties": {
135
+ "files": {
136
+ "type": "integer",
137
+ "minimum": 0
138
+ },
139
+ "additions": {
140
+ "type": "integer",
141
+ "minimum": 0
142
+ },
143
+ "deletions": {
144
+ "type": "integer",
145
+ "minimum": 0
146
+ },
147
+ "truncated": {
148
+ "type": "boolean",
149
+ "description": "Whether any file is truncated."
150
+ }
151
+ }
152
+ },
153
+ "error": false
154
+ }
155
+ },
156
+ "file": {
157
+ "type": "object",
158
+ "required": [
159
+ "path",
160
+ "change",
161
+ "from",
162
+ "binary",
163
+ "additions",
164
+ "deletions",
165
+ "hunkCount",
166
+ "bytes",
167
+ "hunks",
168
+ "truncated"
169
+ ],
170
+ "properties": {
171
+ "path": {
172
+ "type": "string",
173
+ "description": "From the workspace root."
174
+ },
175
+ "change": {
176
+ "enum": [
177
+ "added",
178
+ "modified",
179
+ "deleted",
180
+ "renamed",
181
+ "copied"
182
+ ]
183
+ },
184
+ "from": {
185
+ "type": [
186
+ "string",
187
+ "null"
188
+ ],
189
+ "description": "For a rename or a copy, the path it came from, from the workspace root."
190
+ },
191
+ "binary": {
192
+ "type": "boolean"
193
+ },
194
+ "additions": {
195
+ "type": [
196
+ "integer",
197
+ "null"
198
+ ],
199
+ "minimum": 0,
200
+ "description": "Lines added, as git counts them; null for a binary file."
201
+ },
202
+ "deletions": {
203
+ "type": [
204
+ "integer",
205
+ "null"
206
+ ],
207
+ "minimum": 0,
208
+ "description": "Lines deleted; null for a binary file."
209
+ },
210
+ "hunkCount": {
211
+ "type": "integer",
212
+ "minimum": 0,
213
+ "description": "How many hunks git wrote for the file. hunks holds fewer when truncated."
214
+ },
215
+ "bytes": {
216
+ "type": "integer",
217
+ "minimum": 0,
218
+ "description": "The bytes of hunk text git wrote for the file, headers included, before any cut."
219
+ },
220
+ "hunks": {
221
+ "type": "array",
222
+ "items": {
223
+ "$ref": "#/$defs/hunk"
224
+ }
225
+ },
226
+ "truncated": {
227
+ "type": "boolean",
228
+ "description": "Whether hunks holds less than git wrote: the file's text passed fileBytes, or the total passed totalBytes. The last hunk kept may be cut short."
229
+ }
230
+ }
231
+ },
232
+ "hunk": {
233
+ "type": "object",
234
+ "required": [
235
+ "header",
236
+ "oldStart",
237
+ "oldLines",
238
+ "newStart",
239
+ "newLines",
240
+ "lines"
241
+ ],
242
+ "properties": {
243
+ "header": {
244
+ "type": "string",
245
+ "description": "The @@ line as git writes it."
246
+ },
247
+ "oldStart": {
248
+ "type": "integer",
249
+ "minimum": 0
250
+ },
251
+ "oldLines": {
252
+ "type": "integer",
253
+ "minimum": 0
254
+ },
255
+ "newStart": {
256
+ "type": "integer",
257
+ "minimum": 0
258
+ },
259
+ "newLines": {
260
+ "type": "integer",
261
+ "minimum": 0
262
+ },
263
+ "lines": {
264
+ "type": "array",
265
+ "items": {
266
+ "type": "string"
267
+ },
268
+ "description": "Each line with its leading space, +, - or backslash, without its newline."
269
+ }
270
+ }
271
+ },
272
+ "failure": {
273
+ "description": "The declaration could not be read, a revision is not a commit, or a --path is not inside the workspace, and nothing was read. Exit code 1.",
274
+ "type": "object",
275
+ "required": [
276
+ "$schema",
277
+ "contract",
278
+ "chant",
279
+ "error"
280
+ ],
281
+ "properties": {
282
+ "$schema": {
283
+ "const": "https://intentius.io/chant/schemas/workspace/patch/v1/patch.schema.json"
284
+ },
285
+ "contract": {
286
+ "const": 1
287
+ },
288
+ "chant": {
289
+ "type": "string"
290
+ },
291
+ "error": {
292
+ "type": "object",
293
+ "required": [
294
+ "code",
295
+ "message"
296
+ ],
297
+ "properties": {
298
+ "code": {
299
+ "enum": [
300
+ "declaration-missing",
301
+ "declaration-ambiguous",
302
+ "declaration-unparseable",
303
+ "declaration-invalid",
304
+ "placement-invalid",
305
+ "reader-too-old",
306
+ "root-chant-required",
307
+ "not-a-git-repository",
308
+ "revision-unknown",
309
+ "patch-path-invalid"
310
+ ]
311
+ },
312
+ "message": {
313
+ "type": "string"
314
+ }
315
+ }
316
+ },
317
+ "files": false
318
+ }
319
+ }
320
+ }
321
+ }