@shiftleftpt/sbd-toe-mcp 0.1.0 → 0.2.16

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 (42) hide show
  1. package/.env.example +0 -3
  2. package/README.md +136 -254
  3. package/dist/config.js +0 -3
  4. package/dist/config.js.map +1 -1
  5. package/dist/types.d.ts +0 -3
  6. package/package.json +18 -2
  7. package/dist/backend/semantic-index-gateway.test.d.ts +0 -1
  8. package/dist/backend/semantic-index-gateway.test.js +0 -384
  9. package/dist/backend/semantic-index-gateway.test.js.map +0 -1
  10. package/dist/bootstrap/checkout-backend.d.ts +0 -31
  11. package/dist/bootstrap/checkout-backend.js +0 -136
  12. package/dist/bootstrap/checkout-backend.js.map +0 -1
  13. package/dist/bootstrap/checkout-backend.test.d.ts +0 -1
  14. package/dist/bootstrap/checkout-backend.test.js +0 -158
  15. package/dist/bootstrap/checkout-backend.test.js.map +0 -1
  16. package/dist/bootstrap/release-checkout.d.ts +0 -8
  17. package/dist/bootstrap/release-checkout.js +0 -168
  18. package/dist/bootstrap/release-checkout.js.map +0 -1
  19. package/dist/bootstrap/release-checkout.test.d.ts +0 -1
  20. package/dist/bootstrap/release-checkout.test.js +0 -137
  21. package/dist/bootstrap/release-checkout.test.js.map +0 -1
  22. package/dist/resources/sbd-toe-resources.test.d.ts +0 -1
  23. package/dist/resources/sbd-toe-resources.test.js +0 -134
  24. package/dist/resources/sbd-toe-resources.test.js.map +0 -1
  25. package/dist/test-utils.d.ts +0 -153
  26. package/dist/test-utils.js +0 -176
  27. package/dist/test-utils.js.map +0 -1
  28. package/dist/tools/generate-document.test.d.ts +0 -1
  29. package/dist/tools/generate-document.test.js +0 -189
  30. package/dist/tools/generate-document.test.js.map +0 -1
  31. package/dist/tools/map-review-scope.test.d.ts +0 -1
  32. package/dist/tools/map-review-scope.test.js +0 -204
  33. package/dist/tools/map-review-scope.test.js.map +0 -1
  34. package/dist/tools/plan-repo-governance.test.d.ts +0 -1
  35. package/dist/tools/plan-repo-governance.test.js +0 -237
  36. package/dist/tools/plan-repo-governance.test.js.map +0 -1
  37. package/dist/tools/structured-tools.test.d.ts +0 -1
  38. package/dist/tools/structured-tools.test.js +0 -459
  39. package/dist/tools/structured-tools.test.js.map +0 -1
  40. package/dist/validators/ai-disclosure.test.d.ts +0 -1
  41. package/dist/validators/ai-disclosure.test.js +0 -244
  42. package/dist/validators/ai-disclosure.test.js.map +0 -1
@@ -1,158 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { sanitizeRunManifest, ensurePublishedIndex } from "../bootstrap/checkout-backend.js";
3
- // --- Tests ---
4
- describe("checkout-backend.ts", () => {
5
- describe("sanitizeRunManifest", () => {
6
- it("preserves run_id when present", () => {
7
- const upstream = {
8
- run_id: "test-run-123"
9
- };
10
- const result = sanitizeRunManifest(upstream);
11
- expect(result.run_id).toBe("test-run-123");
12
- });
13
- it("omits run_id when undefined", () => {
14
- const upstream = {
15
- generated_at: "2026-03-24T10:00:00Z"
16
- };
17
- const result = sanitizeRunManifest(upstream);
18
- expect(result).not.toHaveProperty("run_id");
19
- expect(result.generated_at).toBe("2026-03-24T10:00:00Z");
20
- });
21
- it("converts SSH repo_url to HTTPS", () => {
22
- const upstream = {
23
- repo_url: "git@github.com:test/repo.git"
24
- };
25
- const result = sanitizeRunManifest(upstream);
26
- expect(result.repo_url).toBe("https://github.com/test/repo");
27
- });
28
- it("converts SSH repo_url without .git suffix", () => {
29
- const upstream = {
30
- repo_url: "git@github.com:test/repo"
31
- };
32
- const result = sanitizeRunManifest(upstream);
33
- expect(result.repo_url).toBe("https://github.com/test/repo");
34
- });
35
- it("preserves HTTPS repo_url unchanged", () => {
36
- const upstream = {
37
- repo_url: "https://github.com/test/repo.git"
38
- };
39
- const result = sanitizeRunManifest(upstream);
40
- expect(result.repo_url).toBe("https://github.com/test/repo.git");
41
- });
42
- it("handles whitespace in repo_url", () => {
43
- const upstream = {
44
- repo_url: " https://github.com/test/repo "
45
- };
46
- const result = sanitizeRunManifest(upstream);
47
- expect(result.repo_url).toBe("https://github.com/test/repo");
48
- });
49
- it("omits repo_url when undefined", () => {
50
- const upstream = {
51
- run_id: "123"
52
- };
53
- const result = sanitizeRunManifest(upstream);
54
- expect(result).not.toHaveProperty("repo_url");
55
- });
56
- it("omits repo_url when empty string", () => {
57
- const upstream = {
58
- repo_url: ""
59
- };
60
- const result = sanitizeRunManifest(upstream);
61
- expect(result).not.toHaveProperty("repo_url");
62
- });
63
- it("preserves all fields when all present", () => {
64
- const upstream = {
65
- run_id: "run-001",
66
- generated_at: "2026-03-24T10:00:00Z",
67
- branch: "main",
68
- commit_sha: "abc123",
69
- corpus_root: "/corpus",
70
- repo_url: "https://github.com/test/repo",
71
- sync_mode: "full",
72
- version: "1.0.0"
73
- };
74
- const result = sanitizeRunManifest(upstream);
75
- expect(result.run_id).toBe("run-001");
76
- expect(result.generated_at).toBe("2026-03-24T10:00:00Z");
77
- expect(result.branch).toBe("main");
78
- expect(result.commit_sha).toBe("abc123");
79
- expect(result.corpus_root).toBe("/corpus");
80
- expect(result.repo_url).toBe("https://github.com/test/repo");
81
- expect(result.sync_mode).toBe("full");
82
- expect(result.version).toBe("1.0.0");
83
- });
84
- });
85
- describe("ensurePublishedIndex", () => {
86
- it("finds index by record_family", () => {
87
- const items = [
88
- {
89
- index_name: "test_docs",
90
- record_family: "documents",
91
- settings: { ranking: ["asc(popularity)"] }
92
- }
93
- ];
94
- const result = ensurePublishedIndex(items, "documents", "default_docs");
95
- expect(result.indexName).toBe("test_docs");
96
- expect(result.recordFamily).toBe("documents");
97
- expect(result.settings).toEqual({ ranking: ["asc(popularity)"] });
98
- });
99
- it("uses index_name as fallback when record_family not found", () => {
100
- const items = [
101
- {
102
- index_name: "fallback_index",
103
- record_family: "other_family"
104
- }
105
- ];
106
- const result = ensurePublishedIndex(items, "entities", "fallback_index");
107
- expect(result.indexName).toBe("fallback_index");
108
- expect(result.recordFamily).toBe("entities");
109
- });
110
- it("throws when index not found by family or fallback", () => {
111
- const items = [
112
- {
113
- index_name: "some_index",
114
- record_family: "other_family"
115
- }
116
- ];
117
- expect(() => {
118
- ensurePublishedIndex(items, "documents", "missing_fallback");
119
- }).toThrow(/O upstream não publicou um índice utilizável para "documents"/);
120
- });
121
- it("throws when items array is empty", () => {
122
- expect(() => {
123
- ensurePublishedIndex([], "documents", "fallback");
124
- }).toThrow(/O upstream não publicou um índice utilizável/);
125
- });
126
- it("throws when items is undefined", () => {
127
- expect(() => {
128
- ensurePublishedIndex(undefined, "documents", "fallback");
129
- }).toThrow(/O upstream não publicou um índice utilizável/);
130
- });
131
- it("defaults settings to empty object when not provided", () => {
132
- const items = [
133
- {
134
- index_name: "test_index",
135
- record_family: "documents"
136
- // settings omitted
137
- }
138
- ];
139
- const result = ensurePublishedIndex(items, "documents", "fallback");
140
- expect(result.settings).toEqual({});
141
- });
142
- it("prefers exact family match over fallback", () => {
143
- const items = [
144
- {
145
- index_name: "fallback_index",
146
- record_family: "other_family"
147
- },
148
- {
149
- index_name: "exact_docs_index",
150
- record_family: "documents"
151
- }
152
- ];
153
- const result = ensurePublishedIndex(items, "documents", "fallback_index");
154
- expect(result.indexName).toBe("exact_docs_index");
155
- });
156
- });
157
- });
158
- //# sourceMappingURL=checkout-backend.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"checkout-backend.test.js","sourceRoot":"","sources":["../../src/bootstrap/checkout-backend.test.ts"],"names":[],"mappings":"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"}
@@ -1,8 +0,0 @@
1
- import type { AppConfig } from "../types.js";
2
- export declare function assertSafeAssetUrl(url: string): void;
3
- export declare function assertSafeDestPath(destPath: string, baseDir: string): void;
4
- export declare function fetchReleaseAssetUrl(tag: string, timeoutMs: number): Promise<{
5
- assetUrl: string;
6
- assetName: string;
7
- }>;
8
- export declare function checkoutFromRelease(config: AppConfig, destDir: string): Promise<void>;
@@ -1,168 +0,0 @@
1
- import { createWriteStream } from "node:fs";
2
- import { copyFile, mkdir, mkdtemp, rm } from "node:fs/promises";
3
- import { Readable } from "node:stream";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import { pipeline } from "node:stream/promises";
7
- import { spawnSync } from "node:child_process";
8
- // SECURITY: URL base e owner/repo são HARDCODED. Nunca aceitar de env ou input externo.
9
- const HARDCODED_API_BASE = "https://api.github.com/repos/Shiftleftpt/sbd-toe-knowledge-graph";
10
- const ALLOWED_ASSET_URL_PREFIXES = [
11
- "https://github.com/Shiftleftpt/sbd-toe-knowledge-graph/",
12
- "https://objects.githubusercontent.com/",
13
- ];
14
- // Fixed list of files to copy from the extracted bundle to destDir.
15
- // SECURITY: Only known paths are copied; no dynamic traversal of extracted content.
16
- const KNOWN_FILES = [
17
- {
18
- src: path.join("data", "publish", "algolia_docs_records.json"),
19
- dest: path.join("data", "publish", "algolia_docs_records.json"),
20
- },
21
- {
22
- src: path.join("data", "publish", "algolia_entities_records.json"),
23
- dest: path.join("data", "publish", "algolia_entities_records.json"),
24
- },
25
- {
26
- src: path.join("data", "publish", "algolia_docs_records_enriched.json"),
27
- dest: path.join("data", "publish", "algolia_docs_records_enriched.json"),
28
- },
29
- {
30
- src: path.join("data", "publish", "algolia_entities_records_enriched.json"),
31
- dest: path.join("data", "publish", "algolia_entities_records_enriched.json"),
32
- },
33
- {
34
- src: path.join("data", "publish", "algolia_index_settings.json"),
35
- dest: path.join("data", "publish", "algolia_index_settings.json"),
36
- },
37
- {
38
- src: path.join("data", "reports", "run_manifest.json"),
39
- dest: path.join("data", "reports", "run_manifest.json"),
40
- },
41
- ];
42
- export function assertSafeAssetUrl(url) {
43
- const allowed = ALLOWED_ASSET_URL_PREFIXES.some((prefix) => url.startsWith(prefix));
44
- if (!allowed) {
45
- throw new Error(`URL de asset não autorizada: ${url}`);
46
- }
47
- }
48
- export function assertSafeDestPath(destPath, baseDir) {
49
- const resolved = path.resolve(destPath);
50
- const base = path.resolve(baseDir);
51
- if (!resolved.startsWith(base + path.sep) && resolved !== base) {
52
- throw new Error(`Path traversal detectado: ${destPath}`);
53
- }
54
- }
55
- export async function fetchReleaseAssetUrl(tag, timeoutMs) {
56
- const controller = new AbortController();
57
- const timer = setTimeout(() => controller.abort(), timeoutMs);
58
- try {
59
- const releaseUrl = tag === "latest"
60
- ? `${HARDCODED_API_BASE}/releases/latest`
61
- : `${HARDCODED_API_BASE}/releases/tags/${encodeURIComponent(tag)}`;
62
- const resp = await fetch(releaseUrl, {
63
- signal: controller.signal,
64
- headers: {
65
- Accept: "application/vnd.github+json",
66
- "X-GitHub-Api-Version": "2022-11-28",
67
- },
68
- });
69
- if (!resp.ok) {
70
- throw new Error(`GitHub API respondeu ${resp.status} para ${releaseUrl}`);
71
- }
72
- const release = (await resp.json());
73
- const asset = release.assets?.find((a) => a.name.endsWith(".tar.gz") ||
74
- a.name.endsWith(".zip") ||
75
- a.name.includes("bundle"));
76
- if (!asset) {
77
- throw new Error(`Nenhum asset de bundle encontrado na release '${tag}'.`);
78
- }
79
- assertSafeAssetUrl(asset.browser_download_url);
80
- return { assetUrl: asset.browser_download_url, assetName: asset.name };
81
- }
82
- finally {
83
- clearTimeout(timer);
84
- }
85
- }
86
- export async function checkoutFromRelease(config, destDir) {
87
- const { upstreamReleaseTag, upstreamReleaseMaxBytes, upstreamReleaseTimeoutMs } = config.backend;
88
- const { assetUrl, assetName } = await fetchReleaseAssetUrl(upstreamReleaseTag, upstreamReleaseTimeoutMs);
89
- process.stderr.write(`[release-checkout] Downloading asset '${assetName}' from release '${upstreamReleaseTag}'.\n`);
90
- // Download with timeout and size limit
91
- const controller = new AbortController();
92
- const timer = setTimeout(() => controller.abort(), upstreamReleaseTimeoutMs);
93
- let tmpDir;
94
- let tmpFile;
95
- try {
96
- const resp = await fetch(assetUrl, { signal: controller.signal });
97
- if (!resp.ok || !resp.body) {
98
- throw new Error(`Download falhou com status ${resp.status} para ${assetUrl}`);
99
- }
100
- tmpDir = await mkdtemp(path.join(os.tmpdir(), "sbd-release-"));
101
- tmpFile = path.join(tmpDir, assetName);
102
- let bytesReceived = 0;
103
- const fileStream = createWriteStream(tmpFile);
104
- await pipeline(Readable.fromWeb(resp.body), async function* (source) {
105
- for await (const chunk of source) {
106
- bytesReceived += chunk.length;
107
- if (bytesReceived > upstreamReleaseMaxBytes) {
108
- throw new Error(`Asset excede o limite de tamanho (${upstreamReleaseMaxBytes} bytes): ${assetName}`);
109
- }
110
- yield chunk;
111
- }
112
- }, fileStream);
113
- // Extract
114
- const extractDir = path.join(tmpDir, "extracted");
115
- await mkdir(extractDir, { recursive: true });
116
- if (assetName.endsWith(".tar.gz") || assetName.endsWith(".tgz")) {
117
- // SECURITY: argv fixos, sem shell, sem interpolação de env
118
- const result = spawnSync("tar", ["-xzf", tmpFile, "-C", extractDir], {
119
- stdio: ["ignore", "ignore", "pipe"],
120
- timeout: upstreamReleaseTimeoutMs,
121
- shell: false,
122
- });
123
- if (result.status !== 0) {
124
- const stderr = result.stderr instanceof Buffer ? result.stderr.toString() : "";
125
- throw new Error(`Extracção tar falhou (status ${result.status ?? "?"}): ${stderr.trim()}`);
126
- }
127
- }
128
- else if (assetName.endsWith(".zip")) {
129
- // SECURITY: argv fixos, sem shell, sem interpolação de env
130
- const result = spawnSync("unzip", ["-q", tmpFile, "-d", extractDir], {
131
- stdio: ["ignore", "ignore", "pipe"],
132
- timeout: upstreamReleaseTimeoutMs,
133
- shell: false,
134
- });
135
- if (result.status !== 0) {
136
- const stderr = result.stderr instanceof Buffer ? result.stderr.toString() : "";
137
- throw new Error(`Extracção zip falhou (status ${result.status ?? "?"}): ${stderr.trim()}`);
138
- }
139
- }
140
- else {
141
- throw new Error(`Formato de asset não suportado: ${assetName}`);
142
- }
143
- // Copy known files from extracted bundle to destDir
144
- for (const { src, dest } of KNOWN_FILES) {
145
- const srcPath = path.join(extractDir, src);
146
- const destPath = path.join(destDir, dest);
147
- // SECURITY: validate that the destination stays within destDir
148
- assertSafeDestPath(destPath, destDir);
149
- await mkdir(path.dirname(destPath), { recursive: true });
150
- try {
151
- await copyFile(srcPath, destPath);
152
- process.stderr.write(`[release-checkout] Copiado: ${dest}\n`);
153
- }
154
- catch (err) {
155
- const message = err instanceof Error ? err.message : String(err);
156
- process.stderr.write(`[release-checkout] AVISO: ficheiro não encontrado no bundle: ${src} (${message})\n`);
157
- }
158
- }
159
- process.stderr.write(`[release-checkout] Checkout via release '${upstreamReleaseTag}' concluído.\n`);
160
- }
161
- finally {
162
- clearTimeout(timer);
163
- if (tmpDir) {
164
- await rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
165
- }
166
- }
167
- }
168
- //# sourceMappingURL=release-checkout.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-checkout.js","sourceRoot":"","sources":["../../src/bootstrap/release-checkout.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,137 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import path from "node:path";
3
- import { assertSafeAssetUrl, assertSafeDestPath, fetchReleaseAssetUrl, } from "./release-checkout.js";
4
- // ---------------------------------------------------------------------------
5
- // assertSafeAssetUrl
6
- // ---------------------------------------------------------------------------
7
- describe("assertSafeAssetUrl", () => {
8
- it("accepts an authorised github.com URL", () => {
9
- expect(() => assertSafeAssetUrl("https://github.com/Shiftleftpt/sbd-toe-knowledge-graph/releases/download/v1.0.0/bundle.tar.gz")).not.toThrow();
10
- });
11
- it("accepts an authorised objects.githubusercontent.com URL", () => {
12
- expect(() => assertSafeAssetUrl("https://objects.githubusercontent.com/github-production-release-asset-2e65be/123/bundle.tar.gz")).not.toThrow();
13
- });
14
- it("rejects an unauthorised external URL", () => {
15
- expect(() => assertSafeAssetUrl("https://evil.example.com/malicious.tar.gz")).toThrow("URL de asset não autorizada");
16
- });
17
- it("rejects a URL that starts with an allowed prefix but has wrong host", () => {
18
- expect(() => assertSafeAssetUrl("https://evil.github.com/Shiftleftpt/anything")).toThrow("URL de asset não autorizada");
19
- });
20
- });
21
- // ---------------------------------------------------------------------------
22
- // assertSafeDestPath
23
- // ---------------------------------------------------------------------------
24
- describe("assertSafeDestPath", () => {
25
- const baseDir = "/tmp/sbd-base";
26
- it("accepts a path inside baseDir", () => {
27
- expect(() => assertSafeDestPath(path.join(baseDir, "data", "publish", "file.json"), baseDir)).not.toThrow();
28
- });
29
- it("accepts a path equal to baseDir", () => {
30
- expect(() => assertSafeDestPath(baseDir, baseDir)).not.toThrow();
31
- });
32
- it("rejects a path-traversal attempt with ../", () => {
33
- expect(() => assertSafeDestPath(path.join(baseDir, "..", "..", "etc", "passwd"), baseDir)).toThrow("Path traversal detectado");
34
- });
35
- it("rejects an absolute path outside baseDir", () => {
36
- expect(() => assertSafeDestPath("/usr/local/bin/evil", baseDir)).toThrow("Path traversal detectado");
37
- });
38
- });
39
- // ---------------------------------------------------------------------------
40
- // fetchReleaseAssetUrl (mocked fetch)
41
- // ---------------------------------------------------------------------------
42
- describe("fetchReleaseAssetUrl", () => {
43
- beforeEach(() => {
44
- vi.stubGlobal("fetch", vi.fn());
45
- });
46
- afterEach(() => {
47
- vi.unstubAllGlobals();
48
- });
49
- it("throws when GitHub API returns 404", async () => {
50
- vi.mocked(fetch).mockResolvedValueOnce(new Response(null, { status: 404 }));
51
- await expect(fetchReleaseAssetUrl("latest", 5000)).rejects.toThrow("GitHub API respondeu 404");
52
- });
53
- it("throws when release has no assets", async () => {
54
- vi.mocked(fetch).mockResolvedValueOnce(new Response(JSON.stringify({ assets: [] }), {
55
- status: 200,
56
- headers: { "Content-Type": "application/json" },
57
- }));
58
- await expect(fetchReleaseAssetUrl("latest", 5000)).rejects.toThrow("Nenhum asset de bundle encontrado");
59
- });
60
- it("throws when release has no assets property at all", async () => {
61
- vi.mocked(fetch).mockResolvedValueOnce(new Response(JSON.stringify({}), {
62
- status: 200,
63
- headers: { "Content-Type": "application/json" },
64
- }));
65
- await expect(fetchReleaseAssetUrl("latest", 5000)).rejects.toThrow("Nenhum asset de bundle encontrado");
66
- });
67
- it("throws when asset URL is not in allowed prefixes", async () => {
68
- vi.mocked(fetch).mockResolvedValueOnce(new Response(JSON.stringify({
69
- assets: [
70
- {
71
- name: "bundle.tar.gz",
72
- browser_download_url: "https://evil.example.com/bundle.tar.gz",
73
- },
74
- ],
75
- }), {
76
- status: 200,
77
- headers: { "Content-Type": "application/json" },
78
- }));
79
- await expect(fetchReleaseAssetUrl("latest", 5000)).rejects.toThrow("URL de asset não autorizada");
80
- });
81
- it("returns assetUrl and assetName for a valid release with bundle asset", async () => {
82
- const downloadUrl = "https://github.com/Shiftleftpt/sbd-toe-knowledge-graph/releases/download/v1.0.0/bundle.tar.gz";
83
- vi.mocked(fetch).mockResolvedValueOnce(new Response(JSON.stringify({
84
- assets: [
85
- {
86
- name: "bundle.tar.gz",
87
- browser_download_url: downloadUrl,
88
- },
89
- ],
90
- }), {
91
- status: 200,
92
- headers: { "Content-Type": "application/json" },
93
- }));
94
- const result = await fetchReleaseAssetUrl("latest", 5000);
95
- expect(result.assetUrl).toBe(downloadUrl);
96
- expect(result.assetName).toBe("bundle.tar.gz");
97
- });
98
- it("uses tag-specific URL when tag is not 'latest'", async () => {
99
- const downloadUrl = "https://github.com/Shiftleftpt/sbd-toe-knowledge-graph/releases/download/v2.0.0/bundle.tar.gz";
100
- vi.mocked(fetch).mockResolvedValueOnce(new Response(JSON.stringify({
101
- assets: [
102
- {
103
- name: "bundle.tar.gz",
104
- browser_download_url: downloadUrl,
105
- },
106
- ],
107
- }), {
108
- status: 200,
109
- headers: { "Content-Type": "application/json" },
110
- }));
111
- await fetchReleaseAssetUrl("v2.0.0", 5000);
112
- const calledUrl = vi.mocked(fetch).mock.calls[0]?.[0];
113
- expect(calledUrl).toContain("/releases/tags/v2.0.0");
114
- });
115
- });
116
- // ---------------------------------------------------------------------------
117
- // UPSTREAM_SOURCE config validation
118
- // ---------------------------------------------------------------------------
119
- describe("getConfig UPSTREAM_SOURCE validation", () => {
120
- afterEach(() => {
121
- vi.unstubAllGlobals();
122
- // Reset module to clear cached config
123
- vi.resetModules();
124
- });
125
- it("throws when UPSTREAM_SOURCE has an invalid value", async () => {
126
- process.env["UPSTREAM_SOURCE"] = "INVALID";
127
- try {
128
- const { getConfig } = await import("../config.js");
129
- // Reset cache by re-importing a fresh module (resetModules above)
130
- expect(() => getConfig()).toThrow("UPSTREAM_SOURCE deve ser 'local' ou 'release'");
131
- }
132
- finally {
133
- delete process.env["UPSTREAM_SOURCE"];
134
- }
135
- });
136
- });
137
- //# sourceMappingURL=release-checkout.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-checkout.test.js","sourceRoot":"","sources":["../../src/bootstrap/release-checkout.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,134 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { buildSkillTemplateMarkdown, buildChapterApplicabilityJson, buildSetupAgentPrompt } from "./sbd-toe-resources.js";
3
- describe("buildSkillTemplateMarkdown", () => {
4
- it("L1 with projectRole returns non-empty string containing L1 and Cap.", () => {
5
- const result = buildSkillTemplateMarkdown("L1", "mcp-wrapper");
6
- expect(result).toBeTruthy();
7
- expect(result).toContain("L1");
8
- expect(result).toContain("Cap.");
9
- expect(result).toContain("mcp-wrapper");
10
- });
11
- it("L2 with projectRole returns non-empty string containing L2", () => {
12
- const result = buildSkillTemplateMarkdown("L2", "api-service");
13
- expect(result.length).toBeGreaterThan(0);
14
- expect(result).toContain("L2");
15
- expect(result).toContain("api-service");
16
- });
17
- it("L3 with projectRole returns non-empty string containing L3", () => {
18
- const result = buildSkillTemplateMarkdown("L3", "platform-service");
19
- expect(result.length).toBeGreaterThan(0);
20
- expect(result).toContain("L3");
21
- });
22
- it("invalid riskLevel L9 throws Error mentioning L9", () => {
23
- expect(() => buildSkillTemplateMarkdown("L9", "x")).toThrow(Error);
24
- expect(() => buildSkillTemplateMarkdown("L9", "x")).toThrow("L9");
25
- });
26
- it("output contains all required sections", () => {
27
- const result = buildSkillTemplateMarkdown("L1", "mcp-wrapper");
28
- expect(result).toContain("# SbD-ToE Skill Template");
29
- expect(result).toContain("## Classificação de Risco");
30
- expect(result).toContain("## Capítulos Activos");
31
- expect(result).toContain("## Capítulos Condicionais");
32
- expect(result).toContain("## Triggers de Consulta");
33
- expect(result).toContain("## Requisitos Base");
34
- });
35
- it("L1 active chapters are all present in output", () => {
36
- const result = buildSkillTemplateMarkdown("L1", "x");
37
- expect(result).toContain("Cap. 01");
38
- expect(result).toContain("Cap. 02");
39
- expect(result).toContain("Cap. 06");
40
- expect(result).toContain("Cap. 10");
41
- });
42
- it("L2 has wider active list than L1", () => {
43
- const r1 = buildSkillTemplateMarkdown("L1", "x");
44
- const r2 = buildSkillTemplateMarkdown("L2", "x");
45
- // L2 has Cap. 03 and Cap. 04 active; they are only conditional in L1
46
- expect(r2).toContain("Cap. 03");
47
- expect(r2).toContain("Cap. 04");
48
- // L1 lists Cap. 03 as conditional
49
- expect(r1).toContain("Cap. 03");
50
- });
51
- });
52
- describe("buildChapterApplicabilityJson", () => {
53
- it("L1 returns object with riskLevel and active/conditional/excluded as arrays", () => {
54
- const result = buildChapterApplicabilityJson("L1");
55
- expect(result["riskLevel"]).toBe("L1");
56
- expect(Array.isArray(result["active"])).toBe(true);
57
- expect(Array.isArray(result["conditional"])).toBe(true);
58
- expect(Array.isArray(result["excluded"])).toBe(true);
59
- expect(result["active"].length).toBeGreaterThan(0);
60
- });
61
- it("L2 returns object with riskLevel and all arrays", () => {
62
- const result = buildChapterApplicabilityJson("L2");
63
- expect(result["riskLevel"]).toBe("L2");
64
- expect(Array.isArray(result["active"])).toBe(true);
65
- expect(Array.isArray(result["conditional"])).toBe(true);
66
- expect(Array.isArray(result["excluded"])).toBe(true);
67
- expect(result["active"].length).toBeGreaterThan(0);
68
- });
69
- it("L3 returns object with riskLevel and all arrays; excluded is empty", () => {
70
- const result = buildChapterApplicabilityJson("L3");
71
- expect(result["riskLevel"]).toBe("L3");
72
- expect(Array.isArray(result["active"])).toBe(true);
73
- expect(Array.isArray(result["conditional"])).toBe(true);
74
- expect(Array.isArray(result["excluded"])).toBe(true);
75
- expect(result["excluded"].length).toBe(0);
76
- });
77
- it("invalid riskLevel X throws Error mentioning X", () => {
78
- expect(() => buildChapterApplicabilityJson("X")).toThrow(Error);
79
- expect(() => buildChapterApplicabilityJson("X")).toThrow("X");
80
- });
81
- it("L1 active list contains expected chapters from docs/sbd-toe-applicability.md", () => {
82
- const result = buildChapterApplicabilityJson("L1");
83
- expect(result.active).toContain("Cap. 01");
84
- expect(result.active).toContain("Cap. 02");
85
- expect(result.active).toContain("Cap. 05");
86
- expect(result.active).toContain("Cap. 06");
87
- expect(result.active).toContain("Cap. 07");
88
- expect(result.active).toContain("Cap. 10");
89
- });
90
- it("L1 conditional list contains Cap. 03, Cap. 04, Cap. 11, Cap. 12, Cap. 14", () => {
91
- const result = buildChapterApplicabilityJson("L1");
92
- expect(result.conditional).toContain("Cap. 03");
93
- expect(result.conditional).toContain("Cap. 04");
94
- expect(result.conditional).toContain("Cap. 11");
95
- expect(result.conditional).toContain("Cap. 12");
96
- expect(result.conditional).toContain("Cap. 14");
97
- });
98
- });
99
- describe("buildSetupAgentPrompt", () => {
100
- it("L1 returns non-empty string containing L1 and Cap. 01", () => {
101
- const result = buildSetupAgentPrompt("L1");
102
- expect(result.length).toBeGreaterThan(0);
103
- expect(result).toContain("L1");
104
- expect(result).toContain("Cap. 01");
105
- });
106
- it("L1 with projectRole includes projectRole in text", () => {
107
- const result = buildSetupAgentPrompt("L1", "mcp-wrapper");
108
- expect(result).toContain("mcp-wrapper");
109
- });
110
- it("L1 without projectRole still contains mandatory rules", () => {
111
- const result = buildSetupAgentPrompt("L1");
112
- expect(result).toContain("Cap. 01 → Cap. 02");
113
- expect(result).toContain("search_sbd_toe_manual");
114
- });
115
- it("L2 includes L2 active chapters", () => {
116
- const result = buildSetupAgentPrompt("L2");
117
- expect(result).toContain("L2");
118
- expect(result).toContain("Cap. 03");
119
- expect(result).toContain("Cap. 04");
120
- });
121
- it("L3 has no conditional chapters", () => {
122
- const result = buildSetupAgentPrompt("L3");
123
- expect(result).toContain("L3");
124
- expect(result).toContain("nenhum");
125
- });
126
- it("invalid L4 riskLevel throws Error mentioning L4", () => {
127
- expect(() => buildSetupAgentPrompt("L4")).toThrow(Error);
128
- expect(() => buildSetupAgentPrompt("L4")).toThrow("L4");
129
- });
130
- it("invalid empty string riskLevel throws Error", () => {
131
- expect(() => buildSetupAgentPrompt("")).toThrow(Error);
132
- });
133
- });
134
- //# sourceMappingURL=sbd-toe-resources.test.js.map