opencode-plugin-team-agreements 0.1.4 → 0.2.1
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/CHANGELOG.md +23 -0
- package/README.md +93 -21
- package/dist/index.d.ts +15 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -519
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +431 -62
- package/dist/index.test.js.map +1 -1
- package/dist/utils.d.ts +151 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +1613 -0
- package/dist/utils.js.map +1 -0
- package/package.json +1 -1
package/dist/index.test.js
CHANGED
|
@@ -2,7 +2,8 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
|
2
2
|
import { mkdir, writeFile, rm } from "fs/promises";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { tmpdir } from "os";
|
|
5
|
-
import {
|
|
5
|
+
import { TeamAgreementsPlugin } from "./index.js";
|
|
6
|
+
import { fileExists, loadTeamAgreements, formatQuestionsAsMarkdown, buildTopicIssueBody, detectEnforcementMechanisms, formatEnforcementResults, analyzeProject, formatProjectAnalysis, COMMAND_TEMPLATE, PLUGIN_REPO, } from "./utils.js";
|
|
6
7
|
describe("fileExists", () => {
|
|
7
8
|
let testDir;
|
|
8
9
|
beforeEach(async () => {
|
|
@@ -29,34 +30,42 @@ describe("loadTeamAgreements", () => {
|
|
|
29
30
|
let testDir;
|
|
30
31
|
beforeEach(async () => {
|
|
31
32
|
testDir = join(tmpdir(), "team-agreements-test-" + Date.now() + "-" + Math.random().toString(36).slice(2));
|
|
32
|
-
await mkdir(
|
|
33
|
+
await mkdir(testDir, { recursive: true });
|
|
33
34
|
});
|
|
34
35
|
afterEach(async () => {
|
|
35
36
|
await rm(testDir, { recursive: true, force: true });
|
|
36
37
|
});
|
|
37
|
-
it("returns formatted content when
|
|
38
|
+
it("returns formatted content when AGENTS.md exists", async () => {
|
|
38
39
|
const agreementsContent = "# My Team Agreements\n\nWe agree to be awesome.";
|
|
39
|
-
await writeFile(join(testDir, "
|
|
40
|
+
await writeFile(join(testDir, "AGENTS.md"), agreementsContent);
|
|
40
41
|
const result = await loadTeamAgreements(testDir);
|
|
41
42
|
expect(result).not.toBeNull();
|
|
42
43
|
expect(result).toContain("## Team Agreements");
|
|
44
|
+
expect(result).toContain("from AGENTS.md");
|
|
43
45
|
expect(result).toContain("The following team agreements are in effect");
|
|
44
46
|
expect(result).toContain(agreementsContent);
|
|
45
47
|
});
|
|
46
|
-
it("returns
|
|
48
|
+
it("returns formatted content from CLAUDE.md when AGENTS.md does not exist", async () => {
|
|
49
|
+
const agreementsContent = "# Claude Rules\n\nBe helpful.";
|
|
50
|
+
await writeFile(join(testDir, "CLAUDE.md"), agreementsContent);
|
|
47
51
|
const result = await loadTeamAgreements(testDir);
|
|
48
|
-
expect(result).toBeNull();
|
|
52
|
+
expect(result).not.toBeNull();
|
|
53
|
+
expect(result).toContain("## Team Agreements");
|
|
54
|
+
expect(result).toContain("from CLAUDE.md");
|
|
55
|
+
expect(result).toContain(agreementsContent);
|
|
56
|
+
});
|
|
57
|
+
it("prefers AGENTS.md over CLAUDE.md when both exist", async () => {
|
|
58
|
+
await writeFile(join(testDir, "AGENTS.md"), "# AGENTS content");
|
|
59
|
+
await writeFile(join(testDir, "CLAUDE.md"), "# CLAUDE content");
|
|
60
|
+
const result = await loadTeamAgreements(testDir);
|
|
61
|
+
expect(result).not.toBeNull();
|
|
62
|
+
expect(result).toContain("from AGENTS.md");
|
|
63
|
+
expect(result).toContain("# AGENTS content");
|
|
64
|
+
expect(result).not.toContain("# CLAUDE content");
|
|
49
65
|
});
|
|
50
|
-
it("returns null when
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const result = await loadTeamAgreements(emptyDir);
|
|
55
|
-
expect(result).toBeNull();
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
await rm(emptyDir, { recursive: true, force: true });
|
|
59
|
-
}
|
|
66
|
+
it("returns null when neither file exists", async () => {
|
|
67
|
+
const result = await loadTeamAgreements(testDir);
|
|
68
|
+
expect(result).toBeNull();
|
|
60
69
|
});
|
|
61
70
|
});
|
|
62
71
|
describe("formatQuestionsAsMarkdown", () => {
|
|
@@ -108,25 +117,66 @@ describe("buildTopicIssueBody", () => {
|
|
|
108
117
|
describe("COMMAND_TEMPLATE", () => {
|
|
109
118
|
it("contains required sections", () => {
|
|
110
119
|
expect(COMMAND_TEMPLATE).toContain("$ARGUMENTS");
|
|
111
|
-
expect(COMMAND_TEMPLATE).toContain("##
|
|
112
|
-
expect(COMMAND_TEMPLATE).toContain("
|
|
120
|
+
expect(COMMAND_TEMPLATE).toContain("## Overview");
|
|
121
|
+
expect(COMMAND_TEMPLATE).toContain("AGENTS.md");
|
|
122
|
+
expect(COMMAND_TEMPLATE).toContain("## Step 1: Analyze Project & Existing Files");
|
|
123
|
+
expect(COMMAND_TEMPLATE).toContain("## Step 2: Determine the Scenario");
|
|
124
|
+
expect(COMMAND_TEMPLATE).toContain("## Step 3: Present Categories Based on Analysis");
|
|
125
|
+
expect(COMMAND_TEMPLATE).toContain("## Step 4: Gather Team Agreements");
|
|
126
|
+
// Categories
|
|
127
|
+
expect(COMMAND_TEMPLATE).toContain("CATEGORY 1: CODE & QUALITY");
|
|
128
|
+
expect(COMMAND_TEMPLATE).toContain("CATEGORY 2: INTEGRATION & DELIVERY");
|
|
129
|
+
expect(COMMAND_TEMPLATE).toContain("CATEGORY 3: OPERATIONS & QA");
|
|
130
|
+
expect(COMMAND_TEMPLATE).toContain("CATEGORY 4: DOCUMENTATION & KNOWLEDGE");
|
|
131
|
+
expect(COMMAND_TEMPLATE).toContain("CATEGORY 5: AI/LLM COLLABORATION");
|
|
132
|
+
expect(COMMAND_TEMPLATE).toContain("CATEGORY 6: TEAM PROCESS");
|
|
133
|
+
expect(COMMAND_TEMPLATE).toContain("CATEGORY 7: GOVERNANCE");
|
|
134
|
+
// Topics
|
|
113
135
|
expect(COMMAND_TEMPLATE).toContain("Programming Languages");
|
|
114
136
|
expect(COMMAND_TEMPLATE).toContain("Code Quality Standards");
|
|
115
|
-
expect(COMMAND_TEMPLATE).toContain("
|
|
116
|
-
expect(COMMAND_TEMPLATE).toContain("Integration Workflow");
|
|
137
|
+
expect(COMMAND_TEMPLATE).toContain("Code Review Process");
|
|
117
138
|
expect(COMMAND_TEMPLATE).toContain("Testing Requirements");
|
|
139
|
+
expect(COMMAND_TEMPLATE).toContain("Version Control & Branching");
|
|
140
|
+
expect(COMMAND_TEMPLATE).toContain("Security Practices");
|
|
141
|
+
expect(COMMAND_TEMPLATE).toContain("AI Tools & Policies");
|
|
142
|
+
expect(COMMAND_TEMPLATE).toContain("Autonomy Boundaries");
|
|
118
143
|
expect(COMMAND_TEMPLATE).toContain("Amendment Process");
|
|
119
144
|
});
|
|
120
145
|
it("mentions the suggestion tool", () => {
|
|
121
146
|
expect(COMMAND_TEMPLATE).toContain("suggest_team_agreement_topic");
|
|
122
147
|
});
|
|
148
|
+
it("mentions the analyze_project tool", () => {
|
|
149
|
+
expect(COMMAND_TEMPLATE).toContain("analyze_project");
|
|
150
|
+
});
|
|
123
151
|
it("contains enforcement section", () => {
|
|
124
152
|
expect(COMMAND_TEMPLATE).toContain("detect_enforcement_mechanisms");
|
|
125
153
|
expect(COMMAND_TEMPLATE).toContain("Enforcement Mechanisms");
|
|
126
|
-
expect(COMMAND_TEMPLATE).toContain("
|
|
127
|
-
expect(COMMAND_TEMPLATE).toContain("CI
|
|
128
|
-
expect(COMMAND_TEMPLATE).toContain("
|
|
129
|
-
|
|
154
|
+
expect(COMMAND_TEMPLATE).toContain("commitlint");
|
|
155
|
+
expect(COMMAND_TEMPLATE).toContain("CI workflows");
|
|
156
|
+
expect(COMMAND_TEMPLATE).toContain("branch protection");
|
|
157
|
+
});
|
|
158
|
+
it("contains merging guidelines section", () => {
|
|
159
|
+
expect(COMMAND_TEMPLATE).toContain("## Step 5: Generate Documents");
|
|
160
|
+
expect(COMMAND_TEMPLATE).toContain("### Merging Guidelines");
|
|
161
|
+
expect(COMMAND_TEMPLATE).toContain("Preserve existing structure");
|
|
162
|
+
expect(COMMAND_TEMPLATE).toContain("Avoid duplication");
|
|
163
|
+
});
|
|
164
|
+
it("contains CLAUDE.md coordination section", () => {
|
|
165
|
+
expect(COMMAND_TEMPLATE).toContain("## Step 6: Handle CLAUDE.md Coordination");
|
|
166
|
+
expect(COMMAND_TEMPLATE).toContain("@AGENTS.md");
|
|
167
|
+
expect(COMMAND_TEMPLATE).toContain("Claude-specific");
|
|
168
|
+
});
|
|
169
|
+
it("contains AI/LLM collaboration topics", () => {
|
|
170
|
+
expect(COMMAND_TEMPLATE).toContain("AI Tools & Policies");
|
|
171
|
+
expect(COMMAND_TEMPLATE).toContain("Autonomy Boundaries");
|
|
172
|
+
expect(COMMAND_TEMPLATE).toContain("AI Code Generation Standards");
|
|
173
|
+
expect(COMMAND_TEMPLATE).toContain("Context & Session Management");
|
|
174
|
+
expect(COMMAND_TEMPLATE).toContain("Human Oversight & Escalation");
|
|
175
|
+
expect(COMMAND_TEMPLATE).toContain("Learning & Improvement");
|
|
176
|
+
});
|
|
177
|
+
it("contains progress tracking guidance", () => {
|
|
178
|
+
expect(COMMAND_TEMPLATE).toContain("Category X of 7");
|
|
179
|
+
expect(COMMAND_TEMPLATE).toContain("25-40 minutes");
|
|
130
180
|
});
|
|
131
181
|
});
|
|
132
182
|
describe("detectEnforcementMechanisms", () => {
|
|
@@ -278,7 +328,7 @@ describe("TeamAgreementsPlugin", () => {
|
|
|
278
328
|
let testDir;
|
|
279
329
|
beforeEach(async () => {
|
|
280
330
|
testDir = join(tmpdir(), "team-agreements-plugin-test-" + Date.now() + "-" + Math.random().toString(36).slice(2));
|
|
281
|
-
await mkdir(
|
|
331
|
+
await mkdir(testDir, { recursive: true });
|
|
282
332
|
});
|
|
283
333
|
afterEach(async () => {
|
|
284
334
|
await rm(testDir, { recursive: true, force: true });
|
|
@@ -300,23 +350,10 @@ describe("TeamAgreementsPlugin", () => {
|
|
|
300
350
|
expect(config.command["team-agreements"].description).toContain("team agreements");
|
|
301
351
|
expect(config.command["team-agreements"].template).toBe(COMMAND_TEMPLATE);
|
|
302
352
|
});
|
|
303
|
-
it("
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
client: {},
|
|
308
|
-
project: {},
|
|
309
|
-
worktree: testDir,
|
|
310
|
-
serverUrl: new URL("http://localhost"),
|
|
311
|
-
$: {},
|
|
312
|
-
};
|
|
313
|
-
const hooks = await TeamAgreementsPlugin(mockCtx);
|
|
314
|
-
const config = {};
|
|
315
|
-
await hooks.config(config);
|
|
316
|
-
expect(config.instructions).toBeDefined();
|
|
317
|
-
expect(config.instructions).toContain("docs/TEAM_AGREEMENTS.md");
|
|
318
|
-
});
|
|
319
|
-
it("does not add instructions when agreements file does not exist", async () => {
|
|
353
|
+
it("does not inject instructions (AGENTS.md is auto-loaded by OpenCode)", async () => {
|
|
354
|
+
// Note: We no longer inject AGENTS.md into instructions because OpenCode
|
|
355
|
+
// automatically loads it from the project root
|
|
356
|
+
await writeFile(join(testDir, "AGENTS.md"), "# Agreements");
|
|
320
357
|
const mockCtx = {
|
|
321
358
|
directory: testDir,
|
|
322
359
|
client: {},
|
|
@@ -328,28 +365,11 @@ describe("TeamAgreementsPlugin", () => {
|
|
|
328
365
|
const hooks = await TeamAgreementsPlugin(mockCtx);
|
|
329
366
|
const config = {};
|
|
330
367
|
await hooks.config(config);
|
|
368
|
+
// Config should NOT have instructions added - AGENTS.md is auto-loaded
|
|
331
369
|
expect(config.instructions).toBeUndefined();
|
|
332
370
|
});
|
|
333
|
-
it("
|
|
334
|
-
await writeFile(join(testDir, "
|
|
335
|
-
const mockCtx = {
|
|
336
|
-
directory: testDir,
|
|
337
|
-
client: {},
|
|
338
|
-
project: {},
|
|
339
|
-
worktree: testDir,
|
|
340
|
-
serverUrl: new URL("http://localhost"),
|
|
341
|
-
$: {},
|
|
342
|
-
};
|
|
343
|
-
const hooks = await TeamAgreementsPlugin(mockCtx);
|
|
344
|
-
const config = {
|
|
345
|
-
instructions: ["docs/TEAM_AGREEMENTS.md", "other-file.md"],
|
|
346
|
-
};
|
|
347
|
-
await hooks.config(config);
|
|
348
|
-
const count = config.instructions.filter((i) => i === "docs/TEAM_AGREEMENTS.md").length;
|
|
349
|
-
expect(count).toBe(1);
|
|
350
|
-
});
|
|
351
|
-
it("provides compaction hook that injects agreements", async () => {
|
|
352
|
-
await writeFile(join(testDir, "docs", "TEAM_AGREEMENTS.md"), "# Test Agreements");
|
|
371
|
+
it("provides compaction hook that injects agreements from AGENTS.md", async () => {
|
|
372
|
+
await writeFile(join(testDir, "AGENTS.md"), "# Test Agreements");
|
|
353
373
|
const mockCtx = {
|
|
354
374
|
directory: testDir,
|
|
355
375
|
client: {},
|
|
@@ -430,5 +450,354 @@ describe("TeamAgreementsPlugin", () => {
|
|
|
430
450
|
expect(result).toContain("Detected Enforcement Mechanisms");
|
|
431
451
|
expect(result).toContain("husky");
|
|
432
452
|
});
|
|
453
|
+
it("registers the analyze_project tool", async () => {
|
|
454
|
+
const mockCtx = {
|
|
455
|
+
directory: testDir,
|
|
456
|
+
client: {},
|
|
457
|
+
project: {},
|
|
458
|
+
worktree: testDir,
|
|
459
|
+
serverUrl: new URL("http://localhost"),
|
|
460
|
+
$: {},
|
|
461
|
+
};
|
|
462
|
+
const hooks = await TeamAgreementsPlugin(mockCtx);
|
|
463
|
+
expect(hooks.tool).toBeDefined();
|
|
464
|
+
expect(hooks.tool.analyze_project).toBeDefined();
|
|
465
|
+
expect(hooks.tool.analyze_project.description).toContain("Analyze the project");
|
|
466
|
+
});
|
|
467
|
+
it("analyze_project tool returns formatted results", async () => {
|
|
468
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
469
|
+
dependencies: { react: "^18.0.0", express: "^4.0.0" },
|
|
470
|
+
devDependencies: { typescript: "^5.0.0", vitest: "^1.0.0" }
|
|
471
|
+
}));
|
|
472
|
+
const mockCtx = {
|
|
473
|
+
directory: testDir,
|
|
474
|
+
client: {},
|
|
475
|
+
project: {},
|
|
476
|
+
worktree: testDir,
|
|
477
|
+
serverUrl: new URL("http://localhost"),
|
|
478
|
+
$: {},
|
|
479
|
+
};
|
|
480
|
+
const mockToolContext = {
|
|
481
|
+
sessionID: "test-session",
|
|
482
|
+
messageID: "test-message",
|
|
483
|
+
agent: "test-agent",
|
|
484
|
+
abort: new AbortController().signal,
|
|
485
|
+
metadata: () => { },
|
|
486
|
+
ask: async () => { },
|
|
487
|
+
};
|
|
488
|
+
const hooks = await TeamAgreementsPlugin(mockCtx);
|
|
489
|
+
const result = await hooks.tool.analyze_project.execute({}, mockToolContext);
|
|
490
|
+
expect(result).toContain("Project Analysis Results");
|
|
491
|
+
expect(result).toContain("Languages");
|
|
492
|
+
expect(result).toContain("Typescript");
|
|
493
|
+
expect(result).toContain("Frameworks");
|
|
494
|
+
expect(result).toContain("React");
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
describe("analyzeProject", () => {
|
|
498
|
+
let testDir;
|
|
499
|
+
beforeEach(async () => {
|
|
500
|
+
testDir = join(tmpdir(), "analyze-project-test-" + Date.now() + "-" + Math.random().toString(36).slice(2));
|
|
501
|
+
await mkdir(testDir, { recursive: true });
|
|
502
|
+
});
|
|
503
|
+
afterEach(async () => {
|
|
504
|
+
await rm(testDir, { recursive: true, force: true });
|
|
505
|
+
});
|
|
506
|
+
it("returns default analysis for empty project", async () => {
|
|
507
|
+
const result = await analyzeProject(testDir);
|
|
508
|
+
expect(result.languages.typescript).toBe(false);
|
|
509
|
+
expect(result.languages.javascript).toBe(false);
|
|
510
|
+
expect(result.frameworks.react).toBe(false);
|
|
511
|
+
expect(result.ci.githubActions).toBe(false);
|
|
512
|
+
expect(result.aiTools.agentsMd).toBe(false);
|
|
513
|
+
expect(result.recommendations.suggestedCategories).toContain("Code & Quality");
|
|
514
|
+
});
|
|
515
|
+
it("detects TypeScript from package.json", async () => {
|
|
516
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
517
|
+
devDependencies: { typescript: "^5.0.0" }
|
|
518
|
+
}));
|
|
519
|
+
const result = await analyzeProject(testDir);
|
|
520
|
+
expect(result.languages.typescript).toBe(true);
|
|
521
|
+
expect(result.languages.javascript).toBe(true);
|
|
522
|
+
});
|
|
523
|
+
it("detects TypeScript from tsconfig.json", async () => {
|
|
524
|
+
await writeFile(join(testDir, "tsconfig.json"), JSON.stringify({
|
|
525
|
+
compilerOptions: { target: "ES2022" }
|
|
526
|
+
}));
|
|
527
|
+
const result = await analyzeProject(testDir);
|
|
528
|
+
expect(result.languages.typescript).toBe(true);
|
|
529
|
+
});
|
|
530
|
+
it("detects Python from pyproject.toml", async () => {
|
|
531
|
+
await writeFile(join(testDir, "pyproject.toml"), "[project]\nname = 'test'");
|
|
532
|
+
const result = await analyzeProject(testDir);
|
|
533
|
+
expect(result.languages.python).toBe(true);
|
|
534
|
+
});
|
|
535
|
+
it("detects Rust from Cargo.toml", async () => {
|
|
536
|
+
await writeFile(join(testDir, "Cargo.toml"), "[package]\nname = 'test'");
|
|
537
|
+
const result = await analyzeProject(testDir);
|
|
538
|
+
expect(result.languages.rust).toBe(true);
|
|
539
|
+
});
|
|
540
|
+
it("detects Go from go.mod", async () => {
|
|
541
|
+
await writeFile(join(testDir, "go.mod"), "module example.com/test");
|
|
542
|
+
const result = await analyzeProject(testDir);
|
|
543
|
+
expect(result.languages.go).toBe(true);
|
|
544
|
+
});
|
|
545
|
+
it("detects React from package.json", async () => {
|
|
546
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
547
|
+
dependencies: { react: "^18.0.0" }
|
|
548
|
+
}));
|
|
549
|
+
const result = await analyzeProject(testDir);
|
|
550
|
+
expect(result.frameworks.react).toBe(true);
|
|
551
|
+
expect(result.characteristics.hasFrontend).toBe(true);
|
|
552
|
+
});
|
|
553
|
+
it("detects Express from package.json", async () => {
|
|
554
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
555
|
+
dependencies: { express: "^4.0.0" }
|
|
556
|
+
}));
|
|
557
|
+
const result = await analyzeProject(testDir);
|
|
558
|
+
expect(result.frameworks.express).toBe(true);
|
|
559
|
+
expect(result.characteristics.hasBackend).toBe(true);
|
|
560
|
+
expect(result.characteristics.hasApi).toBe(true);
|
|
561
|
+
});
|
|
562
|
+
it("detects Next.js from package.json", async () => {
|
|
563
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
564
|
+
dependencies: { next: "^14.0.0" }
|
|
565
|
+
}));
|
|
566
|
+
const result = await analyzeProject(testDir);
|
|
567
|
+
expect(result.frameworks.nextjs).toBe(true);
|
|
568
|
+
expect(result.characteristics.hasFrontend).toBe(true);
|
|
569
|
+
expect(result.characteristics.hasBackend).toBe(true);
|
|
570
|
+
});
|
|
571
|
+
it("detects GitHub Actions", async () => {
|
|
572
|
+
await mkdir(join(testDir, ".github", "workflows"), { recursive: true });
|
|
573
|
+
const result = await analyzeProject(testDir);
|
|
574
|
+
expect(result.ci.githubActions).toBe(true);
|
|
575
|
+
});
|
|
576
|
+
it("detects GitLab CI", async () => {
|
|
577
|
+
await writeFile(join(testDir, ".gitlab-ci.yml"), "stages:\n - build");
|
|
578
|
+
const result = await analyzeProject(testDir);
|
|
579
|
+
expect(result.ci.gitlabCi).toBe(true);
|
|
580
|
+
});
|
|
581
|
+
it("detects Jest from package.json", async () => {
|
|
582
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
583
|
+
devDependencies: { jest: "^29.0.0" }
|
|
584
|
+
}));
|
|
585
|
+
const result = await analyzeProject(testDir);
|
|
586
|
+
expect(result.testing.jest).toBe(true);
|
|
587
|
+
});
|
|
588
|
+
it("detects Vitest from package.json", async () => {
|
|
589
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
590
|
+
devDependencies: { vitest: "^1.0.0" }
|
|
591
|
+
}));
|
|
592
|
+
const result = await analyzeProject(testDir);
|
|
593
|
+
expect(result.testing.vitest).toBe(true);
|
|
594
|
+
});
|
|
595
|
+
it("detects pytest from conftest.py", async () => {
|
|
596
|
+
await writeFile(join(testDir, "conftest.py"), "# pytest config");
|
|
597
|
+
const result = await analyzeProject(testDir);
|
|
598
|
+
expect(result.testing.pytest).toBe(true);
|
|
599
|
+
});
|
|
600
|
+
it("detects test directory", async () => {
|
|
601
|
+
await mkdir(join(testDir, "tests"), { recursive: true });
|
|
602
|
+
const result = await analyzeProject(testDir);
|
|
603
|
+
expect(result.testing.hasTestDirectory).toBe(true);
|
|
604
|
+
});
|
|
605
|
+
it("detects AGENTS.md", async () => {
|
|
606
|
+
await writeFile(join(testDir, "AGENTS.md"), "# Agent Instructions");
|
|
607
|
+
const result = await analyzeProject(testDir);
|
|
608
|
+
expect(result.aiTools.agentsMd).toBe(true);
|
|
609
|
+
});
|
|
610
|
+
it("detects CLAUDE.md", async () => {
|
|
611
|
+
await writeFile(join(testDir, "CLAUDE.md"), "# Claude Instructions");
|
|
612
|
+
const result = await analyzeProject(testDir);
|
|
613
|
+
expect(result.aiTools.claudeMd).toBe(true);
|
|
614
|
+
});
|
|
615
|
+
it("detects GitHub Copilot instructions", async () => {
|
|
616
|
+
await mkdir(join(testDir, ".github"), { recursive: true });
|
|
617
|
+
await writeFile(join(testDir, ".github", "copilot-instructions.md"), "# Instructions");
|
|
618
|
+
const result = await analyzeProject(testDir);
|
|
619
|
+
expect(result.aiTools.copilotInstructions).toBe(true);
|
|
620
|
+
});
|
|
621
|
+
it("detects Cursor rules", async () => {
|
|
622
|
+
await writeFile(join(testDir, ".cursorrules"), "# Rules");
|
|
623
|
+
const result = await analyzeProject(testDir);
|
|
624
|
+
expect(result.aiTools.cursorRules).toBe(true);
|
|
625
|
+
});
|
|
626
|
+
it("detects OpenCode config", async () => {
|
|
627
|
+
await writeFile(join(testDir, "opencode.json"), '{}');
|
|
628
|
+
const result = await analyzeProject(testDir);
|
|
629
|
+
expect(result.aiTools.openCodeConfig).toBe(true);
|
|
630
|
+
});
|
|
631
|
+
it("detects Prisma", async () => {
|
|
632
|
+
await mkdir(join(testDir, "prisma"), { recursive: true });
|
|
633
|
+
const result = await analyzeProject(testDir);
|
|
634
|
+
expect(result.database.prisma).toBe(true);
|
|
635
|
+
});
|
|
636
|
+
it("detects migrations directory", async () => {
|
|
637
|
+
await mkdir(join(testDir, "migrations"), { recursive: true });
|
|
638
|
+
const result = await analyzeProject(testDir);
|
|
639
|
+
expect(result.database.hasMigrations).toBe(true);
|
|
640
|
+
});
|
|
641
|
+
it("detects Sentry from package.json", async () => {
|
|
642
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
643
|
+
dependencies: { "@sentry/node": "^7.0.0" }
|
|
644
|
+
}));
|
|
645
|
+
const result = await analyzeProject(testDir);
|
|
646
|
+
expect(result.monitoring.sentry).toBe(true);
|
|
647
|
+
});
|
|
648
|
+
it("detects Docker", async () => {
|
|
649
|
+
await writeFile(join(testDir, "Dockerfile"), "FROM node:20");
|
|
650
|
+
const result = await analyzeProject(testDir);
|
|
651
|
+
expect(result.characteristics.hasDocker).toBe(true);
|
|
652
|
+
});
|
|
653
|
+
it("detects monorepo from workspaces", async () => {
|
|
654
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
655
|
+
workspaces: ["packages/*"]
|
|
656
|
+
}));
|
|
657
|
+
const result = await analyzeProject(testDir);
|
|
658
|
+
expect(result.characteristics.isMonorepo).toBe(true);
|
|
659
|
+
});
|
|
660
|
+
it("detects library from package.json exports", async () => {
|
|
661
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
662
|
+
main: "dist/index.js",
|
|
663
|
+
exports: { ".": "./dist/index.js" }
|
|
664
|
+
}));
|
|
665
|
+
const result = await analyzeProject(testDir);
|
|
666
|
+
expect(result.characteristics.isLibrary).toBe(true);
|
|
667
|
+
});
|
|
668
|
+
it("highlights AI collaboration when AI tools detected", async () => {
|
|
669
|
+
await writeFile(join(testDir, "AGENTS.md"), "# Instructions");
|
|
670
|
+
const result = await analyzeProject(testDir);
|
|
671
|
+
expect(result.recommendations.highlightedTopics).toContain("AI/LLM Collaboration");
|
|
672
|
+
});
|
|
673
|
+
it("marks database topics as skippable when no database", async () => {
|
|
674
|
+
const result = await analyzeProject(testDir);
|
|
675
|
+
expect(result.recommendations.skippableTopics).toContain("Database & Schema Changes");
|
|
676
|
+
});
|
|
677
|
+
it("marks a11y as skippable when no frontend", async () => {
|
|
678
|
+
await writeFile(join(testDir, "package.json"), JSON.stringify({
|
|
679
|
+
dependencies: { express: "^4.0.0" }
|
|
680
|
+
}));
|
|
681
|
+
const result = await analyzeProject(testDir);
|
|
682
|
+
expect(result.recommendations.skippableTopics).toContain("Accessibility & Internationalization");
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
describe("formatProjectAnalysis", () => {
|
|
686
|
+
it("formats empty analysis", () => {
|
|
687
|
+
const analysis = {
|
|
688
|
+
languages: { typescript: false, javascript: false, python: false, rust: false, go: false, ruby: false, java: false, csharp: false, other: [] },
|
|
689
|
+
frameworks: { react: false, vue: false, angular: false, nextjs: false, express: false, fastapi: false, django: false, rails: false, springBoot: false, other: [] },
|
|
690
|
+
ci: { githubActions: false, gitlabCi: false, circleCi: false, jenkins: false, other: [] },
|
|
691
|
+
testing: { jest: false, vitest: false, mocha: false, pytest: false, rspec: false, goTest: false, hasTestDirectory: false, other: [] },
|
|
692
|
+
aiTools: { agentsMd: false, claudeMd: false, copilotInstructions: false, cursorRules: false, continueConfig: false, openCodeConfig: false },
|
|
693
|
+
database: { prisma: false, sequelize: false, typeorm: false, drizzle: false, sqlalchemy: false, activeRecord: false, hasMigrations: false, other: [] },
|
|
694
|
+
monitoring: { sentry: false, datadog: false, newRelic: false, prometheus: false, other: [] },
|
|
695
|
+
characteristics: { isMonorepo: false, isLibrary: false, hasDocker: false, hasFrontend: false, hasBackend: false, hasApi: false, hasDocs: false },
|
|
696
|
+
recommendations: { suggestedCategories: ["Code & Quality"], highlightedTopics: [], skippableTopics: [] }
|
|
697
|
+
};
|
|
698
|
+
const result = formatProjectAnalysis(analysis);
|
|
699
|
+
expect(result).toContain("Project Analysis Results");
|
|
700
|
+
expect(result).toContain("Recommendations");
|
|
701
|
+
expect(result).toContain("Code & Quality");
|
|
702
|
+
});
|
|
703
|
+
it("formats languages section", () => {
|
|
704
|
+
const analysis = {
|
|
705
|
+
languages: { typescript: true, javascript: true, python: false, rust: false, go: false, ruby: false, java: false, csharp: false, other: [] },
|
|
706
|
+
frameworks: { react: false, vue: false, angular: false, nextjs: false, express: false, fastapi: false, django: false, rails: false, springBoot: false, other: [] },
|
|
707
|
+
ci: { githubActions: false, gitlabCi: false, circleCi: false, jenkins: false, other: [] },
|
|
708
|
+
testing: { jest: false, vitest: false, mocha: false, pytest: false, rspec: false, goTest: false, hasTestDirectory: false, other: [] },
|
|
709
|
+
aiTools: { agentsMd: false, claudeMd: false, copilotInstructions: false, cursorRules: false, continueConfig: false, openCodeConfig: false },
|
|
710
|
+
database: { prisma: false, sequelize: false, typeorm: false, drizzle: false, sqlalchemy: false, activeRecord: false, hasMigrations: false, other: [] },
|
|
711
|
+
monitoring: { sentry: false, datadog: false, newRelic: false, prometheus: false, other: [] },
|
|
712
|
+
characteristics: { isMonorepo: false, isLibrary: false, hasDocker: false, hasFrontend: false, hasBackend: false, hasApi: false, hasDocs: false },
|
|
713
|
+
recommendations: { suggestedCategories: [], highlightedTopics: [], skippableTopics: [] }
|
|
714
|
+
};
|
|
715
|
+
const result = formatProjectAnalysis(analysis);
|
|
716
|
+
expect(result).toContain("### Languages");
|
|
717
|
+
expect(result).toContain("Typescript");
|
|
718
|
+
expect(result).toContain("Javascript");
|
|
719
|
+
});
|
|
720
|
+
it("formats frameworks section", () => {
|
|
721
|
+
const analysis = {
|
|
722
|
+
languages: { typescript: false, javascript: false, python: false, rust: false, go: false, ruby: false, java: false, csharp: false, other: [] },
|
|
723
|
+
frameworks: { react: true, vue: false, angular: false, nextjs: false, express: true, fastapi: false, django: false, rails: false, springBoot: false, other: [] },
|
|
724
|
+
ci: { githubActions: false, gitlabCi: false, circleCi: false, jenkins: false, other: [] },
|
|
725
|
+
testing: { jest: false, vitest: false, mocha: false, pytest: false, rspec: false, goTest: false, hasTestDirectory: false, other: [] },
|
|
726
|
+
aiTools: { agentsMd: false, claudeMd: false, copilotInstructions: false, cursorRules: false, continueConfig: false, openCodeConfig: false },
|
|
727
|
+
database: { prisma: false, sequelize: false, typeorm: false, drizzle: false, sqlalchemy: false, activeRecord: false, hasMigrations: false, other: [] },
|
|
728
|
+
monitoring: { sentry: false, datadog: false, newRelic: false, prometheus: false, other: [] },
|
|
729
|
+
characteristics: { isMonorepo: false, isLibrary: false, hasDocker: false, hasFrontend: false, hasBackend: false, hasApi: false, hasDocs: false },
|
|
730
|
+
recommendations: { suggestedCategories: [], highlightedTopics: [], skippableTopics: [] }
|
|
731
|
+
};
|
|
732
|
+
const result = formatProjectAnalysis(analysis);
|
|
733
|
+
expect(result).toContain("### Frameworks");
|
|
734
|
+
expect(result).toContain("React");
|
|
735
|
+
expect(result).toContain("Express");
|
|
736
|
+
});
|
|
737
|
+
it("formats AI tools section", () => {
|
|
738
|
+
const analysis = {
|
|
739
|
+
languages: { typescript: false, javascript: false, python: false, rust: false, go: false, ruby: false, java: false, csharp: false, other: [] },
|
|
740
|
+
frameworks: { react: false, vue: false, angular: false, nextjs: false, express: false, fastapi: false, django: false, rails: false, springBoot: false, other: [] },
|
|
741
|
+
ci: { githubActions: false, gitlabCi: false, circleCi: false, jenkins: false, other: [] },
|
|
742
|
+
testing: { jest: false, vitest: false, mocha: false, pytest: false, rspec: false, goTest: false, hasTestDirectory: false, other: [] },
|
|
743
|
+
aiTools: { agentsMd: true, claudeMd: false, copilotInstructions: true, cursorRules: false, continueConfig: false, openCodeConfig: false },
|
|
744
|
+
database: { prisma: false, sequelize: false, typeorm: false, drizzle: false, sqlalchemy: false, activeRecord: false, hasMigrations: false, other: [] },
|
|
745
|
+
monitoring: { sentry: false, datadog: false, newRelic: false, prometheus: false, other: [] },
|
|
746
|
+
characteristics: { isMonorepo: false, isLibrary: false, hasDocker: false, hasFrontend: false, hasBackend: false, hasApi: false, hasDocs: false },
|
|
747
|
+
recommendations: { suggestedCategories: [], highlightedTopics: [], skippableTopics: [] }
|
|
748
|
+
};
|
|
749
|
+
const result = formatProjectAnalysis(analysis);
|
|
750
|
+
expect(result).toContain("### AI Tools Configuration");
|
|
751
|
+
expect(result).toContain("AGENTS.md");
|
|
752
|
+
expect(result).toContain("GitHub Copilot instructions");
|
|
753
|
+
});
|
|
754
|
+
it("shows no CI message when not detected", () => {
|
|
755
|
+
const analysis = {
|
|
756
|
+
languages: { typescript: false, javascript: false, python: false, rust: false, go: false, ruby: false, java: false, csharp: false, other: [] },
|
|
757
|
+
frameworks: { react: false, vue: false, angular: false, nextjs: false, express: false, fastapi: false, django: false, rails: false, springBoot: false, other: [] },
|
|
758
|
+
ci: { githubActions: false, gitlabCi: false, circleCi: false, jenkins: false, other: [] },
|
|
759
|
+
testing: { jest: false, vitest: false, mocha: false, pytest: false, rspec: false, goTest: false, hasTestDirectory: false, other: [] },
|
|
760
|
+
aiTools: { agentsMd: false, claudeMd: false, copilotInstructions: false, cursorRules: false, continueConfig: false, openCodeConfig: false },
|
|
761
|
+
database: { prisma: false, sequelize: false, typeorm: false, drizzle: false, sqlalchemy: false, activeRecord: false, hasMigrations: false, other: [] },
|
|
762
|
+
monitoring: { sentry: false, datadog: false, newRelic: false, prometheus: false, other: [] },
|
|
763
|
+
characteristics: { isMonorepo: false, isLibrary: false, hasDocker: false, hasFrontend: false, hasBackend: false, hasApi: false, hasDocs: false },
|
|
764
|
+
recommendations: { suggestedCategories: [], highlightedTopics: [], skippableTopics: [] }
|
|
765
|
+
};
|
|
766
|
+
const result = formatProjectAnalysis(analysis);
|
|
767
|
+
expect(result).toContain("No CI/CD detected");
|
|
768
|
+
});
|
|
769
|
+
it("formats highlighted topics", () => {
|
|
770
|
+
const analysis = {
|
|
771
|
+
languages: { typescript: false, javascript: false, python: false, rust: false, go: false, ruby: false, java: false, csharp: false, other: [] },
|
|
772
|
+
frameworks: { react: false, vue: false, angular: false, nextjs: false, express: false, fastapi: false, django: false, rails: false, springBoot: false, other: [] },
|
|
773
|
+
ci: { githubActions: false, gitlabCi: false, circleCi: false, jenkins: false, other: [] },
|
|
774
|
+
testing: { jest: false, vitest: false, mocha: false, pytest: false, rspec: false, goTest: false, hasTestDirectory: false, other: [] },
|
|
775
|
+
aiTools: { agentsMd: false, claudeMd: false, copilotInstructions: false, cursorRules: false, continueConfig: false, openCodeConfig: false },
|
|
776
|
+
database: { prisma: false, sequelize: false, typeorm: false, drizzle: false, sqlalchemy: false, activeRecord: false, hasMigrations: false, other: [] },
|
|
777
|
+
monitoring: { sentry: false, datadog: false, newRelic: false, prometheus: false, other: [] },
|
|
778
|
+
characteristics: { isMonorepo: false, isLibrary: false, hasDocker: false, hasFrontend: false, hasBackend: false, hasApi: false, hasDocs: false },
|
|
779
|
+
recommendations: { suggestedCategories: [], highlightedTopics: ["AI/LLM Collaboration", "Security"], skippableTopics: [] }
|
|
780
|
+
};
|
|
781
|
+
const result = formatProjectAnalysis(analysis);
|
|
782
|
+
expect(result).toContain("Topics to highlight");
|
|
783
|
+
expect(result).toContain("AI/LLM Collaboration");
|
|
784
|
+
expect(result).toContain("Security");
|
|
785
|
+
});
|
|
786
|
+
it("formats skippable topics", () => {
|
|
787
|
+
const analysis = {
|
|
788
|
+
languages: { typescript: false, javascript: false, python: false, rust: false, go: false, ruby: false, java: false, csharp: false, other: [] },
|
|
789
|
+
frameworks: { react: false, vue: false, angular: false, nextjs: false, express: false, fastapi: false, django: false, rails: false, springBoot: false, other: [] },
|
|
790
|
+
ci: { githubActions: false, gitlabCi: false, circleCi: false, jenkins: false, other: [] },
|
|
791
|
+
testing: { jest: false, vitest: false, mocha: false, pytest: false, rspec: false, goTest: false, hasTestDirectory: false, other: [] },
|
|
792
|
+
aiTools: { agentsMd: false, claudeMd: false, copilotInstructions: false, cursorRules: false, continueConfig: false, openCodeConfig: false },
|
|
793
|
+
database: { prisma: false, sequelize: false, typeorm: false, drizzle: false, sqlalchemy: false, activeRecord: false, hasMigrations: false, other: [] },
|
|
794
|
+
monitoring: { sentry: false, datadog: false, newRelic: false, prometheus: false, other: [] },
|
|
795
|
+
characteristics: { isMonorepo: false, isLibrary: false, hasDocker: false, hasFrontend: false, hasBackend: false, hasApi: false, hasDocs: false },
|
|
796
|
+
recommendations: { suggestedCategories: [], highlightedTopics: [], skippableTopics: ["Database & Schema Changes"] }
|
|
797
|
+
};
|
|
798
|
+
const result = formatProjectAnalysis(analysis);
|
|
799
|
+
expect(result).toContain("Topics that may be skippable");
|
|
800
|
+
expect(result).toContain("Database & Schema Changes");
|
|
801
|
+
});
|
|
433
802
|
});
|
|
434
803
|
//# sourceMappingURL=index.test.js.map
|