get-tbd 0.1.24 → 0.1.26
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/README.md +2 -2
- package/dist/bin.mjs +195 -49
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +126 -44
- package/dist/cli.mjs.map +1 -1
- package/dist/{config-CB1tcqTZ.mjs → config-BZte2m3w.mjs} +1 -1
- package/dist/{config-CmEAGaxz.mjs → config-b20Kf5pW.mjs} +3 -2
- package/dist/config-b20Kf5pW.mjs.map +1 -0
- package/dist/docs/README.md +2 -2
- package/dist/docs/SKILL.md +31 -31
- package/dist/docs/guidelines/cli-agent-skill-patterns.md +1 -1
- package/dist/docs/guidelines/convex-limits-best-practices.md +16 -16
- package/dist/docs/guidelines/convex-rules.md +3 -3
- package/dist/docs/guidelines/electron-app-development-patterns.md +1 -1
- package/dist/docs/guidelines/error-handling-rules.md +2 -2
- package/dist/docs/guidelines/general-coding-rules.md +2 -2
- package/dist/docs/guidelines/general-comment-rules.md +2 -2
- package/dist/docs/guidelines/general-eng-assistant-rules.md +2 -2
- package/dist/docs/guidelines/python-rules.md +4 -4
- package/dist/docs/guidelines/typescript-rules.md +17 -17
- package/dist/docs/guidelines/typescript-yaml-handling-rules.md +8 -8
- package/dist/docs/shortcuts/standard/new-guideline.md +4 -4
- package/dist/docs/shortcuts/standard/new-validation-plan.md +13 -13
- package/dist/docs/shortcuts/standard/revise-all-architecture-docs.md +1 -1
- package/dist/docs/shortcuts/standard/setup-github-cli.md +1 -1
- package/dist/docs/shortcuts/standard/welcome-user.md +12 -12
- package/dist/docs/shortcuts/system/skill-baseline.md +31 -31
- package/dist/id-mapping-BA_xn516.mjs +3 -0
- package/dist/{id-mapping-DjVJIO4M.mjs → id-mapping-BtBwq5nG.mjs} +68 -15
- package/dist/id-mapping-BtBwq5nG.mjs.map +1 -0
- package/dist/index.mjs +2 -2
- package/dist/schemas-BQYmDnkv.mjs +311 -0
- package/dist/schemas-BQYmDnkv.mjs.map +1 -0
- package/dist/{src-BrM6xcdG.mjs → src-DQcOQnFp.mjs} +4 -3
- package/dist/{src-BrM6xcdG.mjs.map → src-DQcOQnFp.mjs.map} +1 -1
- package/dist/tbd +195 -49
- package/dist/yaml-utils-BPy991by.mjs +273 -0
- package/dist/yaml-utils-BPy991by.mjs.map +1 -0
- package/dist/yaml-utils-swV780m5.mjs +3 -0
- package/package.json +1 -1
- package/dist/config-CmEAGaxz.mjs.map +0 -1
- package/dist/id-mapping-DjVJIO4M.mjs.map +0 -1
- package/dist/id-mapping-LjnDSEhN.mjs +0 -3
- package/dist/yaml-utils-U7l9hhkh.mjs +0 -581
- package/dist/yaml-utils-U7l9hhkh.mjs.map +0 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/schemas.ts
|
|
4
|
+
/**
|
|
5
|
+
* Zod schemas for tbd entities.
|
|
6
|
+
*
|
|
7
|
+
* These schemas are the normative specification for the file format.
|
|
8
|
+
* See: tbd-design.md §2.6 Schemas
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* ISO8601 timestamp with Z suffix (UTC).
|
|
12
|
+
*/
|
|
13
|
+
const Timestamp = z.string().datetime();
|
|
14
|
+
/**
|
|
15
|
+
* Issue ID: prefix + 26 lowercase alphanumeric characters (ULID format).
|
|
16
|
+
* Format: is-{ulid} where ulid is 26 chars (a-z, 0-9).
|
|
17
|
+
* Example: is-01hx5zzkbkactav9wevgemmvrz
|
|
18
|
+
*/
|
|
19
|
+
const IssueId = z.string().regex(/^is-[0-9a-z]{26}$/);
|
|
20
|
+
/**
|
|
21
|
+
* Short ID: 1+ base36 characters used for external/display IDs.
|
|
22
|
+
* Typically 4 chars for new IDs (e.g., a7k2, b3m9).
|
|
23
|
+
* Imports may preserve longer numeric IDs (e.g., "100" from "tbd-100").
|
|
24
|
+
* Legacy imports may include dots (e.g., "208.1" from hierarchical numbering).
|
|
25
|
+
* Imports may include dashes/underscores (e.g., "stat-in_progress" from JSONL).
|
|
26
|
+
*/
|
|
27
|
+
const ShortId = z.string().regex(/^[0-9a-z._-]+$/);
|
|
28
|
+
/**
|
|
29
|
+
* ULID: 26 lowercase alphanumeric characters.
|
|
30
|
+
* Used in internal IDs and ID mappings.
|
|
31
|
+
* Example: 01hx5zzkbkactav9wevgemmvrz
|
|
32
|
+
*/
|
|
33
|
+
const Ulid = z.string().regex(/^[0-9a-z]{26}$/);
|
|
34
|
+
/**
|
|
35
|
+
* External Issue ID input: accepts {prefix}-{short} or just {short}.
|
|
36
|
+
* Examples: bd-a7k2, a7k2, bd-100, 100
|
|
37
|
+
*/
|
|
38
|
+
const ExternalIssueIdInput = z.string().regex(/^([a-z]+-)?[0-9a-z]+$/);
|
|
39
|
+
/**
|
|
40
|
+
* Edit counter - incremented on every local change.
|
|
41
|
+
* NOTE: Version is NOT used for conflict detection (Git push rejection is used).
|
|
42
|
+
* Content hash is used as tiebreaker during merge resolution.
|
|
43
|
+
* Version is informational only - set to max(local, remote) + 1 after merges.
|
|
44
|
+
*/
|
|
45
|
+
const Version = z.number().int().nonnegative();
|
|
46
|
+
/**
|
|
47
|
+
* Entity type discriminator.
|
|
48
|
+
*/
|
|
49
|
+
const EntityType = z.literal("is");
|
|
50
|
+
/**
|
|
51
|
+
* All entities share common fields.
|
|
52
|
+
*/
|
|
53
|
+
const BaseEntity = z.object({
|
|
54
|
+
type: EntityType,
|
|
55
|
+
id: IssueId,
|
|
56
|
+
version: Version,
|
|
57
|
+
created_at: Timestamp,
|
|
58
|
+
updated_at: Timestamp,
|
|
59
|
+
extensions: z.record(z.string(), z.unknown()).optional()
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* Issue status values matching Beads.
|
|
63
|
+
*/
|
|
64
|
+
const IssueStatus = z.enum([
|
|
65
|
+
"open",
|
|
66
|
+
"in_progress",
|
|
67
|
+
"blocked",
|
|
68
|
+
"deferred",
|
|
69
|
+
"closed"
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* Issue kind/type values matching Beads.
|
|
73
|
+
* Note: CLI uses --type flag, which maps to this `kind` field.
|
|
74
|
+
*/
|
|
75
|
+
const IssueKind = z.enum([
|
|
76
|
+
"bug",
|
|
77
|
+
"feature",
|
|
78
|
+
"task",
|
|
79
|
+
"epic",
|
|
80
|
+
"chore"
|
|
81
|
+
]);
|
|
82
|
+
/**
|
|
83
|
+
* Priority: 0 (highest/critical) to 4 (lowest).
|
|
84
|
+
*/
|
|
85
|
+
const Priority = z.number().int().min(0).max(4);
|
|
86
|
+
/**
|
|
87
|
+
* Dependency types - only "blocks" supported initially.
|
|
88
|
+
*/
|
|
89
|
+
const DependencyRelationType = z.enum(["blocks"]);
|
|
90
|
+
/**
|
|
91
|
+
* A dependency relationship.
|
|
92
|
+
*/
|
|
93
|
+
const Dependency = z.object({
|
|
94
|
+
type: DependencyRelationType,
|
|
95
|
+
target: IssueId
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Full issue schema.
|
|
99
|
+
*
|
|
100
|
+
* Field order is canonical and mirrored by ISSUE_FIELD_ORDER below:
|
|
101
|
+
* type, id, title, kind, status, priority, version (the "header seven"),
|
|
102
|
+
* then linkages, assignment, hierarchy, scheduling, provenance,
|
|
103
|
+
* timestamps, lifecycle, and extensions.
|
|
104
|
+
*
|
|
105
|
+
* Note: Fields use .nullable() in addition to .optional() because
|
|
106
|
+
* YAML parses `field: null` as JavaScript null, not undefined.
|
|
107
|
+
*
|
|
108
|
+
* Design note: We could add the short ID to this schema. We didn't originally
|
|
109
|
+
* because it's one more field to maintain consistency around across files.
|
|
110
|
+
* Having it here might make recovery of lost ID mappings far easier, but for
|
|
111
|
+
* now we have more reliable management of the mappings file (ids.yml) and
|
|
112
|
+
* consider it authoritative. See IdMappingYamlSchema (§2.6.8).
|
|
113
|
+
*/
|
|
114
|
+
const IssueSchema = BaseEntity.extend({
|
|
115
|
+
type: z.literal("is"),
|
|
116
|
+
title: z.string().min(1).max(500),
|
|
117
|
+
kind: IssueKind.default("task"),
|
|
118
|
+
status: IssueStatus.default("open"),
|
|
119
|
+
priority: Priority.default(2),
|
|
120
|
+
description: z.string().max(5e4).nullable().optional(),
|
|
121
|
+
notes: z.string().max(5e4).nullable().optional(),
|
|
122
|
+
spec_path: z.string().nullable().optional(),
|
|
123
|
+
assignee: z.string().nullable().optional(),
|
|
124
|
+
labels: z.array(z.string()).default([]),
|
|
125
|
+
dependencies: z.array(Dependency).default([]),
|
|
126
|
+
parent_id: IssueId.nullable().optional(),
|
|
127
|
+
child_order_hints: z.array(IssueId).nullable().optional(),
|
|
128
|
+
due_date: Timestamp.nullable().optional(),
|
|
129
|
+
deferred_until: Timestamp.nullable().optional(),
|
|
130
|
+
created_by: z.string().nullable().optional(),
|
|
131
|
+
closed_at: Timestamp.nullable().optional(),
|
|
132
|
+
close_reason: z.string().nullable().optional()
|
|
133
|
+
});
|
|
134
|
+
/**
|
|
135
|
+
* Git branch name - restricted to safe characters.
|
|
136
|
+
* Allows: alphanumeric, hyphens, underscores, forward slashes, and dots.
|
|
137
|
+
* Prevents shell injection in git commands.
|
|
138
|
+
*/
|
|
139
|
+
const GitBranchName = z.string().min(1).max(255).regex(/^[a-zA-Z0-9._/-]+$/, "Invalid branch name: only alphanumeric, dots, underscores, hyphens, and slashes allowed");
|
|
140
|
+
/**
|
|
141
|
+
* Git remote name - restricted to safe characters.
|
|
142
|
+
* Allows: alphanumeric, hyphens, underscores, and dots.
|
|
143
|
+
* Prevents shell injection in git commands.
|
|
144
|
+
*/
|
|
145
|
+
const GitRemoteName = z.string().min(1).max(255).regex(/^[a-zA-Z0-9._-]+$/, "Invalid remote name: only alphanumeric, dots, underscores, and hyphens allowed");
|
|
146
|
+
/**
|
|
147
|
+
* Doc cache configuration - maps destination paths to source locations.
|
|
148
|
+
*
|
|
149
|
+
* Keys are destination paths relative to .tbd/docs/ (e.g., "shortcuts/standard/code-review-and-commit.md")
|
|
150
|
+
* Values are source locations:
|
|
151
|
+
* - internal: prefix for bundled docs (e.g., "internal:shortcuts/standard/code-review-and-commit.md")
|
|
152
|
+
* - Full URL for external docs (e.g., "https://raw.githubusercontent.com/org/repo/main/file.md")
|
|
153
|
+
*
|
|
154
|
+
* Example:
|
|
155
|
+
* ```yaml
|
|
156
|
+
* doc_cache:
|
|
157
|
+
* shortcuts/standard/code-review-and-commit.md: internal:shortcuts/standard/code-review-and-commit.md
|
|
158
|
+
* shortcuts/custom/my-shortcut.md: https://raw.githubusercontent.com/org/repo/main/shortcuts/my-shortcut.md
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
const DocCacheConfigSchema = z.record(z.string(), z.string());
|
|
162
|
+
/**
|
|
163
|
+
* Documentation cache configuration (consolidated structure).
|
|
164
|
+
*
|
|
165
|
+
* Combines file sync mappings and lookup paths into a single config block.
|
|
166
|
+
* See: docs/project/specs/active/plan-2026-01-26-docs-cache-config-restructure.md
|
|
167
|
+
*/
|
|
168
|
+
const DocsCacheSchema = z.object({
|
|
169
|
+
files: z.record(z.string(), z.string()).optional(),
|
|
170
|
+
lookup_path: z.array(z.string()).default([".tbd/docs/shortcuts/system", ".tbd/docs/shortcuts/standard"])
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* Project configuration stored in .tbd/config.yml
|
|
174
|
+
*
|
|
175
|
+
* ⚠️ FORMAT VERSIONING: See tbd-format.ts for version history and migration rules.
|
|
176
|
+
* The tbd_format field tracks breaking changes to this schema.
|
|
177
|
+
*
|
|
178
|
+
* ⚠️ FORWARD COMPATIBILITY POLICY:
|
|
179
|
+
* This schema uses Zod's default strip() mode, which discards unknown fields.
|
|
180
|
+
* To prevent data loss when users mix tbd versions:
|
|
181
|
+
*
|
|
182
|
+
* 1. **When changing config schema (adding, removing, or modifying fields), ALWAYS
|
|
183
|
+
* bump the format version** (e.g., f03 → f04)
|
|
184
|
+
* 2. Older tbd versions will error when they see an unknown format version
|
|
185
|
+
* 3. The error message tells users to upgrade tbd
|
|
186
|
+
*
|
|
187
|
+
* This ensures older versions fail fast rather than silently corrupting config.
|
|
188
|
+
* The format version check happens in config.ts via isCompatibleFormat().
|
|
189
|
+
*
|
|
190
|
+
* See tbd-format.ts for format version history and migration rules.
|
|
191
|
+
*/
|
|
192
|
+
const ConfigSchema = z.object({
|
|
193
|
+
tbd_format: z.string().default("f01"),
|
|
194
|
+
tbd_version: z.string(),
|
|
195
|
+
sync: z.object({
|
|
196
|
+
branch: GitBranchName.default("tbd-sync"),
|
|
197
|
+
remote: GitRemoteName.default("origin")
|
|
198
|
+
}).default({}),
|
|
199
|
+
display: z.object({ id_prefix: z.string().min(1).max(20) }),
|
|
200
|
+
settings: z.object({
|
|
201
|
+
auto_sync: z.boolean().default(false),
|
|
202
|
+
doc_auto_sync_hours: z.number().default(24),
|
|
203
|
+
use_gh_cli: z.boolean().default(true)
|
|
204
|
+
}).default({}),
|
|
205
|
+
docs_cache: DocsCacheSchema.optional()
|
|
206
|
+
});
|
|
207
|
+
/**
|
|
208
|
+
* Shared metadata stored in .tbd/data-sync/meta.yml
|
|
209
|
+
*/
|
|
210
|
+
const MetaSchema = z.object({
|
|
211
|
+
schema_version: z.number().int(),
|
|
212
|
+
created_at: Timestamp
|
|
213
|
+
});
|
|
214
|
+
/**
|
|
215
|
+
* Per-node state stored in .tbd/state.yml (gitignored).
|
|
216
|
+
* Tracks local timing information that shouldn't be shared across nodes.
|
|
217
|
+
*/
|
|
218
|
+
const LocalStateSchema = z.object({
|
|
219
|
+
last_sync_at: Timestamp.optional(),
|
|
220
|
+
last_doc_sync_at: Timestamp.optional(),
|
|
221
|
+
welcome_seen: z.boolean().optional()
|
|
222
|
+
});
|
|
223
|
+
/**
|
|
224
|
+
* Preserved conflict losers.
|
|
225
|
+
*/
|
|
226
|
+
const AtticEntrySchema = z.object({
|
|
227
|
+
entity_id: IssueId,
|
|
228
|
+
timestamp: Timestamp,
|
|
229
|
+
field: z.string(),
|
|
230
|
+
lost_value: z.string(),
|
|
231
|
+
winner_source: z.enum(["local", "remote"]),
|
|
232
|
+
loser_source: z.enum(["local", "remote"]),
|
|
233
|
+
context: z.object({
|
|
234
|
+
local_version: Version,
|
|
235
|
+
remote_version: Version,
|
|
236
|
+
local_updated_at: Timestamp,
|
|
237
|
+
remote_updated_at: Timestamp
|
|
238
|
+
})
|
|
239
|
+
});
|
|
240
|
+
/**
|
|
241
|
+
* ID mapping YAML file schema for ids.yml.
|
|
242
|
+
* Maps short IDs to ULIDs.
|
|
243
|
+
* Format: { "a7k2": "01hx5zzkbkactav9wevgemmvrz", ... }
|
|
244
|
+
*/
|
|
245
|
+
const IdMappingYamlSchema = z.record(ShortId, Ulid);
|
|
246
|
+
/**
|
|
247
|
+
* Canonical field order for issue YAML frontmatter.
|
|
248
|
+
* (description and notes are body content, not frontmatter)
|
|
249
|
+
*/
|
|
250
|
+
const ISSUE_FIELD_ORDER = [
|
|
251
|
+
"type",
|
|
252
|
+
"id",
|
|
253
|
+
"title",
|
|
254
|
+
"kind",
|
|
255
|
+
"status",
|
|
256
|
+
"priority",
|
|
257
|
+
"version",
|
|
258
|
+
"spec_path",
|
|
259
|
+
"assignee",
|
|
260
|
+
"labels",
|
|
261
|
+
"dependencies",
|
|
262
|
+
"parent_id",
|
|
263
|
+
"child_order_hints",
|
|
264
|
+
"due_date",
|
|
265
|
+
"deferred_until",
|
|
266
|
+
"created_by",
|
|
267
|
+
"created_at",
|
|
268
|
+
"updated_at",
|
|
269
|
+
"closed_at",
|
|
270
|
+
"close_reason",
|
|
271
|
+
"extensions"
|
|
272
|
+
];
|
|
273
|
+
/**
|
|
274
|
+
* Canonical field order for config YAML.
|
|
275
|
+
*/
|
|
276
|
+
const CONFIG_FIELD_ORDER = [
|
|
277
|
+
"tbd_format",
|
|
278
|
+
"tbd_version",
|
|
279
|
+
"display",
|
|
280
|
+
"sync",
|
|
281
|
+
"settings",
|
|
282
|
+
"docs_cache"
|
|
283
|
+
];
|
|
284
|
+
/**
|
|
285
|
+
* Canonical field order for attic entry YAML.
|
|
286
|
+
*/
|
|
287
|
+
const ATTIC_ENTRY_FIELD_ORDER = [
|
|
288
|
+
"entity_id",
|
|
289
|
+
"timestamp",
|
|
290
|
+
"field",
|
|
291
|
+
"lost_value",
|
|
292
|
+
"winner_source",
|
|
293
|
+
"loser_source",
|
|
294
|
+
"context"
|
|
295
|
+
];
|
|
296
|
+
/**
|
|
297
|
+
* Canonical field order for meta YAML.
|
|
298
|
+
*/
|
|
299
|
+
const META_FIELD_ORDER = ["schema_version", "created_at"];
|
|
300
|
+
/**
|
|
301
|
+
* Canonical field order for local state YAML.
|
|
302
|
+
*/
|
|
303
|
+
const LOCAL_STATE_FIELD_ORDER = [
|
|
304
|
+
"last_sync_at",
|
|
305
|
+
"last_doc_sync_at",
|
|
306
|
+
"welcome_seen"
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
//#endregion
|
|
310
|
+
export { MetaSchema as C, Ulid as D, Timestamp as E, Version as O, META_FIELD_ORDER as S, ShortId as T, IssueKind as _, ConfigSchema as a, LOCAL_STATE_FIELD_ORDER as b, DocCacheConfigSchema as c, ExternalIssueIdInput as d, GitBranchName as f, IssueId as g, IdMappingYamlSchema as h, CONFIG_FIELD_ORDER as i, DocsCacheSchema as l, ISSUE_FIELD_ORDER as m, AtticEntrySchema as n, Dependency as o, GitRemoteName as p, BaseEntity as r, DependencyRelationType as s, ATTIC_ENTRY_FIELD_ORDER as t, EntityType as u, IssueSchema as v, Priority as w, LocalStateSchema as x, IssueStatus as y };
|
|
311
|
+
//# sourceMappingURL=schemas-BQYmDnkv.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas-BQYmDnkv.mjs","names":[],"sources":["../src/lib/schemas.ts"],"sourcesContent":["/**\n * Zod schemas for tbd entities.\n *\n * These schemas are the normative specification for the file format.\n * See: tbd-design.md §2.6 Schemas\n */\n\nimport { z } from 'zod';\n\n// =============================================================================\n// Common Types (§2.6.1)\n// =============================================================================\n\n/**\n * ISO8601 timestamp with Z suffix (UTC).\n */\nexport const Timestamp = z.string().datetime();\n\n/**\n * Issue ID: prefix + 26 lowercase alphanumeric characters (ULID format).\n * Format: is-{ulid} where ulid is 26 chars (a-z, 0-9).\n * Example: is-01hx5zzkbkactav9wevgemmvrz\n */\nexport const IssueId = z.string().regex(/^is-[0-9a-z]{26}$/);\n\n/**\n * Short ID: 1+ base36 characters used for external/display IDs.\n * Typically 4 chars for new IDs (e.g., a7k2, b3m9).\n * Imports may preserve longer numeric IDs (e.g., \"100\" from \"tbd-100\").\n * Legacy imports may include dots (e.g., \"208.1\" from hierarchical numbering).\n * Imports may include dashes/underscores (e.g., \"stat-in_progress\" from JSONL).\n */\nexport const ShortId = z.string().regex(/^[0-9a-z._-]+$/);\n\n/**\n * ULID: 26 lowercase alphanumeric characters.\n * Used in internal IDs and ID mappings.\n * Example: 01hx5zzkbkactav9wevgemmvrz\n */\nexport const Ulid = z.string().regex(/^[0-9a-z]{26}$/);\n\n/**\n * External Issue ID input: accepts {prefix}-{short} or just {short}.\n * Examples: bd-a7k2, a7k2, bd-100, 100\n */\nexport const ExternalIssueIdInput = z.string().regex(/^([a-z]+-)?[0-9a-z]+$/);\n\n/**\n * Edit counter - incremented on every local change.\n * NOTE: Version is NOT used for conflict detection (Git push rejection is used).\n * Content hash is used as tiebreaker during merge resolution.\n * Version is informational only - set to max(local, remote) + 1 after merges.\n */\nexport const Version = z.number().int().nonnegative();\n\n/**\n * Entity type discriminator.\n */\nexport const EntityType = z.literal('is');\n\n// =============================================================================\n// BaseEntity (§2.6.2)\n// =============================================================================\n\n/**\n * All entities share common fields.\n */\nexport const BaseEntity = z.object({\n type: EntityType,\n id: IssueId,\n version: Version,\n created_at: Timestamp,\n updated_at: Timestamp,\n\n // Extensibility namespace for third-party data\n extensions: z.record(z.string(), z.unknown()).optional(),\n});\n\n// =============================================================================\n// Issue Schema (§2.6.3)\n// =============================================================================\n\n/**\n * Issue status values matching Beads.\n */\nexport const IssueStatus = z.enum(['open', 'in_progress', 'blocked', 'deferred', 'closed']);\n\n/**\n * Issue kind/type values matching Beads.\n * Note: CLI uses --type flag, which maps to this `kind` field.\n */\nexport const IssueKind = z.enum(['bug', 'feature', 'task', 'epic', 'chore']);\n\n/**\n * Priority: 0 (highest/critical) to 4 (lowest).\n */\nexport const Priority = z.number().int().min(0).max(4);\n\n/**\n * Dependency types - only \"blocks\" supported initially.\n */\nexport const DependencyRelationType = z.enum(['blocks']);\n\n/**\n * A dependency relationship.\n */\nexport const Dependency = z.object({\n type: DependencyRelationType,\n target: IssueId,\n});\n\n/**\n * Full issue schema.\n *\n * Field order is canonical and mirrored by ISSUE_FIELD_ORDER below:\n * type, id, title, kind, status, priority, version (the \"header seven\"),\n * then linkages, assignment, hierarchy, scheduling, provenance,\n * timestamps, lifecycle, and extensions.\n *\n * Note: Fields use .nullable() in addition to .optional() because\n * YAML parses `field: null` as JavaScript null, not undefined.\n *\n * Design note: We could add the short ID to this schema. We didn't originally\n * because it's one more field to maintain consistency around across files.\n * Having it here might make recovery of lost ID mappings far easier, but for\n * now we have more reliable management of the mappings file (ids.yml) and\n * consider it authoritative. See IdMappingYamlSchema (§2.6.8).\n */\nexport const IssueSchema = BaseEntity.extend({\n // Header seven: the fields you always want to see at a glance\n type: z.literal('is'),\n // id, version inherited from BaseEntity\n title: z.string().min(1).max(500),\n kind: IssueKind.default('task'),\n status: IssueStatus.default('open'),\n priority: Priority.default(2),\n\n // Body content (serialized outside frontmatter)\n description: z.string().max(50000).nullable().optional(),\n notes: z.string().max(50000).nullable().optional(),\n\n // Linkages\n spec_path: z.string().nullable().optional(),\n\n // Assignment and categorization\n assignee: z.string().nullable().optional(),\n labels: z.array(z.string()).default([]),\n dependencies: z.array(Dependency).default([]),\n\n // Hierarchical issues\n parent_id: IssueId.nullable().optional(),\n\n // Child ordering hints - soft ordering for children under this parent.\n // Array of internal IssueIds in preferred display order.\n // May contain stale IDs; display logic filters for actual children.\n child_order_hints: z.array(IssueId).nullable().optional(),\n\n // Scheduling\n due_date: Timestamp.nullable().optional(),\n deferred_until: Timestamp.nullable().optional(),\n\n // Provenance and lifecycle\n created_by: z.string().nullable().optional(),\n closed_at: Timestamp.nullable().optional(),\n close_reason: z.string().nullable().optional(),\n});\n\n// =============================================================================\n// Config Schema (§2.6.4)\n// =============================================================================\n\n/**\n * Git branch name - restricted to safe characters.\n * Allows: alphanumeric, hyphens, underscores, forward slashes, and dots.\n * Prevents shell injection in git commands.\n */\nexport const GitBranchName = z\n .string()\n .min(1)\n .max(255)\n .regex(\n /^[a-zA-Z0-9._/-]+$/,\n 'Invalid branch name: only alphanumeric, dots, underscores, hyphens, and slashes allowed',\n );\n\n/**\n * Git remote name - restricted to safe characters.\n * Allows: alphanumeric, hyphens, underscores, and dots.\n * Prevents shell injection in git commands.\n */\nexport const GitRemoteName = z\n .string()\n .min(1)\n .max(255)\n .regex(\n /^[a-zA-Z0-9._-]+$/,\n 'Invalid remote name: only alphanumeric, dots, underscores, and hyphens allowed',\n );\n\n/**\n * Doc cache configuration - maps destination paths to source locations.\n *\n * Keys are destination paths relative to .tbd/docs/ (e.g., \"shortcuts/standard/code-review-and-commit.md\")\n * Values are source locations:\n * - internal: prefix for bundled docs (e.g., \"internal:shortcuts/standard/code-review-and-commit.md\")\n * - Full URL for external docs (e.g., \"https://raw.githubusercontent.com/org/repo/main/file.md\")\n *\n * Example:\n * ```yaml\n * doc_cache:\n * shortcuts/standard/code-review-and-commit.md: internal:shortcuts/standard/code-review-and-commit.md\n * shortcuts/custom/my-shortcut.md: https://raw.githubusercontent.com/org/repo/main/shortcuts/my-shortcut.md\n * ```\n */\nexport const DocCacheConfigSchema = z.record(z.string(), z.string());\n\n/**\n * Documentation cache configuration (consolidated structure).\n *\n * Combines file sync mappings and lookup paths into a single config block.\n * See: docs/project/specs/active/plan-2026-01-26-docs-cache-config-restructure.md\n */\nexport const DocsCacheSchema = z.object({\n /**\n * Files to sync: maps destination paths to source locations.\n * Keys are destination paths relative to .tbd/docs/\n * Values are source locations:\n * - internal: prefix for bundled docs (e.g., \"internal:shortcuts/standard/code-review-and-commit.md\")\n * - Full URL for external docs (e.g., \"https://raw.githubusercontent.com/org/repo/main/file.md\")\n */\n files: z.record(z.string(), z.string()).optional(),\n /**\n * Search paths for doc lookup (like shell $PATH).\n * Earlier paths take precedence when names conflict.\n */\n lookup_path: z\n .array(z.string())\n .default(['.tbd/docs/shortcuts/system', '.tbd/docs/shortcuts/standard']),\n});\n\n/**\n * Project configuration stored in .tbd/config.yml\n *\n * ⚠️ FORMAT VERSIONING: See tbd-format.ts for version history and migration rules.\n * The tbd_format field tracks breaking changes to this schema.\n *\n * ⚠️ FORWARD COMPATIBILITY POLICY:\n * This schema uses Zod's default strip() mode, which discards unknown fields.\n * To prevent data loss when users mix tbd versions:\n *\n * 1. **When changing config schema (adding, removing, or modifying fields), ALWAYS\n * bump the format version** (e.g., f03 → f04)\n * 2. Older tbd versions will error when they see an unknown format version\n * 3. The error message tells users to upgrade tbd\n *\n * This ensures older versions fail fast rather than silently corrupting config.\n * The format version check happens in config.ts via isCompatibleFormat().\n *\n * See tbd-format.ts for format version history and migration rules.\n */\nexport const ConfigSchema = z.object({\n /**\n * Format version for the .tbd/ directory structure.\n * See tbd-format.ts for version history and migration rules.\n * Only bumped for breaking changes that require migration.\n */\n tbd_format: z.string().default('f01'),\n\n tbd_version: z.string(),\n sync: z\n .object({\n branch: GitBranchName.default('tbd-sync'),\n remote: GitRemoteName.default('origin'),\n })\n .default({}),\n display: z.object({\n id_prefix: z.string().min(1).max(20), // Required: set during init --prefix or import\n }),\n settings: z\n .object({\n auto_sync: z.boolean().default(false),\n /**\n * How often to automatically sync documentation cache (in hours).\n * - Default: 24 (sync once per day when actively using tbd)\n * - Set to 0 to disable auto-sync\n * - Only triggers when accessing docs (shortcut, guidelines, template commands)\n */\n doc_auto_sync_hours: z.number().default(24),\n /**\n * Whether to install the ensure-gh-cli.sh hook script during setup.\n * When true (default), `tbd setup` installs a SessionStart hook that\n * ensures the GitHub CLI is available in agent sessions.\n * Set to false or use `tbd setup --no-gh-cli` to disable.\n */\n use_gh_cli: z.boolean().default(true),\n })\n .default({}),\n /**\n * Documentation cache configuration (consolidated).\n * Contains files to sync and lookup paths.\n * See DocsCacheSchema for structure details.\n */\n docs_cache: DocsCacheSchema.optional(),\n});\n\n// =============================================================================\n// Meta Schema (§2.6.5)\n// =============================================================================\n\n/**\n * Shared metadata stored in .tbd/data-sync/meta.yml\n */\nexport const MetaSchema = z.object({\n schema_version: z.number().int(),\n created_at: Timestamp,\n});\n\n// =============================================================================\n// Local State Schema (§2.6.6)\n// =============================================================================\n\n/**\n * Per-node state stored in .tbd/state.yml (gitignored).\n * Tracks local timing information that shouldn't be shared across nodes.\n */\nexport const LocalStateSchema = z.object({\n /** When this node last synced issues successfully */\n last_sync_at: Timestamp.optional(),\n /** When this node last synced the doc cache successfully */\n last_doc_sync_at: Timestamp.optional(),\n /** Whether the user has seen the welcome message */\n welcome_seen: z.boolean().optional(),\n});\n\n// =============================================================================\n// Attic Entry Schema (§2.6.7)\n// =============================================================================\n\n/**\n * Preserved conflict losers.\n */\nexport const AtticEntrySchema = z.object({\n entity_id: IssueId,\n timestamp: Timestamp,\n field: z.string(),\n lost_value: z.string(),\n winner_source: z.enum(['local', 'remote']),\n loser_source: z.enum(['local', 'remote']),\n context: z.object({\n local_version: Version,\n remote_version: Version,\n local_updated_at: Timestamp,\n remote_updated_at: Timestamp,\n }),\n});\n\n// =============================================================================\n// ID Mapping Schema (§2.6.8)\n// =============================================================================\n\n/**\n * ID mapping YAML file schema for ids.yml.\n * Maps short IDs to ULIDs.\n * Format: { \"a7k2\": \"01hx5zzkbkactav9wevgemmvrz\", ... }\n */\nexport const IdMappingYamlSchema = z.record(ShortId, Ulid);\n\n// =============================================================================\n// Field Order Constants for YAML Serialization\n// =============================================================================\n//\n// Each array defines the canonical field order for YAML output.\n// Order mirrors the Zod schema definition above: identity first,\n// human-relevant fields next, bookkeeping last.\n//\n// Used with sortKeys() from yaml-utils.ts and ordering.manual()\n// from comparison-chain.ts. Fields not listed sort to the end.\n\n/**\n * Canonical field order for issue YAML frontmatter.\n * (description and notes are body content, not frontmatter)\n */\nexport const ISSUE_FIELD_ORDER = [\n // Header seven: the fields you always want to see at a glance\n 'type',\n 'id',\n 'title',\n 'kind',\n 'status',\n 'priority',\n 'version',\n\n // Linkages\n 'spec_path',\n\n // Assignment and categorization\n 'assignee',\n 'labels',\n 'dependencies',\n\n // Hierarchy\n 'parent_id',\n 'child_order_hints',\n\n // Scheduling\n 'due_date',\n 'deferred_until',\n\n // Provenance\n 'created_by',\n\n // Timestamps\n 'created_at',\n 'updated_at',\n\n // Lifecycle (closure)\n 'closed_at',\n 'close_reason',\n\n // Extensibility\n 'extensions',\n] as const;\n\n/**\n * Canonical field order for config YAML.\n */\nexport const CONFIG_FIELD_ORDER = [\n 'tbd_format',\n 'tbd_version',\n 'display',\n 'sync',\n 'settings',\n 'docs_cache',\n] as const;\n\n/**\n * Canonical field order for attic entry YAML.\n */\nexport const ATTIC_ENTRY_FIELD_ORDER = [\n 'entity_id',\n 'timestamp',\n 'field',\n 'lost_value',\n 'winner_source',\n 'loser_source',\n 'context',\n] as const;\n\n/**\n * Canonical field order for meta YAML.\n */\nexport const META_FIELD_ORDER = ['schema_version', 'created_at'] as const;\n\n/**\n * Canonical field order for local state YAML.\n */\nexport const LOCAL_STATE_FIELD_ORDER = [\n 'last_sync_at',\n 'last_doc_sync_at',\n 'welcome_seen',\n] as const;\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { m as ISSUE_FIELD_ORDER, v as IssueSchema } from "./schemas-BQYmDnkv.mjs";
|
|
2
|
+
import { c as stringifyYamlCompact, o as sortKeys, s as stringifyYaml } from "./yaml-utils-BPy991by.mjs";
|
|
2
3
|
import matter from "gray-matter";
|
|
3
4
|
import { parse } from "yaml";
|
|
4
5
|
|
|
@@ -182,8 +183,8 @@ function serializeIssue(issue) {
|
|
|
182
183
|
* Package version, derived from git at build time.
|
|
183
184
|
* Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.
|
|
184
185
|
*/
|
|
185
|
-
const VERSION = "0.1.
|
|
186
|
+
const VERSION = "0.1.26";
|
|
186
187
|
|
|
187
188
|
//#endregion
|
|
188
189
|
export { insertAfterFrontmatter as a, noopLogger as c, serializeIssue as i, parseIssue as n, parseMarkdown as o, parseMarkdownWithFrontmatter as r, stripFrontmatter as s, VERSION as t };
|
|
189
|
-
//# sourceMappingURL=src-
|
|
190
|
+
//# sourceMappingURL=src-DQcOQnFp.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"src-BrM6xcdG.mjs","names":["parseYaml"],"sources":["../src/lib/types.ts","../src/utils/markdown-utils.ts","../src/file/parser.ts","../src/index.ts"],"sourcesContent":["/**\n * TypeScript types derived from Zod schemas.\n *\n * These types are the canonical TypeScript interface for tbd entities.\n */\n\nimport type { z } from 'zod';\n\nimport type {\n IssueSchema,\n IssueStatus,\n IssueKind,\n Priority,\n Dependency,\n ConfigSchema,\n MetaSchema,\n LocalStateSchema,\n AtticEntrySchema,\n} from './schemas.js';\n\n// =============================================================================\n// Entity Types\n// =============================================================================\n\n/**\n * A tbd issue entity.\n */\nexport type Issue = z.infer<typeof IssueSchema>;\n\n/**\n * Issue status enum values.\n */\nexport type IssueStatusType = z.infer<typeof IssueStatus>;\n\n/**\n * Issue kind enum values.\n */\nexport type IssueKindType = z.infer<typeof IssueKind>;\n\n/**\n * Priority level (0-4).\n */\nexport type PriorityType = z.infer<typeof Priority>;\n\n/**\n * A dependency relationship.\n */\nexport type DependencyType = z.infer<typeof Dependency>;\n\n// =============================================================================\n// Configuration Types\n// =============================================================================\n\n/**\n * Project configuration.\n */\nexport type Config = z.infer<typeof ConfigSchema>;\n\n/**\n * Shared metadata.\n */\nexport type Meta = z.infer<typeof MetaSchema>;\n\n/**\n * Per-node local state.\n */\nexport type LocalState = z.infer<typeof LocalStateSchema>;\n\n/**\n * Attic entry for conflict losers.\n */\nexport type AtticEntry = z.infer<typeof AtticEntrySchema>;\n\n// =============================================================================\n// Input Types for Commands\n// =============================================================================\n\n/**\n * Options for creating an issue.\n */\nexport interface CreateIssueOptions {\n title: string;\n description?: string;\n kind?: IssueKindType;\n priority?: PriorityType;\n assignee?: string;\n labels?: string[];\n parent_id?: string;\n due_date?: string;\n deferred_until?: string;\n}\n\n/**\n * Options for updating an issue.\n */\nexport interface UpdateIssueOptions {\n title?: string;\n description?: string;\n notes?: string;\n kind?: IssueKindType;\n status?: IssueStatusType;\n priority?: PriorityType;\n assignee?: string | null;\n addLabels?: string[];\n removeLabels?: string[];\n parent_id?: string | null;\n due_date?: string | null;\n deferred_until?: string | null;\n}\n\n/**\n * Options for listing issues.\n */\nexport interface ListIssuesOptions {\n status?: IssueStatusType | IssueStatusType[];\n kind?: IssueKindType | IssueKindType[];\n priority?: PriorityType;\n assignee?: string;\n labels?: string[];\n parent?: string;\n all?: boolean;\n sort?: 'priority' | 'created' | 'updated';\n limit?: number;\n}\n\n/**\n * Options for searching issues.\n */\nexport interface SearchIssuesOptions {\n query: string;\n status?: IssueStatusType | IssueStatusType[];\n limit?: number;\n}\n\n// =============================================================================\n// CLI Utility Types\n// =============================================================================\n\n/**\n * A documentation section with title and slug.\n * Used by docs and design commands.\n */\nexport interface DocSection {\n title: string;\n slug: string;\n}\n\n/**\n * Logger interface for long-running operations in non-CLI layers.\n *\n * Allows core logic (file/, lib/) to report progress without depending on\n * the CLI output layer. CLI commands create an OperationLogger via\n * `OutputManager.logger(spinner)` and pass it to core functions.\n *\n * All methods are required. Use `noopLogger` when no logging is needed.\n */\nexport interface OperationLogger {\n /** Key milestones — drives the spinner in CLI context */\n progress: (message: string) => void;\n /** Operational detail (shown with --verbose or --debug) */\n info: (message: string) => void;\n /** Non-fatal warnings */\n warn: (message: string) => void;\n /** Internal state for troubleshooting (shown with --debug only) */\n debug: (message: string) => void;\n}\n\n/**\n * No-op logger for when no logging is needed.\n * Analogous to noopSpinner in the CLI layer.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst noop = () => {};\nexport const noopLogger: OperationLogger = {\n progress: noop,\n info: noop,\n warn: noop,\n debug: noop,\n};\n","/**\n * Markdown utilities for processing markdown content.\n *\n * Uses gray-matter for parsing and centralized yaml-utils for stringify to ensure\n * proper handling of special YAML characters (colons, quotes, etc.).\n */\n\nimport matter from 'gray-matter';\n\nimport { stringifyYamlCompact } from './yaml-utils.js';\n\nexport interface ParsedMarkdown {\n /** Raw frontmatter string (without --- delimiters), or null if no frontmatter */\n frontmatter: string | null;\n /** Body content after frontmatter, with leading newlines trimmed */\n body: string;\n}\n\n/**\n * Normalize line endings to LF.\n */\nexport function normalizeLineEndings(content: string): string {\n return content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n}\n\n/**\n * Parse markdown content into frontmatter and body.\n * Handles both LF and CRLF line endings.\n *\n * @returns Object with frontmatter (null if none) and body\n */\nexport function parseMarkdown(content: string): ParsedMarkdown {\n const normalized = normalizeLineEndings(content);\n\n if (!matter.test(normalized)) {\n return { frontmatter: null, body: content };\n }\n\n try {\n const parsed = matter(normalized);\n\n // Extract frontmatter from parsed.data by stringifying back to YAML\n // The matter property is unreliable, so we reconstruct from data\n const data = parsed.data;\n let frontmatter: string | null = null;\n\n if (data && Object.keys(data).length > 0) {\n // Use centralized yaml-utils for proper handling of special characters\n // (colons, quotes, multiline strings, etc.)\n frontmatter = stringifyYamlCompact(data).trimEnd();\n } else {\n // Empty frontmatter (just --- followed by ---)\n frontmatter = '';\n }\n\n // Body with leading newlines trimmed\n const body = parsed.content.replace(/^\\n+/, '');\n\n return { frontmatter, body };\n } catch {\n // Invalid/unclosed frontmatter - treat as no frontmatter\n return { frontmatter: null, body: content };\n }\n}\n\n/**\n * Parse YAML frontmatter from markdown content.\n * Returns the frontmatter content (without delimiters) or null if no valid frontmatter.\n * Handles both LF and CRLF line endings.\n */\nexport function parseFrontmatter(content: string): string | null {\n return parseMarkdown(content).frontmatter;\n}\n\n/**\n * Strip YAML frontmatter from markdown content.\n * Returns the body content without frontmatter, with leading newlines trimmed.\n * Handles both LF and CRLF line endings.\n */\nexport function stripFrontmatter(content: string): string {\n return parseMarkdown(content).body;\n}\n\n/**\n * Insert content after YAML frontmatter.\n * If no frontmatter exists, prepends the content.\n * Content is inserted directly after ---. Include leading newlines in toInsert if needed.\n */\nexport function insertAfterFrontmatter(content: string, toInsert: string): string {\n const { frontmatter, body } = parseMarkdown(content);\n\n if (frontmatter === null) {\n return toInsert + content;\n }\n\n const frontmatterBlock = frontmatter ? `---\\n${frontmatter}\\n---` : '---\\n---';\n return `${frontmatterBlock}\\n${toInsert}\\n\\n${body}`;\n}\n","/**\n * YAML front matter parser and serializer for issue files.\n *\n * Issues are stored as Markdown files with YAML front matter:\n * ---\n * type: is\n * id: is-a1b2c3\n * ...\n * ---\n *\n * Description body here.\n *\n * ## Notes\n *\n * Working notes here.\n *\n * See: tbd-design.md §2.1 Markdown + YAML Front Matter Format\n */\n\nimport matter from 'gray-matter';\nimport { parse as parseYaml } from 'yaml';\n\nimport { normalizeLineEndings } from '../utils/markdown-utils.js';\nimport { sortKeys, stringifyYaml } from '../utils/yaml-utils.js';\nimport type { Issue } from '../lib/types.js';\nimport { IssueSchema, ISSUE_FIELD_ORDER } from '../lib/schemas.js';\n\n/**\n * gray-matter options using the 'yaml' package as engine.\n * This preserves date strings instead of converting them to Date objects.\n */\nexport const matterOptions = {\n engines: {\n yaml: {\n parse: (str: string): object => parseYaml(str) as object,\n stringify: (obj: object): string => stringifyYaml(obj),\n },\n },\n};\n\n/**\n * Parsed issue file content.\n */\nexport interface ParsedIssueFile {\n frontmatter: Record<string, unknown>;\n description: string;\n notes: string;\n}\n\n/**\n * Parse a Markdown file with YAML front matter.\n * Uses gray-matter for consistent frontmatter parsing.\n * Handles both LF and CRLF line endings.\n */\nexport function parseMarkdownWithFrontmatter(content: string): ParsedIssueFile {\n // Normalize CRLF to LF before parsing\n const normalizedContent = normalizeLineEndings(content);\n\n // Check for valid frontmatter\n if (!matter.test(normalizedContent)) {\n throw new Error('Invalid format: missing front matter opening delimiter');\n }\n\n const parsed = matter(normalizedContent, matterOptions);\n\n // gray-matter returns empty object if no closing delimiter found\n // but the raw matter string will be empty if parsing failed\n if (parsed.matter === '' && !normalizedContent.includes('---\\n---')) {\n // Check if there's actually a closing delimiter\n const lines = normalizedContent.split('\\n');\n let hasClosing = false;\n for (let i = 1; i < lines.length; i++) {\n if (lines[i]?.trim() === '---') {\n hasClosing = true;\n break;\n }\n }\n if (!hasClosing) {\n throw new Error('Invalid format: missing front matter closing delimiter');\n }\n }\n\n const frontmatter = parsed.data as Record<string, unknown>;\n\n // Parse body - split into description and notes\n const body = parsed.content.trim();\n\n // Find notes section\n const notesMatch = /\\n## Notes\\n/i.exec(body);\n let description = body;\n let notes = '';\n\n if (notesMatch?.index !== undefined) {\n description = body.slice(0, notesMatch.index).trim();\n notes = body.slice(notesMatch.index + notesMatch[0].length).trim();\n }\n\n return { frontmatter, description, notes };\n}\n\n/**\n * Parse an issue from Markdown file content.\n */\nexport function parseIssue(content: string): Issue {\n const { frontmatter, description, notes } = parseMarkdownWithFrontmatter(content);\n\n // Merge body content into frontmatter\n const data = {\n ...frontmatter,\n description: description || undefined,\n notes: notes || undefined,\n };\n\n // Validate and parse with Zod\n return IssueSchema.parse(data);\n}\n\n/**\n * Serialize an issue to Markdown file content.\n * Uses canonical serialization for deterministic output.\n */\nexport function serializeIssue(issue: Issue): string {\n // Extract body fields\n const { description, notes, ...metadata } = issue;\n\n // Sort keys using canonical field order (not alphabetical)\n const sortedMetadata = sortKeys(metadata, ISSUE_FIELD_ORDER);\n\n // Serialize YAML with compact output for frontmatter.\n // sortMapEntries: false preserves our manual ordering.\n const yaml = stringifyYaml(sortedMetadata, {\n lineWidth: 0,\n nullStr: 'null',\n sortMapEntries: false,\n });\n\n // Build the file content\n // Note: No blank line between closing --- and body content\n const parts = ['---', yaml.trim(), '---'];\n\n if (description) {\n parts.push(description.trim());\n }\n\n if (notes) {\n parts.push('');\n parts.push('## Notes');\n parts.push('');\n parts.push(notes.trim());\n }\n\n // Single newline at end\n return parts.join('\\n') + '\\n';\n}\n","/**\n * tbd: Git-native issue tracking for AI agents and humans\n *\n * This is the library entry point. All exports here should be node-free\n * to support browser/edge runtime usage. CLI-specific code is in ./cli/.\n */\n\n// Version injected at build time\ndeclare const __TBD_VERSION__: string;\n\n/**\n * Package version, derived from git at build time.\n * Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.\n */\nexport const VERSION: string =\n typeof __TBD_VERSION__ !== 'undefined' ? __TBD_VERSION__ : 'development';\n\n// Re-export schemas for library consumers\nexport * from './lib/schemas.js';\nexport * from './lib/types.js';\n\n// Re-export core operations (these should be node-free)\nexport { parseIssue, serializeIssue } from './file/parser.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"src-DQcOQnFp.mjs","names":["parseYaml"],"sources":["../src/lib/types.ts","../src/utils/markdown-utils.ts","../src/file/parser.ts","../src/index.ts"],"sourcesContent":["/**\n * TypeScript types derived from Zod schemas.\n *\n * These types are the canonical TypeScript interface for tbd entities.\n */\n\nimport type { z } from 'zod';\n\nimport type {\n IssueSchema,\n IssueStatus,\n IssueKind,\n Priority,\n Dependency,\n ConfigSchema,\n MetaSchema,\n LocalStateSchema,\n AtticEntrySchema,\n} from './schemas.js';\n\n// =============================================================================\n// Entity Types\n// =============================================================================\n\n/**\n * A tbd issue entity.\n */\nexport type Issue = z.infer<typeof IssueSchema>;\n\n/**\n * Issue status enum values.\n */\nexport type IssueStatusType = z.infer<typeof IssueStatus>;\n\n/**\n * Issue kind enum values.\n */\nexport type IssueKindType = z.infer<typeof IssueKind>;\n\n/**\n * Priority level (0-4).\n */\nexport type PriorityType = z.infer<typeof Priority>;\n\n/**\n * A dependency relationship.\n */\nexport type DependencyType = z.infer<typeof Dependency>;\n\n// =============================================================================\n// Configuration Types\n// =============================================================================\n\n/**\n * Project configuration.\n */\nexport type Config = z.infer<typeof ConfigSchema>;\n\n/**\n * Shared metadata.\n */\nexport type Meta = z.infer<typeof MetaSchema>;\n\n/**\n * Per-node local state.\n */\nexport type LocalState = z.infer<typeof LocalStateSchema>;\n\n/**\n * Attic entry for conflict losers.\n */\nexport type AtticEntry = z.infer<typeof AtticEntrySchema>;\n\n// =============================================================================\n// Input Types for Commands\n// =============================================================================\n\n/**\n * Options for creating an issue.\n */\nexport interface CreateIssueOptions {\n title: string;\n description?: string;\n kind?: IssueKindType;\n priority?: PriorityType;\n assignee?: string;\n labels?: string[];\n parent_id?: string;\n due_date?: string;\n deferred_until?: string;\n}\n\n/**\n * Options for updating an issue.\n */\nexport interface UpdateIssueOptions {\n title?: string;\n description?: string;\n notes?: string;\n kind?: IssueKindType;\n status?: IssueStatusType;\n priority?: PriorityType;\n assignee?: string | null;\n addLabels?: string[];\n removeLabels?: string[];\n parent_id?: string | null;\n due_date?: string | null;\n deferred_until?: string | null;\n}\n\n/**\n * Options for listing issues.\n */\nexport interface ListIssuesOptions {\n status?: IssueStatusType | IssueStatusType[];\n kind?: IssueKindType | IssueKindType[];\n priority?: PriorityType;\n assignee?: string;\n labels?: string[];\n parent?: string;\n all?: boolean;\n sort?: 'priority' | 'created' | 'updated';\n limit?: number;\n}\n\n/**\n * Options for searching issues.\n */\nexport interface SearchIssuesOptions {\n query: string;\n status?: IssueStatusType | IssueStatusType[];\n limit?: number;\n}\n\n// =============================================================================\n// CLI Utility Types\n// =============================================================================\n\n/**\n * A documentation section with title and slug.\n * Used by docs and design commands.\n */\nexport interface DocSection {\n title: string;\n slug: string;\n}\n\n/**\n * Logger interface for long-running operations in non-CLI layers.\n *\n * Allows core logic (file/, lib/) to report progress without depending on\n * the CLI output layer. CLI commands create an OperationLogger via\n * `OutputManager.logger(spinner)` and pass it to core functions.\n *\n * All methods are required. Use `noopLogger` when no logging is needed.\n */\nexport interface OperationLogger {\n /** Key milestones — drives the spinner in CLI context */\n progress: (message: string) => void;\n /** Operational detail (shown with --verbose or --debug) */\n info: (message: string) => void;\n /** Non-fatal warnings */\n warn: (message: string) => void;\n /** Internal state for troubleshooting (shown with --debug only) */\n debug: (message: string) => void;\n}\n\n/**\n * No-op logger for when no logging is needed.\n * Analogous to noopSpinner in the CLI layer.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst noop = () => {};\nexport const noopLogger: OperationLogger = {\n progress: noop,\n info: noop,\n warn: noop,\n debug: noop,\n};\n","/**\n * Markdown utilities for processing markdown content.\n *\n * Uses gray-matter for parsing and centralized yaml-utils for stringify to ensure\n * proper handling of special YAML characters (colons, quotes, etc.).\n */\n\nimport matter from 'gray-matter';\n\nimport { stringifyYamlCompact } from './yaml-utils.js';\n\nexport interface ParsedMarkdown {\n /** Raw frontmatter string (without --- delimiters), or null if no frontmatter */\n frontmatter: string | null;\n /** Body content after frontmatter, with leading newlines trimmed */\n body: string;\n}\n\n/**\n * Normalize line endings to LF.\n */\nexport function normalizeLineEndings(content: string): string {\n return content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n}\n\n/**\n * Parse markdown content into frontmatter and body.\n * Handles both LF and CRLF line endings.\n *\n * @returns Object with frontmatter (null if none) and body\n */\nexport function parseMarkdown(content: string): ParsedMarkdown {\n const normalized = normalizeLineEndings(content);\n\n if (!matter.test(normalized)) {\n return { frontmatter: null, body: content };\n }\n\n try {\n const parsed = matter(normalized);\n\n // Extract frontmatter from parsed.data by stringifying back to YAML\n // The matter property is unreliable, so we reconstruct from data\n const data = parsed.data;\n let frontmatter: string | null = null;\n\n if (data && Object.keys(data).length > 0) {\n // Use centralized yaml-utils for proper handling of special characters\n // (colons, quotes, multiline strings, etc.)\n frontmatter = stringifyYamlCompact(data).trimEnd();\n } else {\n // Empty frontmatter (just --- followed by ---)\n frontmatter = '';\n }\n\n // Body with leading newlines trimmed\n const body = parsed.content.replace(/^\\n+/, '');\n\n return { frontmatter, body };\n } catch {\n // Invalid/unclosed frontmatter - treat as no frontmatter\n return { frontmatter: null, body: content };\n }\n}\n\n/**\n * Parse YAML frontmatter from markdown content.\n * Returns the frontmatter content (without delimiters) or null if no valid frontmatter.\n * Handles both LF and CRLF line endings.\n */\nexport function parseFrontmatter(content: string): string | null {\n return parseMarkdown(content).frontmatter;\n}\n\n/**\n * Strip YAML frontmatter from markdown content.\n * Returns the body content without frontmatter, with leading newlines trimmed.\n * Handles both LF and CRLF line endings.\n */\nexport function stripFrontmatter(content: string): string {\n return parseMarkdown(content).body;\n}\n\n/**\n * Insert content after YAML frontmatter.\n * If no frontmatter exists, prepends the content.\n * Content is inserted directly after ---. Include leading newlines in toInsert if needed.\n */\nexport function insertAfterFrontmatter(content: string, toInsert: string): string {\n const { frontmatter, body } = parseMarkdown(content);\n\n if (frontmatter === null) {\n return toInsert + content;\n }\n\n const frontmatterBlock = frontmatter ? `---\\n${frontmatter}\\n---` : '---\\n---';\n return `${frontmatterBlock}\\n${toInsert}\\n\\n${body}`;\n}\n","/**\n * YAML front matter parser and serializer for issue files.\n *\n * Issues are stored as Markdown files with YAML front matter:\n * ---\n * type: is\n * id: is-a1b2c3\n * ...\n * ---\n *\n * Description body here.\n *\n * ## Notes\n *\n * Working notes here.\n *\n * See: tbd-design.md §2.1 Markdown + YAML Front Matter Format\n */\n\nimport matter from 'gray-matter';\nimport { parse as parseYaml } from 'yaml';\n\nimport { normalizeLineEndings } from '../utils/markdown-utils.js';\nimport { sortKeys, stringifyYaml } from '../utils/yaml-utils.js';\nimport type { Issue } from '../lib/types.js';\nimport { IssueSchema, ISSUE_FIELD_ORDER } from '../lib/schemas.js';\n\n/**\n * gray-matter options using the 'yaml' package as engine.\n * This preserves date strings instead of converting them to Date objects.\n */\nexport const matterOptions = {\n engines: {\n yaml: {\n parse: (str: string): object => parseYaml(str) as object,\n stringify: (obj: object): string => stringifyYaml(obj),\n },\n },\n};\n\n/**\n * Parsed issue file content.\n */\nexport interface ParsedIssueFile {\n frontmatter: Record<string, unknown>;\n description: string;\n notes: string;\n}\n\n/**\n * Parse a Markdown file with YAML front matter.\n * Uses gray-matter for consistent frontmatter parsing.\n * Handles both LF and CRLF line endings.\n */\nexport function parseMarkdownWithFrontmatter(content: string): ParsedIssueFile {\n // Normalize CRLF to LF before parsing\n const normalizedContent = normalizeLineEndings(content);\n\n // Check for valid frontmatter\n if (!matter.test(normalizedContent)) {\n throw new Error('Invalid format: missing front matter opening delimiter');\n }\n\n const parsed = matter(normalizedContent, matterOptions);\n\n // gray-matter returns empty object if no closing delimiter found\n // but the raw matter string will be empty if parsing failed\n if (parsed.matter === '' && !normalizedContent.includes('---\\n---')) {\n // Check if there's actually a closing delimiter\n const lines = normalizedContent.split('\\n');\n let hasClosing = false;\n for (let i = 1; i < lines.length; i++) {\n if (lines[i]?.trim() === '---') {\n hasClosing = true;\n break;\n }\n }\n if (!hasClosing) {\n throw new Error('Invalid format: missing front matter closing delimiter');\n }\n }\n\n const frontmatter = parsed.data as Record<string, unknown>;\n\n // Parse body - split into description and notes\n const body = parsed.content.trim();\n\n // Find notes section\n const notesMatch = /\\n## Notes\\n/i.exec(body);\n let description = body;\n let notes = '';\n\n if (notesMatch?.index !== undefined) {\n description = body.slice(0, notesMatch.index).trim();\n notes = body.slice(notesMatch.index + notesMatch[0].length).trim();\n }\n\n return { frontmatter, description, notes };\n}\n\n/**\n * Parse an issue from Markdown file content.\n */\nexport function parseIssue(content: string): Issue {\n const { frontmatter, description, notes } = parseMarkdownWithFrontmatter(content);\n\n // Merge body content into frontmatter\n const data = {\n ...frontmatter,\n description: description || undefined,\n notes: notes || undefined,\n };\n\n // Validate and parse with Zod\n return IssueSchema.parse(data);\n}\n\n/**\n * Serialize an issue to Markdown file content.\n * Uses canonical serialization for deterministic output.\n */\nexport function serializeIssue(issue: Issue): string {\n // Extract body fields\n const { description, notes, ...metadata } = issue;\n\n // Sort keys using canonical field order (not alphabetical)\n const sortedMetadata = sortKeys(metadata, ISSUE_FIELD_ORDER);\n\n // Serialize YAML with compact output for frontmatter.\n // sortMapEntries: false preserves our manual ordering.\n const yaml = stringifyYaml(sortedMetadata, {\n lineWidth: 0,\n nullStr: 'null',\n sortMapEntries: false,\n });\n\n // Build the file content\n // Note: No blank line between closing --- and body content\n const parts = ['---', yaml.trim(), '---'];\n\n if (description) {\n parts.push(description.trim());\n }\n\n if (notes) {\n parts.push('');\n parts.push('## Notes');\n parts.push('');\n parts.push(notes.trim());\n }\n\n // Single newline at end\n return parts.join('\\n') + '\\n';\n}\n","/**\n * tbd: Git-native issue tracking for AI agents and humans\n *\n * This is the library entry point. All exports here should be node-free\n * to support browser/edge runtime usage. CLI-specific code is in ./cli/.\n */\n\n// Version injected at build time\ndeclare const __TBD_VERSION__: string;\n\n/**\n * Package version, derived from git at build time.\n * Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.\n */\nexport const VERSION: string =\n typeof __TBD_VERSION__ !== 'undefined' ? __TBD_VERSION__ : 'development';\n\n// Re-export schemas for library consumers\nexport * from './lib/schemas.js';\nexport * from './lib/types.js';\n\n// Re-export core operations (these should be node-free)\nexport { parseIssue, serializeIssue } from './file/parser.js';\n"],"mappings":"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"}
|