@markjaquith/agency 2.48.1 → 2.50.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 (44) hide show
  1. package/README.md +40 -9
  2. package/cli.ts +32 -0
  3. package/package.json +1 -1
  4. package/schemas/agency-graph-v1.schema.json +141 -38
  5. package/src/cli-parser.test.ts +120 -0
  6. package/src/cli-parser.ts +122 -7
  7. package/src/cli.test.ts +71 -0
  8. package/src/commands/claim.ts +26 -2
  9. package/src/commands/phase.ts +23 -2
  10. package/src/commands/review.ts +38 -0
  11. package/src/commands/task.ts +49 -7
  12. package/src/commands/work.test.ts +2 -0
  13. package/src/commands/work.ts +2 -2
  14. package/src/graph-schema.ts +35 -13
  15. package/src/protocol.ts +1 -0
  16. package/src/services/ArchiveService.test.ts +2 -2
  17. package/src/services/ArchiveService.ts +1 -0
  18. package/src/services/ClaimService.test.ts +59 -0
  19. package/src/services/ClaimService.ts +36 -2
  20. package/src/services/ContextService.ts +56 -4
  21. package/src/services/DoctorService.ts +45 -1
  22. package/src/services/GraphMutationService.ts +21 -0
  23. package/src/services/GraphService.ts +118 -54
  24. package/src/services/IntegrationService.test.ts +4 -1
  25. package/src/services/PhaseService.ts +55 -3
  26. package/src/services/PullRequestService.test.ts +22 -2
  27. package/src/services/PullRequestService.ts +44 -3
  28. package/src/services/ReadinessService.ts +7 -3
  29. package/src/services/ReviewService.test.ts +472 -0
  30. package/src/services/ReviewService.ts +404 -0
  31. package/src/services/SyncService.test.ts +68 -2
  32. package/src/services/SyncService.ts +123 -6
  33. package/src/services/TaskPhaseService.test.ts +182 -0
  34. package/src/services/TaskService.ts +128 -11
  35. package/src/services/WorkbaseService.test.ts +58 -0
  36. package/src/services/WorkbaseService.ts +22 -1
  37. package/src/services/WorktreeService.test.ts +16 -16
  38. package/src/services/WorktreeService.ts +76 -40
  39. package/src/test-utils.ts +2 -0
  40. package/src/work-view.ts +14 -6
  41. package/src/workbase/AGENTS.md +7 -2
  42. package/src/workbase/completion.ts +28 -0
  43. package/src/workbase/schemas.test.ts +57 -0
  44. package/src/workbase/schemas.ts +65 -0
@@ -43,7 +43,8 @@ interface ExecutionWorkspace {
43
43
  readonly taskPath: string
44
44
  readonly phasePath: string | null
45
45
  readonly codePath: string
46
- readonly writablePath: string
46
+ readonly writablePath: string | null
47
+ readonly reviewPath: string | null
47
48
  readonly repo: string
48
49
  readonly repos: readonly RepositoryReference[]
49
50
  readonly dryRun: boolean
@@ -181,12 +182,14 @@ const inspectExecution = (
181
182
  const root = yield* workbase.discover(startPath)
182
183
  const task = yield* tasks.show(taskId, root)
183
184
 
184
- let execution: {
185
- repo: string
186
- repos?: readonly RepositoryReference[]
187
- branch: string
188
- base: string
189
- }
185
+ let execution:
186
+ | {
187
+ repo: string
188
+ repos?: readonly RepositoryReference[]
189
+ branch: string
190
+ base: string
191
+ }
192
+ | { review: { repo: string; commit: string } }
190
193
  let owner: WorktreeOwner
191
194
  let codePath: string
192
195
  if ("phases" in task.data) {
@@ -230,6 +233,7 @@ const inspectExecution = (
230
233
  ownership.set(key, owners)
231
234
  }
232
235
  } else {
236
+ if (!("repo" in taskRecord.data)) continue
233
237
  const key = `${taskRecord.data.repo}:${taskRecord.data.branch}`
234
238
  const owners = ownership.get(key) ?? []
235
239
  owners.push({
@@ -244,10 +248,13 @@ const inspectExecution = (
244
248
  const declared: readonly (
245
249
  | { readonly repo: string; readonly branch: string }
246
250
  | RepositoryReference
247
- )[] = [
248
- { repo: execution.repo, branch: execution.branch },
249
- ...(execution.repos ?? []),
250
- ]
251
+ )[] =
252
+ "review" in execution
253
+ ? [{ repo: execution.review.repo, ref: execution.review.commit }]
254
+ : [
255
+ { repo: execution.repo, branch: execution.branch },
256
+ ...(execution.repos ?? []),
257
+ ]
251
258
  const checkouts: WorktreeCheckoutInspection[] = []
252
259
  for (const checkout of declared) {
253
260
  const repositoryPath = join(root, "repos", checkout.repo)
@@ -608,12 +615,14 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
608
615
  }
609
616
  const task = yield* tasks.show(taskId, root)
610
617
 
611
- let execution: {
612
- repo: string
613
- repos?: readonly RepositoryReference[]
614
- branch: string
615
- base: string
616
- }
618
+ let execution:
619
+ | {
620
+ repo: string
621
+ repos?: readonly RepositoryReference[]
622
+ branch: string
623
+ base: string
624
+ }
625
+ | { review: { repo: string; commit: string } }
617
626
  let phasePath: string | null = null
618
627
  let codePath: string
619
628
  if ("phases" in task.data) {
@@ -639,10 +648,19 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
639
648
  const requestedCheckouts: readonly (
640
649
  | { readonly repo: string; readonly branch: string }
641
650
  | RepositoryReference
642
- )[] = [
643
- { repo: execution.repo, branch: execution.branch },
644
- ...(execution.repos ?? []),
645
- ]
651
+ )[] =
652
+ "review" in execution
653
+ ? [
654
+ {
655
+ repo: execution.review.repo,
656
+ ref: execution.review.commit,
657
+ },
658
+ ]
659
+ : [
660
+ { repo: execution.repo, branch: execution.branch },
661
+ ...(execution.repos ?? []),
662
+ ]
663
+ const executionBase = "base" in execution ? execution.base : ""
646
664
  const canonicalCodePath = (yield* fs.exists(codePath))
647
665
  ? yield* fs.realPath(codePath)
648
666
  : resolve(codePath)
@@ -732,7 +750,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
732
750
  repositoryPath,
733
751
  "rev-parse",
734
752
  "--verify",
735
- `${execution.base}^{commit}`,
753
+ `${executionBase}^{commit}`,
736
754
  ],
737
755
  { captureOutput: true },
738
756
  )
@@ -744,7 +762,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
744
762
  repositoryPath,
745
763
  "ls-remote",
746
764
  "origin",
747
- originRef(execution.base),
765
+ originRef(executionBase),
748
766
  ],
749
767
  { captureOutput: true },
750
768
  )
@@ -753,7 +771,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
753
771
  !remoteBase.stdout.trim()
754
772
  ) {
755
773
  return yield* new WorktreeError({
756
- message: `Base '${execution.base}' for repository '${alias}' does not resolve to a commit`,
774
+ message: `Base '${executionBase}' for repository '${alias}' does not resolve to a commit`,
757
775
  })
758
776
  }
759
777
  }
@@ -764,7 +782,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
764
782
  repo: repositoryPath,
765
783
  worktree: checkoutPath,
766
784
  branch: checkout.branch,
767
- base: execution.base,
785
+ base: executionBase,
768
786
  })
769
787
  } catch (cause) {
770
788
  return yield* new WorktreeError({
@@ -991,7 +1009,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
991
1009
  repo: repositoryPath,
992
1010
  worktree: checkoutPath,
993
1011
  branch: checkout.branch,
994
- base: execution.base,
1012
+ base: executionBase,
995
1013
  }
996
1014
  try {
997
1015
  args = expandWorktreeCreateCommand(
@@ -1026,7 +1044,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
1026
1044
  repositoryPath,
1027
1045
  "branch",
1028
1046
  checkout.branch,
1029
- execution.base,
1047
+ executionBase,
1030
1048
  ]
1031
1049
  operations.push({
1032
1050
  action: "create-branch",
@@ -1085,7 +1103,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
1085
1103
  repositoryPath,
1086
1104
  "rev-parse",
1087
1105
  "--verify",
1088
- `${execution.base}^{commit}`,
1106
+ `${executionBase}^{commit}`,
1089
1107
  ],
1090
1108
  { captureOutput: true },
1091
1109
  )
@@ -1266,9 +1284,17 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
1266
1284
  taskPath: task.path,
1267
1285
  phasePath,
1268
1286
  codePath,
1269
- writablePath: join(codePath, execution.repo),
1270
- repo: execution.repo,
1271
- repos: execution.repos ?? [],
1287
+ writablePath:
1288
+ "review" in execution ? null : join(codePath, execution.repo),
1289
+ reviewPath:
1290
+ "review" in execution
1291
+ ? join(codePath, execution.review.repo)
1292
+ : null,
1293
+ repo:
1294
+ "review" in execution
1295
+ ? execution.review.repo
1296
+ : execution.repo,
1297
+ repos: "review" in execution ? [] : (execution.repos ?? []),
1272
1298
  dryRun: options.dryRun === true,
1273
1299
  checkouts: checkoutReports,
1274
1300
  operations,
@@ -1412,11 +1438,13 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
1412
1438
  }
1413
1439
  const task = yield* tasks.show(taskId, root)
1414
1440
 
1415
- let execution: {
1416
- repo: string
1417
- repos?: readonly RepositoryReference[]
1418
- branch: string
1419
- }
1441
+ let execution:
1442
+ | {
1443
+ repo: string
1444
+ repos?: readonly RepositoryReference[]
1445
+ branch: string
1446
+ }
1447
+ | { review: { repo: string; commit: string } }
1420
1448
  let codePath: string
1421
1449
  if ("phases" in task.data) {
1422
1450
  if (!phaseId) {
@@ -1450,10 +1478,18 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
1450
1478
  const expectedCheckouts: readonly (
1451
1479
  | { readonly repo: string; readonly branch: string }
1452
1480
  | RepositoryReference
1453
- )[] = [
1454
- { repo: execution.repo, branch: execution.branch },
1455
- ...(execution.repos ?? []),
1456
- ]
1481
+ )[] =
1482
+ "review" in execution
1483
+ ? [
1484
+ {
1485
+ repo: execution.review.repo,
1486
+ ref: execution.review.commit,
1487
+ },
1488
+ ]
1489
+ : [
1490
+ { repo: execution.repo, branch: execution.branch },
1491
+ ...(execution.repos ?? []),
1492
+ ]
1457
1493
  const expectedAliases = expectedCheckouts.map(({ repo }) => repo)
1458
1494
  if (codeDirectoryExists) {
1459
1495
  const unmanaged = (yield* fs.readDirectory(codePath)).filter(
package/src/test-utils.ts CHANGED
@@ -20,6 +20,7 @@ import { SyncService } from "./services/SyncService"
20
20
  import { ReadinessService } from "./services/ReadinessService"
21
21
  import { GraphMutationService } from "./services/GraphMutationService"
22
22
  import { DoctorService } from "./services/DoctorService"
23
+ import { ReviewService } from "./services/ReviewService"
23
24
 
24
25
  export const createTempDir = () => mkdtemp(join(tmpdir(), "agency-test-"))
25
26
 
@@ -44,6 +45,7 @@ const TestLayer = Layer.mergeAll(
44
45
  ReadinessService.Default,
45
46
  GraphMutationService.Default,
46
47
  DoctorService.Default,
48
+ ReviewService.Default,
47
49
  )
48
50
 
49
51
  export async function runTestEffect<A, E>(
package/src/work-view.ts CHANGED
@@ -79,7 +79,11 @@ const rowFor = (
79
79
  executions: readonly ExecutionNode[],
80
80
  ): WorkViewRow => {
81
81
  const branches = [
82
- ...new Set(executions.map((execution) => execution.data.branch)),
82
+ ...new Set(
83
+ executions.flatMap((execution) =>
84
+ "branch" in execution.data ? [execution.data.branch] : [],
85
+ ),
86
+ ),
83
87
  ]
84
88
  const parent =
85
89
  node.kind === "task"
@@ -105,16 +109,19 @@ const rowFor = (
105
109
  : branches.length === 1
106
110
  ? branches[0]!
107
111
  : "multiple",
108
- pr: aggregateLabel(executions, (execution) => Boolean(execution.data.pr), [
109
- "present",
110
- "absent",
111
- ]),
112
+ pr: aggregateLabel(
113
+ executions,
114
+ (execution) => "pr" in execution.data && Boolean(execution.data.pr),
115
+ ["present", "absent"],
116
+ ),
112
117
  worktree: aggregateLabel(
113
118
  executions,
114
119
  (execution) => execution.workspace?.materialized === true,
115
120
  ["materialized", "absent"],
116
121
  ),
117
- hasPr: executions.some((execution) => Boolean(execution.data.pr)),
122
+ hasPr: executions.some(
123
+ (execution) => "pr" in execution.data && Boolean(execution.data.pr),
124
+ ),
118
125
  }
119
126
  }
120
127
 
@@ -213,6 +220,7 @@ export const getWorkViews = (options: WorkViewOptions = {}) =>
213
220
  return executions.filter(
214
221
  (execution) =>
215
222
  execution.data.taskId === taskId &&
223
+ "phaseId" in execution.data &&
216
224
  execution.data.phaseId === phaseId,
217
225
  )
218
226
  }
@@ -43,7 +43,8 @@ reason to edit `agency.json` or `repos/` by hand.
43
43
  Require explicit user intent before initializing a workbase; changing repository
44
44
  aliases or applying repository setup or workbase sync changes; launching another
45
45
  agent from an active agent session; creating a pull request; archiving, restoring,
46
- dropping, or reopening work; or using `--force` to override readiness.
46
+ dropping, reopening, or completing work without a pull request; or using `--force`
47
+ to override readiness.
47
48
 
48
49
  ## Safety
49
50
 
@@ -80,7 +81,9 @@ release the claim with the current document revision.
80
81
  An execution unit remains `working` after implementation is committed and while
81
82
  its pull request is open. It becomes `done` only after its authoritative pull
82
83
  request is merged and Agency reconciles that state. Do not mark committed or
83
- review-ready work `done` manually.
84
+ review-ready work `done` manually. A genuine investigation, operational action,
85
+ or no-change result may complete without a pull request only with explicit user
86
+ intent, `--no-pull-request`, and a durable outcome summary.
84
87
 
85
88
  At each closeout trigger (creating or updating a PR, marking it ready, completing
86
89
  a refinement loop, or pausing or handing off completed implementation work):
@@ -90,6 +93,8 @@ a refinement loop, or pausing or handing off completed implementation work):
90
93
  keep the execution unit `working` through review and merge.
91
94
  - After merge, run `agency sync --apply` to reconcile the execution unit to
92
95
  `done`.
96
+ - For an approved non-PR outcome, finish an active claim or update unclaimed
97
+ status with `--no-pull-request --summary <text>` and optional supporting URL.
93
98
  - Refresh durable delivery context in `TASK.md` or `PHASE.md`, including recorded
94
99
  PR state, current head, diff summary, and verification results after later
95
100
  pushes when those details are maintained there.
@@ -0,0 +1,28 @@
1
+ import type { CompletionRecord } from "./schemas"
2
+
3
+ export interface NonPrCompletionInput {
4
+ readonly summary: string
5
+ readonly evidenceUrl?: string
6
+ }
7
+
8
+ export const buildNonPrCompletion = (
9
+ input: NonPrCompletionInput,
10
+ now: Date,
11
+ ): { readonly value: CompletionRecord } | { readonly error: string } => {
12
+ const summary = input.summary.trim()
13
+ if (!summary) return { error: "Non-PR completion summary must not be empty" }
14
+
15
+ const evidenceUrl = input.evidenceUrl?.trim()
16
+ if (evidenceUrl && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(evidenceUrl)) {
17
+ return { error: `Invalid completion evidence URL: ${evidenceUrl}` }
18
+ }
19
+
20
+ return {
21
+ value: {
22
+ mode: "non-pr",
23
+ completedAt: now.toISOString(),
24
+ summary,
25
+ ...(evidenceUrl ? { evidenceUrl } : {}),
26
+ },
27
+ }
28
+ }
@@ -64,6 +64,63 @@ describe("portable repository declarations", () => {
64
64
  })
65
65
 
66
66
  describe("body-of-work descriptions", () => {
67
+ test("decodes review tasks strictly and rejects writable execution fields", () => {
68
+ const review = {
69
+ ticketUrl: null,
70
+ review: {
71
+ repo: "agency",
72
+ source: { kind: "branch", ref: "refs/heads/feature/review" },
73
+ commit: "a".repeat(40),
74
+ refreshedAt: "2026-07-23T12:00:00.000Z",
75
+ },
76
+ }
77
+ expect(
78
+ Schema.decodeUnknownSync(TaskFrontmatter, { onExcessProperty: "error" })(
79
+ review,
80
+ ),
81
+ ).toMatchObject({ status: "open", review: review.review })
82
+ for (const field of ["repo", "repos", "branch", "base", "pr"]) {
83
+ expect(() =>
84
+ Schema.decodeUnknownSync(TaskFrontmatter, {
85
+ onExcessProperty: "error",
86
+ })({ ...review, [field]: field === "repos" ? [] : "forbidden" }),
87
+ ).toThrow()
88
+ }
89
+ })
90
+
91
+ test("rejects inconsistent pull request source provenance", () => {
92
+ const source = {
93
+ kind: "pull-request",
94
+ provider: "github",
95
+ repository: "owner/repo",
96
+ identifier: "42",
97
+ url: "https://github.com/owner/repo/pull/42",
98
+ fetchRef: "refs/pull/42/head",
99
+ }
100
+ const review = {
101
+ ticketUrl: null,
102
+ review: {
103
+ repo: "agency",
104
+ source,
105
+ commit: "a".repeat(40),
106
+ refreshedAt: "2026-07-23T12:00:00.000Z",
107
+ },
108
+ }
109
+ expect(Schema.decodeUnknownSync(TaskFrontmatter)(review)).toBeDefined()
110
+ for (const inconsistent of [
111
+ { ...source, url: "https://github.com/owner/repo/pull/41" },
112
+ { ...source, repository: "other/repo" },
113
+ { ...source, fetchRef: "refs/pull/41/head" },
114
+ ]) {
115
+ expect(() =>
116
+ Schema.decodeUnknownSync(TaskFrontmatter)({
117
+ ...review,
118
+ review: { ...review.review, source: inconsistent },
119
+ }),
120
+ ).toThrow()
121
+ }
122
+ })
123
+
67
124
  test("accepts descriptions on epics, tasks, and phases", () => {
68
125
  const epic = Schema.decodeUnknownSync(EpicFrontmatter)({
69
126
  ticketUrl: "https://example.com/epic",
@@ -43,6 +43,8 @@ const IsoTimestamp = NonEmptyString.pipe(
43
43
  Schema.pattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/),
44
44
  )
45
45
 
46
+ const GitCommit = Schema.String.pipe(Schema.pattern(/^[a-f0-9]{40}$/))
47
+
46
48
  const DocumentRevision = Schema.String.pipe(Schema.pattern(/^[a-f0-9]{64}$/))
47
49
 
48
50
  export const ClaimRecord = Schema.Struct({
@@ -75,6 +77,13 @@ export const PullRequestRecord = Schema.Struct({
75
77
  mergeable: Schema.optional(Schema.NullOr(Schema.Boolean)),
76
78
  })
77
79
 
80
+ export const CompletionRecord = Schema.Struct({
81
+ mode: Schema.Literal("non-pr"),
82
+ completedAt: IsoTimestamp,
83
+ summary: NonEmptyString,
84
+ evidenceUrl: Schema.optional(Url),
85
+ })
86
+
78
87
  const DeliveryProvider = Schema.Struct({
79
88
  provider: EntityId,
80
89
  remote: Schema.optional(NonEmptyString),
@@ -141,6 +150,7 @@ const ExecutionUnit = {
141
150
  pr: Schema.NullOr(Schema.Union(GitHubPullRequestUrl, PullRequestRecord)),
142
151
  status: Schema.optionalWith(WorkStatus, { default: () => "open" as const }),
143
152
  claim: Schema.optional(ClaimRecord),
153
+ completion: Schema.optional(CompletionRecord),
144
154
  }
145
155
 
146
156
  export const EpicFrontmatter = Schema.Struct({
@@ -164,9 +174,61 @@ const MultiPhaseTaskFrontmatter = Schema.Struct({
164
174
  phases: Schema.Array(Dependency),
165
175
  })
166
176
 
177
+ export const ReviewPullRequestSource = Schema.Struct({
178
+ kind: Schema.Literal("pull-request"),
179
+ provider: Schema.Literal("github"),
180
+ repository: NonEmptyString,
181
+ identifier: NonEmptyString.pipe(Schema.pattern(/^\d+$/)),
182
+ url: GitHubPullRequestUrl,
183
+ fetchRef: NonEmptyString,
184
+ }).pipe(
185
+ Schema.filter(
186
+ (source) =>
187
+ source.url ===
188
+ `https://github.com/${source.repository}/pull/${source.identifier}` &&
189
+ source.fetchRef === `refs/pull/${source.identifier}/head`,
190
+ {
191
+ message: () =>
192
+ "Pull request review source URL, repository, identifier, and fetch ref must agree",
193
+ },
194
+ ),
195
+ )
196
+
197
+ export const ReviewBranchSource = Schema.Struct({
198
+ kind: Schema.Literal("branch"),
199
+ ref: NonEmptyString.pipe(
200
+ Schema.pattern(
201
+ /^refs\/heads\/(?!HEAD$)(?!.*(?:\.\.|@\{|[ ~^:?*\[\\\]]))(?!.*\/\/)(?!.*(?:^|\/)\.)(?!.*\/$)(?!.*\.lock(?:\/|$))[A-Za-z0-9._\/-]+$/,
202
+ ),
203
+ ),
204
+ })
205
+
206
+ export const ReviewSource = Schema.Union(
207
+ ReviewPullRequestSource,
208
+ ReviewBranchSource,
209
+ )
210
+
211
+ export const ReviewRecord = Schema.Struct({
212
+ repo: RepositoryAlias,
213
+ source: ReviewSource,
214
+ commit: GitCommit,
215
+ refreshedAt: IsoTimestamp,
216
+ })
217
+
218
+ const ReviewTaskFrontmatter = Schema.Struct({
219
+ ticketUrl: Schema.NullOr(Url),
220
+ description: Description,
221
+ epic: Schema.optional(EntityId),
222
+ review: ReviewRecord,
223
+ status: Schema.optionalWith(WorkStatus, { default: () => "open" as const }),
224
+ claim: Schema.optional(ClaimRecord),
225
+ completion: Schema.optional(CompletionRecord),
226
+ })
227
+
167
228
  export const TaskFrontmatter = Schema.Union(
168
229
  SinglePhaseTaskFrontmatter,
169
230
  MultiPhaseTaskFrontmatter,
231
+ ReviewTaskFrontmatter,
170
232
  )
171
233
 
172
234
  export const PhaseFrontmatter = Schema.Struct({
@@ -187,6 +249,9 @@ export type RepositoryDeclaration = Schema.Schema.Type<
187
249
  export type WorkStatus = Schema.Schema.Type<typeof WorkStatus>
188
250
  export type ClaimRecord = Schema.Schema.Type<typeof ClaimRecord>
189
251
  export type PullRequestRecord = Schema.Schema.Type<typeof PullRequestRecord>
252
+ export type ReviewSource = Schema.Schema.Type<typeof ReviewSource>
253
+ export type ReviewRecord = Schema.Schema.Type<typeof ReviewRecord>
254
+ export type CompletionRecord = Schema.Schema.Type<typeof CompletionRecord>
190
255
  export type EpicFrontmatter = Schema.Schema.Type<typeof EpicFrontmatter>
191
256
  export type TaskFrontmatter = Schema.Schema.Type<typeof TaskFrontmatter>
192
257
  export type PhaseFrontmatter = Schema.Schema.Type<typeof PhaseFrontmatter>