@cliangdev/flux-plugin 0.0.0-dev.cbdf207 → 0.0.0-dev.df3e9bb

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 (86) hide show
  1. package/README.md +8 -4
  2. package/bin/install.cjs +150 -16
  3. package/package.json +7 -11
  4. package/src/__tests__/version.test.ts +37 -0
  5. package/src/adapters/local/.gitkeep +0 -0
  6. package/src/server/__tests__/config.test.ts +163 -0
  7. package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
  8. package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
  9. package/src/server/adapters/__tests__/dependency-ops.test.ts +395 -0
  10. package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
  11. package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
  12. package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
  13. package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
  14. package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
  15. package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
  16. package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
  17. package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
  18. package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
  19. package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
  20. package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
  21. package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
  22. package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
  23. package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
  24. package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
  25. package/src/server/adapters/factory.ts +90 -0
  26. package/src/server/adapters/index.ts +9 -0
  27. package/src/server/adapters/linear/adapter.ts +1136 -0
  28. package/src/server/adapters/linear/client.ts +169 -0
  29. package/src/server/adapters/linear/config.ts +152 -0
  30. package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
  31. package/src/server/adapters/linear/helpers/index.ts +7 -0
  32. package/src/server/adapters/linear/index.ts +16 -0
  33. package/src/server/adapters/linear/mappers/description.ts +136 -0
  34. package/src/server/adapters/linear/mappers/epic.ts +81 -0
  35. package/src/server/adapters/linear/mappers/index.ts +27 -0
  36. package/src/server/adapters/linear/mappers/prd.ts +178 -0
  37. package/src/server/adapters/linear/mappers/task.ts +82 -0
  38. package/src/server/adapters/linear/types.ts +264 -0
  39. package/src/server/adapters/local-adapter.ts +968 -0
  40. package/src/server/adapters/types.ts +293 -0
  41. package/src/server/config.ts +73 -0
  42. package/src/server/db/__tests__/queries.test.ts +472 -0
  43. package/src/server/db/ids.ts +17 -0
  44. package/src/server/db/index.ts +69 -0
  45. package/src/server/db/queries.ts +142 -0
  46. package/src/server/db/refs.ts +60 -0
  47. package/src/server/db/schema.ts +88 -0
  48. package/src/server/db/sqlite.ts +10 -0
  49. package/src/server/index.ts +83 -0
  50. package/src/server/tools/__tests__/crud.test.ts +301 -0
  51. package/src/server/tools/__tests__/get-version.test.ts +27 -0
  52. package/src/server/tools/__tests__/mcp-interface.test.ts +388 -0
  53. package/src/server/tools/__tests__/query.test.ts +353 -0
  54. package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
  55. package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
  56. package/src/server/tools/configure-linear.ts +373 -0
  57. package/src/server/tools/create-epic.ts +35 -0
  58. package/src/server/tools/create-prd.ts +31 -0
  59. package/src/server/tools/create-task.ts +38 -0
  60. package/src/server/tools/criteria.ts +50 -0
  61. package/src/server/tools/delete-entity.ts +76 -0
  62. package/src/server/tools/dependencies.ts +55 -0
  63. package/src/server/tools/get-entity.ts +238 -0
  64. package/src/server/tools/get-linear-url.ts +28 -0
  65. package/src/server/tools/get-project-context.ts +33 -0
  66. package/src/server/tools/get-stats.ts +52 -0
  67. package/src/server/tools/get-version.ts +20 -0
  68. package/src/server/tools/index.ts +114 -0
  69. package/src/server/tools/init-project.ts +108 -0
  70. package/src/server/tools/query-entities.ts +167 -0
  71. package/src/server/tools/render-status.ts +201 -0
  72. package/src/server/tools/update-entity.ts +140 -0
  73. package/src/server/tools/update-status.ts +166 -0
  74. package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
  75. package/src/server/utils/logger.ts +9 -0
  76. package/src/server/utils/mcp-response.ts +254 -0
  77. package/src/server/utils/status-transitions.ts +160 -0
  78. package/src/status-line/__tests__/status-line.test.ts +215 -0
  79. package/src/status-line/index.ts +147 -0
  80. package/src/utils/__tests__/chalk-import.test.ts +32 -0
  81. package/src/utils/__tests__/display.test.ts +97 -0
  82. package/src/utils/__tests__/status-renderer.test.ts +310 -0
  83. package/src/utils/display.ts +62 -0
  84. package/src/utils/status-renderer.ts +188 -0
  85. package/src/version.ts +5 -0
  86. package/dist/server/index.js +0 -87063
@@ -0,0 +1,287 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ addCriterionToDescription,
4
+ generateCriteriaId,
5
+ parseCriteriaFromDescription,
6
+ updateCriterionInDescription,
7
+ } from "../linear/helpers/criteria-parser.js";
8
+
9
+ describe("criteria-parser", () => {
10
+ describe("generateCriteriaId", () => {
11
+ test("generates stable hash-based IDs", () => {
12
+ const id1 = generateCriteriaId("User can sign in");
13
+ const id2 = generateCriteriaId("User can sign in");
14
+ expect(id1).toBe(id2);
15
+ });
16
+
17
+ test("generates different IDs for different text", () => {
18
+ const id1 = generateCriteriaId("User can sign in");
19
+ const id2 = generateCriteriaId("User can sign out");
20
+ expect(id1).not.toBe(id2);
21
+ });
22
+
23
+ test("ID format is ac_ prefix with 8 hex chars", () => {
24
+ const id = generateCriteriaId("Test criterion");
25
+ expect(id).toMatch(/^ac_[a-f0-9]{8}$/);
26
+ });
27
+
28
+ test("trims whitespace for consistent IDs", () => {
29
+ const id1 = generateCriteriaId(" Test ");
30
+ const id2 = generateCriteriaId("Test");
31
+ expect(id1).toBe(id2);
32
+ });
33
+ });
34
+
35
+ describe("parseCriteriaFromDescription", () => {
36
+ test("returns empty array for undefined description", () => {
37
+ const result = parseCriteriaFromDescription(undefined);
38
+ expect(result).toEqual([]);
39
+ });
40
+
41
+ test("returns empty array for empty description", () => {
42
+ const result = parseCriteriaFromDescription("");
43
+ expect(result).toEqual([]);
44
+ });
45
+
46
+ test("parses unchecked checkboxes with dash", () => {
47
+ const description = "- [ ] First item\n- [ ] Second item";
48
+ const result = parseCriteriaFromDescription(description);
49
+ expect(result).toHaveLength(2);
50
+ expect(result[0].text).toBe("First item");
51
+ expect(result[0].isMet).toBe(false);
52
+ expect(result[1].text).toBe("Second item");
53
+ expect(result[1].isMet).toBe(false);
54
+ });
55
+
56
+ test("parses checked checkboxes with lowercase x", () => {
57
+ const description = "- [x] Completed item";
58
+ const result = parseCriteriaFromDescription(description);
59
+ expect(result).toHaveLength(1);
60
+ expect(result[0].text).toBe("Completed item");
61
+ expect(result[0].isMet).toBe(true);
62
+ });
63
+
64
+ test("parses checked checkboxes with uppercase X", () => {
65
+ const description = "- [X] Completed item";
66
+ const result = parseCriteriaFromDescription(description);
67
+ expect(result).toHaveLength(1);
68
+ expect(result[0].isMet).toBe(true);
69
+ });
70
+
71
+ test("parses checkboxes with asterisk", () => {
72
+ const description = "* [ ] Item with asterisk";
73
+ const result = parseCriteriaFromDescription(description);
74
+ expect(result).toHaveLength(1);
75
+ expect(result[0].text).toBe("Item with asterisk");
76
+ });
77
+
78
+ test("handles indented checkboxes", () => {
79
+ const description = " - [ ] Indented item";
80
+ const result = parseCriteriaFromDescription(description);
81
+ expect(result).toHaveLength(1);
82
+ expect(result[0].text).toBe("Indented item");
83
+ });
84
+
85
+ test("parses only AC section when header is present", () => {
86
+ const description = `# Overview
87
+ Some overview text
88
+
89
+ - [ ] Not a criterion
90
+
91
+ ## Acceptance Criteria
92
+
93
+ - [ ] Real criterion 1
94
+ - [x] Real criterion 2
95
+
96
+ ## Notes
97
+ - [ ] Not a criterion either`;
98
+
99
+ const result = parseCriteriaFromDescription(description);
100
+ expect(result).toHaveLength(2);
101
+ expect(result[0].text).toBe("Real criterion 1");
102
+ expect(result[1].text).toBe("Real criterion 2");
103
+ });
104
+
105
+ test("parses AC section with bold header", () => {
106
+ const description = `**Acceptance Criteria**
107
+
108
+ - [ ] Criterion 1
109
+ - [ ] Criterion 2
110
+
111
+ **Notes**
112
+ - [ ] Not a criterion`;
113
+
114
+ const result = parseCriteriaFromDescription(description);
115
+ expect(result).toHaveLength(2);
116
+ });
117
+
118
+ test("includes line index for updates", () => {
119
+ const description = "Header\n\n- [ ] Item 1\n- [ ] Item 2";
120
+ const result = parseCriteriaFromDescription(description);
121
+ expect(result[0].lineIndex).toBe(2);
122
+ expect(result[1].lineIndex).toBe(3);
123
+ });
124
+
125
+ test("generates correct IDs for criteria", () => {
126
+ const description = "- [ ] Test criterion";
127
+ const result = parseCriteriaFromDescription(description);
128
+ const expectedId = generateCriteriaId("Test criterion");
129
+ expect(result[0].id).toBe(expectedId);
130
+ });
131
+ });
132
+
133
+ describe("updateCriterionInDescription", () => {
134
+ test("marks criterion as met", () => {
135
+ const description = "- [ ] First item\n- [ ] Second item";
136
+ const criteriaId = generateCriteriaId("First item");
137
+ const result = updateCriterionInDescription(
138
+ description,
139
+ criteriaId,
140
+ true,
141
+ );
142
+ expect(result).toBe("- [x] First item\n- [ ] Second item");
143
+ });
144
+
145
+ test("marks criterion as unmet", () => {
146
+ const description = "- [x] First item\n- [ ] Second item";
147
+ const criteriaId = generateCriteriaId("First item");
148
+ const result = updateCriterionInDescription(
149
+ description,
150
+ criteriaId,
151
+ false,
152
+ );
153
+ expect(result).toBe("- [ ] First item\n- [ ] Second item");
154
+ });
155
+
156
+ test("preserves indentation", () => {
157
+ const description = " - [ ] Indented item";
158
+ const criteriaId = generateCriteriaId("Indented item");
159
+ const result = updateCriterionInDescription(
160
+ description,
161
+ criteriaId,
162
+ true,
163
+ );
164
+ expect(result).toBe(" - [x] Indented item");
165
+ });
166
+
167
+ test("throws for non-existent criterion", () => {
168
+ const description = "- [ ] Item";
169
+ expect(() =>
170
+ updateCriterionInDescription(description, "ac_nonexist", true),
171
+ ).toThrow("Criterion not found");
172
+ });
173
+
174
+ test("updates correct criterion when multiple exist", () => {
175
+ const description = "- [ ] First\n- [ ] Second\n- [ ] Third";
176
+ const criteriaId = generateCriteriaId("Second");
177
+ const result = updateCriterionInDescription(
178
+ description,
179
+ criteriaId,
180
+ true,
181
+ );
182
+ expect(result).toBe("- [ ] First\n- [x] Second\n- [ ] Third");
183
+ });
184
+ });
185
+
186
+ describe("addCriterionToDescription", () => {
187
+ test("creates AC section for undefined description", () => {
188
+ const result = addCriterionToDescription(undefined, "New criterion");
189
+ expect(result).toBe("## Acceptance Criteria\n\n- [ ] New criterion");
190
+ });
191
+
192
+ test("creates AC section for empty description", () => {
193
+ const result = addCriterionToDescription("", "New criterion");
194
+ expect(result).toContain("## Acceptance Criteria");
195
+ expect(result).toContain("- [ ] New criterion");
196
+ });
197
+
198
+ test("adds to existing AC section", () => {
199
+ const description = `## Acceptance Criteria
200
+
201
+ - [ ] Existing criterion`;
202
+
203
+ const result = addCriterionToDescription(description, "New criterion");
204
+ expect(result).toContain("- [ ] Existing criterion");
205
+ expect(result).toContain("- [ ] New criterion");
206
+ });
207
+
208
+ test("inserts after last checkbox in AC section", () => {
209
+ const description = `## Acceptance Criteria
210
+
211
+ - [ ] First
212
+ - [ ] Second
213
+
214
+ ## Notes
215
+ Some notes`;
216
+
217
+ const result = addCriterionToDescription(description, "Third");
218
+ const lines = result.split("\n");
219
+ const thirdIndex = lines.findIndex((l) => l.includes("Third"));
220
+ const notesIndex = lines.findIndex((l) => l.includes("## Notes"));
221
+ expect(thirdIndex).toBeLessThan(notesIndex);
222
+ });
223
+
224
+ test("preserves description content before AC section", () => {
225
+ const description = `# Overview
226
+
227
+ This is an overview.
228
+
229
+ ## Acceptance Criteria
230
+
231
+ - [ ] Existing`;
232
+
233
+ const result = addCriterionToDescription(description, "New");
234
+ expect(result).toContain("# Overview");
235
+ expect(result).toContain("This is an overview.");
236
+ });
237
+
238
+ test("creates AC section at end if none exists", () => {
239
+ const description = `# Overview
240
+
241
+ Some content here.`;
242
+
243
+ const result = addCriterionToDescription(description, "New criterion");
244
+ expect(result).toContain("# Overview");
245
+ expect(result).toContain("Some content here.");
246
+ expect(result).toContain("## Acceptance Criteria");
247
+ expect(result).toContain("- [ ] New criterion");
248
+ });
249
+ });
250
+
251
+ describe("integration: parse, update, parse cycle", () => {
252
+ test("round-trip preserves structure", () => {
253
+ const original = `## Acceptance Criteria
254
+
255
+ - [ ] User can login
256
+ - [ ] User can logout
257
+ - [ ] Session persists`;
258
+
259
+ const parsed = parseCriteriaFromDescription(original);
260
+ expect(parsed).toHaveLength(3);
261
+
262
+ const updated = updateCriterionInDescription(
263
+ original,
264
+ parsed[1].id,
265
+ true,
266
+ );
267
+ const reParsed = parseCriteriaFromDescription(updated);
268
+
269
+ expect(reParsed).toHaveLength(3);
270
+ expect(reParsed[0].isMet).toBe(false);
271
+ expect(reParsed[1].isMet).toBe(true);
272
+ expect(reParsed[2].isMet).toBe(false);
273
+ });
274
+
275
+ test("add and parse cycle", () => {
276
+ let description = "## Overview\n\nSome text";
277
+
278
+ description = addCriterionToDescription(description, "First");
279
+ description = addCriterionToDescription(description, "Second");
280
+
281
+ const parsed = parseCriteriaFromDescription(description);
282
+ expect(parsed).toHaveLength(2);
283
+ expect(parsed.map((c) => c.text)).toContain("First");
284
+ expect(parsed.map((c) => c.text)).toContain("Second");
285
+ });
286
+ });
287
+ });
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Linear Description Validation Test
3
+ *
4
+ * This test verifies that Linear issue descriptions can handle PRD-equivalent content
5
+ * including rich markdown, images, and diagrams. Run this test manually against a
6
+ * real Linear instance before proceeding with the refactor.
7
+ *
8
+ * Usage:
9
+ * FLUX_LINEAR_API_KEY=lin_api_xxx FLUX_LINEAR_TEAM_ID=xxx bun run test:linear-description
10
+ *
11
+ * Note: This is an integration test that requires real Linear credentials.
12
+ * It creates and then archives a test issue.
13
+ */
14
+
15
+ import { LinearClient } from "@linear/sdk";
16
+
17
+ /**
18
+ * Sample PRD content with rich markdown formatting.
19
+ * This simulates what would be stored in a PRD issue description.
20
+ */
21
+ const SAMPLE_PRD_CONTENT = `# Feature: User Authentication
22
+
23
+ ## Overview
24
+ Implement OAuth2 login with Google and GitHub providers. This feature enables users to sign in without creating a new password and provides a seamless authentication experience.
25
+
26
+ ## User Stories
27
+ - As a user, I want to sign in with Google so I don't need another password
28
+ - As a user, I want to link multiple auth providers to one account
29
+ - As a user, I want to see which providers are linked to my account
30
+ - As a security-conscious user, I want to unlink providers I no longer use
31
+
32
+ ## Requirements
33
+
34
+ ### Functional Requirements
35
+ 1. **Google OAuth Integration**
36
+ - Support Google OAuth 2.0 flow
37
+ - Request minimal scopes (email, profile)
38
+ - Handle token refresh automatically
39
+
40
+ 2. **GitHub OAuth Integration**
41
+ - Support GitHub OAuth flow
42
+ - Request read:user scope
43
+ - Handle organization restrictions
44
+
45
+ 3. **Account Linking**
46
+ - Allow linking multiple providers to one account
47
+ - Prevent duplicate email conflicts
48
+ - Support unlinking providers (min 1 must remain)
49
+
50
+ ### Non-Functional Requirements
51
+ - Authentication response time < 2 seconds
52
+ - Support 1000 concurrent auth requests
53
+ - 99.9% uptime for auth service
54
+
55
+ ## Wireframe
56
+ ![Login Screen](https://via.placeholder.com/400x300?text=Login+Wireframe)
57
+
58
+ ## Architecture
59
+
60
+ \`\`\`mermaid
61
+ graph LR
62
+ A[Client] --> B[Auth Service]
63
+ B --> C[Google OAuth]
64
+ B --> D[GitHub OAuth]
65
+ B --> E[Session Store]
66
+ E --> F[(Redis)]
67
+ B --> G[(PostgreSQL)]
68
+ \`\`\`
69
+
70
+ ## API Design
71
+
72
+ ### POST /auth/google
73
+ \`\`\`json
74
+ {
75
+ "code": "google_auth_code",
76
+ "redirect_uri": "https://app.example.com/callback"
77
+ }
78
+ \`\`\`
79
+
80
+ ### POST /auth/github
81
+ \`\`\`json
82
+ {
83
+ "code": "github_auth_code",
84
+ "redirect_uri": "https://app.example.com/callback"
85
+ }
86
+ \`\`\`
87
+
88
+ ## Acceptance Criteria
89
+ - [ ] Google OAuth integration working
90
+ - [ ] GitHub OAuth integration working
91
+ - [ ] Session management implemented
92
+ - [ ] Account linking/unlinking functional
93
+ - [ ] Error handling for OAuth failures
94
+ - [ ] Rate limiting on auth endpoints
95
+ - [ ] Audit logging for auth events
96
+
97
+ ## Technical Notes
98
+ - Use \`passport.js\` for OAuth abstraction
99
+ - Store refresh tokens encrypted with AES-256
100
+ - Implement PKCE for mobile clients
101
+
102
+ ## Dependencies
103
+ - passport: ^0.7.0
104
+ - passport-google-oauth20: ^2.0.0
105
+ - passport-github2: ^0.1.12
106
+ - ioredis: ^5.3.0
107
+ `;
108
+
109
+ /**
110
+ * Run the validation test.
111
+ * This creates an issue, verifies content, and archives it.
112
+ */
113
+ async function runValidationTest() {
114
+ const apiKey = process.env.FLUX_LINEAR_API_KEY;
115
+ const teamId = process.env.FLUX_LINEAR_TEAM_ID;
116
+
117
+ if (!apiKey || !teamId) {
118
+ console.error("Missing required environment variables:");
119
+ console.error(" FLUX_LINEAR_API_KEY - Your Linear API key (lin_api_...)");
120
+ console.error(" FLUX_LINEAR_TEAM_ID - Your Linear team ID");
121
+ console.error("");
122
+ console.error("Usage:");
123
+ console.error(
124
+ " FLUX_LINEAR_API_KEY=lin_api_xxx FLUX_LINEAR_TEAM_ID=xxx bun run test:linear-description",
125
+ );
126
+ process.exit(1);
127
+ }
128
+
129
+ const client = new LinearClient({ apiKey });
130
+
131
+ console.log("=== Linear Description Validation Test ===\n");
132
+
133
+ // 1. Create test issue with rich markdown content
134
+ console.log("1. Creating test issue with PRD-equivalent content...");
135
+ console.log(` Content length: ${SAMPLE_PRD_CONTENT.length} characters`);
136
+
137
+ const createResult = await client.createIssue({
138
+ title: "[TEST] PRD Description Validation - Can Delete",
139
+ description: SAMPLE_PRD_CONTENT,
140
+ teamId,
141
+ });
142
+
143
+ if (!createResult.success) {
144
+ console.error(" ❌ Failed to create issue");
145
+ process.exit(1);
146
+ }
147
+
148
+ const issue = await createResult.issue;
149
+ if (!issue) {
150
+ console.error(" ❌ Issue not returned after creation");
151
+ process.exit(1);
152
+ }
153
+
154
+ console.log(` ✅ Issue created: ${issue.identifier}`);
155
+ console.log(` URL: ${issue.url}`);
156
+
157
+ // 2. Fetch the issue back and verify content
158
+ console.log("\n2. Fetching issue to verify content preservation...");
159
+
160
+ const fetchedIssue = await client.issue(issue.id);
161
+ if (!fetchedIssue) {
162
+ console.error(" ❌ Failed to fetch issue");
163
+ process.exit(1);
164
+ }
165
+
166
+ const description = fetchedIssue.description || "";
167
+ console.log(
168
+ ` Retrieved description length: ${description.length} characters`,
169
+ );
170
+
171
+ // Verify key sections are present
172
+ const requiredSections = [
173
+ "# Feature: User Authentication",
174
+ "## Overview",
175
+ "## User Stories",
176
+ "## Requirements",
177
+ "## Wireframe",
178
+ "## Architecture",
179
+ "```mermaid",
180
+ "## API Design",
181
+ "## Acceptance Criteria",
182
+ "- [ ] Google OAuth integration working",
183
+ "## Technical Notes",
184
+ "## Dependencies",
185
+ ];
186
+
187
+ let allSectionsFound = true;
188
+ for (const section of requiredSections) {
189
+ if (description.includes(section)) {
190
+ console.log(` ✅ Found: "${section.substring(0, 40)}..."`);
191
+ } else {
192
+ console.log(` ❌ Missing: "${section.substring(0, 40)}..."`);
193
+ allSectionsFound = false;
194
+ }
195
+ }
196
+
197
+ // 3. Check image URL preservation
198
+ console.log("\n3. Checking image URL preservation...");
199
+ const imageUrl = "https://via.placeholder.com/400x300?text=Login+Wireframe";
200
+ if (description.includes(imageUrl)) {
201
+ console.log(` ✅ Image URL preserved: ${imageUrl}`);
202
+ } else {
203
+ console.log(` ⚠️ Image URL may have been processed/modified by Linear`);
204
+ }
205
+
206
+ // 4. Archive the test issue (cleanup)
207
+ console.log("\n4. Archiving test issue (cleanup)...");
208
+ const archiveResult = await fetchedIssue.archive();
209
+ if (archiveResult.success) {
210
+ console.log(" ✅ Test issue archived successfully");
211
+ } else {
212
+ console.log(" ⚠️ Failed to archive test issue - please delete manually");
213
+ console.log(` Issue identifier: ${issue.identifier}`);
214
+ }
215
+
216
+ // Summary
217
+ console.log("\n=== Validation Summary ===");
218
+ if (allSectionsFound) {
219
+ console.log("✅ SUCCESS: Linear issue descriptions can handle PRD content");
220
+ console.log(" - All markdown sections preserved");
221
+ console.log(" - Content length is acceptable");
222
+ console.log(" - Ready to proceed with refactor");
223
+ } else {
224
+ console.log("⚠️ PARTIAL: Some content may be modified by Linear");
225
+ console.log(" - Check the issue in Linear UI to verify rendering");
226
+ console.log(" - Some markdown features may not be fully supported");
227
+ }
228
+
229
+ console.log(
230
+ `\nNote: Check the issue in Linear UI to verify images render correctly: ${issue.url}`,
231
+ );
232
+ }
233
+
234
+ // Run if executed directly
235
+ runValidationTest().catch((error) => {
236
+ console.error("Test failed with error:", error);
237
+ process.exit(1);
238
+ });