@payconductor/houndsman 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/package.json +1 -1
- package/src/cli/commands/context-command.ts +11 -9
- package/src/cli/commands/doctor-command.ts +5 -0
- package/src/cli/commands/findings-command.ts +13 -3
- package/src/cli/commands/review-command.ts +1 -2
- package/src/cli/help.ts +7 -3
- package/src/cli/index.ts +2 -2
- package/src/core/commands/context.ts +29 -13
- package/src/core/commands/findings.ts +155 -120
- package/src/core/commands/review.ts +7 -8
- package/src/core/commands/scaffold.ts +15 -16
- package/src/core/config.ts +16 -7
- package/src/core/engine/houndsman-engine.ts +15 -1
- package/src/core/engine/review-session.ts +37 -9
- package/src/core/findings-state.ts +32 -71
- package/src/core/jev/jev-candidates.ts +49 -35
- package/src/core/orchestrator.ts +175 -128
- package/src/core/paths.ts +23 -17
- package/src/core/pr-context.ts +19 -5
- package/src/core/project-config.ts +4 -5
- package/src/core/renderers/manifest-writer.ts +29 -18
- package/src/core/run-layout.ts +4 -104
- package/src/core/run-status.ts +28 -53
- package/src/core/services/git-repository.ts +16 -0
- package/src/core/services/project-files.ts +13 -1
- package/src/core/storage/factory.ts +24 -0
- package/src/core/storage/file/file-storage.ts +269 -0
- package/src/core/storage/paths.ts +64 -0
- package/src/core/storage/repo-identity.ts +70 -0
- package/src/core/storage/sqlite/mappers.ts +160 -0
- package/src/core/storage/sqlite/schema.ts +108 -0
- package/src/core/storage/sqlite/sqlite-storage.ts +427 -0
- package/src/core/storage/types.ts +143 -0
- package/src/core/storage/work-area.ts +191 -0
- package/src/index.ts +27 -6
- package/src/server/artifacts.ts +8 -61
- package/src/server/client/ArtifactsPanel.tsx +137 -23
- package/src/server/client/FindingCard.tsx +37 -6
- package/src/server/client/MarkdownText.tsx +101 -0
- package/src/server/client/SubmitModal.tsx +9 -4
- package/src/server/client/code-highlight.ts +422 -0
- package/src/server/client/constants.ts +130 -24
- package/src/server/client/json-highlight.ts +55 -0
- package/src/server/client/markdown.ts +153 -0
- package/src/server/client/types.ts +23 -0
- package/src/server/index.ts +95 -15
- package/src/server/run-repository.ts +144 -136
- package/src/skill/OUTPUT-FORMAT.md +6 -4
- package/src/skill/README.md +22 -14
- package/src/skill/SETUP.md +4 -4
- package/src/skill/fix.md +5 -5
- package/src/skill/hunt.md +27 -18
- package/src/skill/train.md +1 -1
- package/src/skill/verify.md +5 -5
- package/src/tests/__snapshots__/baseline.test.ts.snap +44 -33
- package/src/tests/baseline.test.ts +76 -34
- package/src/tests/cli.test.ts +40 -19
- package/src/tests/code-highlight.test.ts +123 -0
- package/src/tests/domain.test.ts +1 -1
- package/src/tests/findings-commands.test.ts +153 -131
- package/src/tests/findings-state.test.ts +6 -3
- package/src/tests/fixtures/fake-project.ts +0 -1
- package/src/tests/infrastructure.test.ts +33 -38
- package/src/tests/markdown.test.ts +89 -0
- package/src/tests/review-scripts.test.ts +8 -5
- package/src/tests/run-status.test.ts +33 -23
- package/src/tests/server.test.ts +108 -75
- package/src/tests/storage.test.ts +386 -0
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
* Configuração do projeto (domínio) — resolve defaults e expõe o registry.
|
|
3
3
|
*
|
|
4
4
|
* Evita o "data clump" de passar `HoundsmanConfig` para dezenas de funções:
|
|
5
|
-
* quem precisa de
|
|
5
|
+
* quem precisa de orçamento/concorrência/watchdogs pede aqui.
|
|
6
6
|
* O contrato do consumidor (`houndsman.config.ts`) não muda.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { HoundsmanConfig } from "./config";
|
|
9
|
+
import type { HoundsmanConfig, HoundsmanStorage } from "./config";
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_ALWAYS_INCLUDE_DOCS,
|
|
12
12
|
DEFAULT_JEV_CONCURRENCY,
|
|
13
13
|
DEFAULT_MAX_FILES_PER_BRIEF,
|
|
14
14
|
} from "./defaults";
|
|
15
|
-
import { ARTIFACT_DIR } from "./paths";
|
|
16
15
|
import { WatchdogRegistry } from "./watchdog-registry";
|
|
17
16
|
|
|
18
17
|
/** Configuração resolvida do projeto. */
|
|
@@ -29,8 +28,8 @@ export class ProjectConfig<TId extends string = string> {
|
|
|
29
28
|
return this.raw.projectName;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
get
|
|
33
|
-
return this.raw.
|
|
31
|
+
get storage(): HoundsmanStorage {
|
|
32
|
+
return this.raw.storage ?? {};
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
get maxFilesPerBrief(): number {
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Manifesto da run (biblioteca) — monta e grava o `manifest.json`.
|
|
2
|
+
* Manifesto da run (biblioteca) — monta e grava o artefato `manifest.json`.
|
|
3
3
|
*
|
|
4
4
|
* Concentra o resumo do que foi ativado (watchdogs, orçamento, churn, contexto,
|
|
5
|
-
* Jev e incremental)
|
|
5
|
+
* Jev e incremental). O manifesto vive no storage; os caminhos de briefs e
|
|
6
|
+
* findings apontam para o staging da review.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { Classification } from "../domain/classification";
|
|
9
10
|
import type { ClassificationResult } from "../file-classifier";
|
|
10
11
|
import type { JevCandidatesFile } from "../jev";
|
|
11
|
-
import { OUTPUT_FORMAT_PATH
|
|
12
|
-
import { readContextSummary } from "../pr-context";
|
|
12
|
+
import { OUTPUT_FORMAT_PATH } from "../paths";
|
|
13
13
|
import type { ProjectConfig } from "../project-config";
|
|
14
14
|
import type { MergedFinding } from "../report-builder";
|
|
15
|
-
import type { RunLayout } from "../run-layout";
|
|
16
15
|
import { systemClock } from "../services/clock";
|
|
17
|
-
import {
|
|
16
|
+
import type { ReviewRecord, StorageProvider } from "../storage/types";
|
|
17
|
+
import type { ReviewWorkArea } from "../storage/work-area";
|
|
18
18
|
|
|
19
19
|
/** Opções do manifesto (subset do `ReviewOptions`). */
|
|
20
20
|
export type ManifestOptions = {
|
|
@@ -32,12 +32,22 @@ export type ManifestClassification = ClassificationResult & {
|
|
|
32
32
|
|
|
33
33
|
/** Escreve o `manifest.json` de uma run. */
|
|
34
34
|
export class ManifestWriter {
|
|
35
|
-
private readonly
|
|
35
|
+
private readonly storage: StorageProvider;
|
|
36
|
+
private readonly review: ReviewRecord;
|
|
37
|
+
private readonly work: ReviewWorkArea;
|
|
36
38
|
private readonly project: ProjectConfig;
|
|
37
39
|
private readonly options: ManifestOptions;
|
|
38
40
|
|
|
39
|
-
constructor(
|
|
40
|
-
|
|
41
|
+
constructor(
|
|
42
|
+
storage: StorageProvider,
|
|
43
|
+
review: ReviewRecord,
|
|
44
|
+
work: ReviewWorkArea,
|
|
45
|
+
project: ProjectConfig,
|
|
46
|
+
options: ManifestOptions,
|
|
47
|
+
) {
|
|
48
|
+
this.storage = storage;
|
|
49
|
+
this.review = review;
|
|
50
|
+
this.work = work;
|
|
41
51
|
this.project = project;
|
|
42
52
|
this.options = options;
|
|
43
53
|
}
|
|
@@ -60,16 +70,16 @@ export class ManifestWriter {
|
|
|
60
70
|
name: this.project.watchdogs.nameOf(id),
|
|
61
71
|
files: selection.selected,
|
|
62
72
|
overflow: selection.overflow.length,
|
|
63
|
-
brief:
|
|
64
|
-
findings:
|
|
73
|
+
brief: this.work.briefFile(id),
|
|
74
|
+
findings: this.work.findingFile(id),
|
|
65
75
|
status: pending.includes(id) ? "pending" : "done",
|
|
66
76
|
};
|
|
67
77
|
});
|
|
68
78
|
|
|
69
|
-
const summary =
|
|
79
|
+
const summary = this.work.readContextSummary();
|
|
70
80
|
const manifest = {
|
|
71
|
-
branch: this.
|
|
72
|
-
slug: this.
|
|
81
|
+
branch: this.review.branch,
|
|
82
|
+
slug: this.review.slug,
|
|
73
83
|
base: this.options.base,
|
|
74
84
|
head: this.options.head,
|
|
75
85
|
headSha: classification.headSha,
|
|
@@ -104,9 +114,10 @@ export class ManifestWriter {
|
|
|
104
114
|
excludedCount: classification.excludedCount,
|
|
105
115
|
};
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
this.storage.writeArtifact(this.review.id, {
|
|
118
|
+
name: "manifest.json",
|
|
119
|
+
kind: "manifest",
|
|
120
|
+
content: `${JSON.stringify(manifest, null, 2)}\n`,
|
|
121
|
+
});
|
|
111
122
|
}
|
|
112
123
|
}
|
package/src/core/run-layout.ts
CHANGED
|
@@ -1,108 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Removido: o layout de artefatos do projeto deu lugar ao staging global por
|
|
3
|
+
* review (`ReviewWorkArea` em `core/storage/work-area.ts`).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* motor: quem precisa de um caminho pede ao layout.
|
|
5
|
+
* Mantido apenas para não quebrar imports antigos de tipos.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
CONTEXT_BRIEF_FILE,
|
|
12
|
-
CONTEXT_FILE,
|
|
13
|
-
CONTEXT_SUMMARY_FILE,
|
|
14
|
-
RUN_STATUS_FILE,
|
|
15
|
-
STATE_FILE,
|
|
16
|
-
VERIFY_FILE,
|
|
17
|
-
} from "./paths";
|
|
18
|
-
import { sanitizeBranch } from "./report-builder";
|
|
19
|
-
|
|
20
|
-
/** Caminhos dos artefatos de contexto da PR. */
|
|
21
|
-
export type ContextPaths = {
|
|
22
|
-
dir: string;
|
|
23
|
-
context: string;
|
|
24
|
-
summary: string;
|
|
25
|
-
brief: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/** Diretório e caminhos de uma run (branch-slug). */
|
|
29
|
-
export class RunLayout {
|
|
30
|
-
readonly artifactDir: string;
|
|
31
|
-
readonly branch: string;
|
|
32
|
-
|
|
33
|
-
constructor(artifactDir: string, branch: string) {
|
|
34
|
-
this.artifactDir = artifactDir;
|
|
35
|
-
this.branch = branch;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get slug(): string {
|
|
39
|
-
return sanitizeBranch(this.branch);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
get outDir(): string {
|
|
43
|
-
return join(this.artifactDir, this.slug);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get briefsDir(): string {
|
|
47
|
-
return join(this.outDir, "briefs");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get findingsDir(): string {
|
|
51
|
-
return join(this.outDir, "findings");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Caminho de um arquivo dentro da pasta da run. */
|
|
55
|
-
file(name: string): string {
|
|
56
|
-
return join(this.outDir, name);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
briefFile(id: string): string {
|
|
60
|
-
return join(this.briefsDir, `${id}.md`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
findingFile(id: string): string {
|
|
64
|
-
return join(this.findingsDir, `${id}.json`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
get classificationFile(): string {
|
|
68
|
-
return this.file("classification.json");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
get manifestFile(): string {
|
|
72
|
-
return this.file("manifest.json");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
get candidatesFile(): string {
|
|
76
|
-
return this.file("candidates.json");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get reviewFile(): string {
|
|
80
|
-
return this.file("review.md");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
get stateFile(): string {
|
|
84
|
-
return this.file(STATE_FILE);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
get runStatusFile(): string {
|
|
88
|
-
return this.file(RUN_STATUS_FILE);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
get verifyFile(): string {
|
|
92
|
-
return this.file(VERIFY_FILE);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Relatório isolado de um watchdog (`review.<id>.md`). */
|
|
96
|
-
domainReportFile(id: string): string {
|
|
97
|
-
return this.file(`review.${sanitizeBranch(id)}.md`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
get contextPaths(): ContextPaths {
|
|
101
|
-
return {
|
|
102
|
-
dir: this.outDir,
|
|
103
|
-
context: this.file(CONTEXT_FILE),
|
|
104
|
-
summary: this.file(CONTEXT_SUMMARY_FILE),
|
|
105
|
-
brief: this.file(CONTEXT_BRIEF_FILE),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
8
|
+
export type { ContextPaths } from "./storage/work-area";
|
package/src/core/run-status.ts
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Status do run (biblioteca) —
|
|
2
|
+
* Status do run (biblioteca) — colunas da review no storage.
|
|
3
3
|
*
|
|
4
4
|
* Diz à UI em que pé está uma run: `reviewing` (revisando) → `review_done`
|
|
5
5
|
* (revisão finalizada) → `fixing` (corrigindo) → `fix_done` (correção finalizada).
|
|
6
6
|
*
|
|
7
|
-
* Toda ação que muda o estado faz um **ping** no `updatedAt
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Toda ação que muda o estado faz um **ping** no `updatedAt`. Timeout é
|
|
8
|
+
* **display-only** (a UI compara `updatedAt` com o agora) — não é um status
|
|
9
|
+
* persistido.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { join } from "node:path";
|
|
13
12
|
import type { FindingsStateFile } from "./findings-state";
|
|
14
|
-
import { RUN_STATUS_FILE } from "./paths";
|
|
15
|
-
import { sanitizeBranch } from "./report-builder";
|
|
16
13
|
import { systemClock } from "./services/clock";
|
|
17
|
-
import {
|
|
14
|
+
import type { ReviewRecord, ReviewStatus, StorageProvider } from "./storage/types";
|
|
18
15
|
|
|
19
16
|
/** Estados de execução de uma run (persistidos). */
|
|
20
|
-
export type RunStatus =
|
|
17
|
+
export type RunStatus = ReviewStatus;
|
|
21
18
|
|
|
22
19
|
/** Valores canônicos de status (ordem de exibição/lifecycle). */
|
|
23
20
|
export const RUN_STATUSES: readonly RunStatus[] = [
|
|
@@ -27,56 +24,34 @@ export const RUN_STATUSES: readonly RunStatus[] = [
|
|
|
27
24
|
"fix_done",
|
|
28
25
|
];
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
branch: string;
|
|
37
|
-
status: RunStatus;
|
|
38
|
-
/** Início do ciclo de revisão atual (reiniciado a cada `prepare`). */
|
|
39
|
-
startedAt: string;
|
|
40
|
-
/** Última ação importante (ping). */
|
|
41
|
-
updatedAt: string;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** Caminho do `run.json` dentro da pasta da run. */
|
|
45
|
-
export function runStatusPath(runDir: string): string {
|
|
46
|
-
return join(runDir, RUN_STATUS_FILE);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Lê o `run.json` da run (null se ausente/inválido). */
|
|
50
|
-
export function readRunStatus(runDir: string): RunStatusFile | null {
|
|
51
|
-
return new ProjectFiles().readJson<RunStatusFile>(runStatusPath(runDir));
|
|
27
|
+
/** Lê o status atual (null se a review não existir). */
|
|
28
|
+
export function readRunStatus(
|
|
29
|
+
storage: StorageProvider,
|
|
30
|
+
reviewId: string,
|
|
31
|
+
): ReviewRecord | null {
|
|
32
|
+
return storage.getReview(reviewId);
|
|
52
33
|
}
|
|
53
34
|
|
|
54
35
|
export type WriteRunStatusOptions = {
|
|
55
|
-
branch: string;
|
|
56
36
|
status: RunStatus;
|
|
57
37
|
/** Reinicia o `startedAt` — usado no início de uma nova revisão. */
|
|
58
38
|
resetStartedAt?: boolean;
|
|
59
39
|
now?: string;
|
|
60
40
|
};
|
|
61
41
|
|
|
62
|
-
/** Grava/atualiza o
|
|
42
|
+
/** Grava/atualiza o status da review (ping no `updatedAt`). */
|
|
63
43
|
export function writeRunStatus(
|
|
64
|
-
|
|
44
|
+
storage: StorageProvider,
|
|
45
|
+
reviewId: string,
|
|
65
46
|
options: WriteRunStatusOptions,
|
|
66
|
-
):
|
|
67
|
-
const
|
|
68
|
-
const now = options.now ?? systemClock();
|
|
69
|
-
const previous = readRunStatus(runDir);
|
|
70
|
-
const file: RunStatusFile = {
|
|
71
|
-
version: RUN_STATUS_VERSION,
|
|
72
|
-
slug: sanitizeBranch(options.branch),
|
|
73
|
-
branch: options.branch,
|
|
47
|
+
): ReviewRecord {
|
|
48
|
+
const updated = storage.updateReview(reviewId, {
|
|
74
49
|
status: options.status,
|
|
75
|
-
|
|
76
|
-
updatedAt: now,
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
return
|
|
50
|
+
resetStartedAt: options.resetStartedAt,
|
|
51
|
+
updatedAt: options.now ?? systemClock(),
|
|
52
|
+
});
|
|
53
|
+
if (!updated) throw new Error(`Review '${reviewId}' não encontrada.`);
|
|
54
|
+
return updated;
|
|
80
55
|
}
|
|
81
56
|
|
|
82
57
|
/**
|
|
@@ -105,14 +80,14 @@ export function statusAfterFindingsChange(
|
|
|
105
80
|
return previous ?? "review_done";
|
|
106
81
|
}
|
|
107
82
|
|
|
108
|
-
/** Ping: deriva o status da mudança nos findings e grava
|
|
83
|
+
/** Ping: deriva o status da mudança nos findings e grava na review. */
|
|
109
84
|
export function pingFindingsStatus(
|
|
110
|
-
|
|
85
|
+
storage: StorageProvider,
|
|
86
|
+
reviewId: string,
|
|
111
87
|
state: FindingsStateFile,
|
|
112
|
-
):
|
|
113
|
-
const previous =
|
|
114
|
-
return writeRunStatus(
|
|
115
|
-
branch: state.branch,
|
|
88
|
+
): ReviewRecord {
|
|
89
|
+
const previous = storage.getReview(reviewId)?.status ?? null;
|
|
90
|
+
return writeRunStatus(storage, reviewId, {
|
|
116
91
|
status: statusAfterFindingsChange(previous, state),
|
|
117
92
|
});
|
|
118
93
|
}
|
|
@@ -52,6 +52,22 @@ export class GitRepository {
|
|
|
52
52
|
return branch.length > 0 ? branch : null;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** Raiz do repositório; `null` fora de um repo git. */
|
|
56
|
+
rootPath(): string | null {
|
|
57
|
+
const result = this.tryRun(["rev-parse", "--show-toplevel"]);
|
|
58
|
+
if (!result.ok) return null;
|
|
59
|
+
const root = result.stdout.trim();
|
|
60
|
+
return root.length > 0 ? root : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** URL de um remote (default `origin`); `null` se ausente. */
|
|
64
|
+
remoteUrl(name = "origin"): string | null {
|
|
65
|
+
const result = this.tryRun(["remote", "get-url", name]);
|
|
66
|
+
if (!result.ok) return null;
|
|
67
|
+
const url = result.stdout.trim();
|
|
68
|
+
return url.length > 0 ? url : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
/**
|
|
56
72
|
* `true` se `ancestor` é ancestral de `descendant`. Qualquer outro desfecho
|
|
57
73
|
* (ref desconhecida, exit ≠ 0) devolve `false` — cai no review completo.
|
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
* manter o comportamento fail-open do motor.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
existsSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
10
17
|
import { dirname, isAbsolute, join } from "node:path";
|
|
11
18
|
|
|
12
19
|
/** Leitor de arquivos relativos à raiz do projeto (injetável nos testes). */
|
|
@@ -67,6 +74,11 @@ export class ProjectFiles {
|
|
|
67
74
|
mkdirSync(this.resolve(path), { recursive: true });
|
|
68
75
|
}
|
|
69
76
|
|
|
77
|
+
/** Remove um diretório (recursivo, best-effort). */
|
|
78
|
+
removeDir(path: string): void {
|
|
79
|
+
rmSync(this.resolve(path), { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
/** `true` se o caminho existe. */
|
|
71
83
|
exists(path: string): boolean {
|
|
72
84
|
return existsSync(this.resolve(path));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fábrica do provider de persistência (biblioteca).
|
|
3
|
+
*
|
|
4
|
+
* Default: SQLite no diretório global. `storage.provider: "file"` mantém o
|
|
5
|
+
* backend em arquivos sob o mesmo home — útil para debug ou rollback.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { HoundsmanConfig } from "../config";
|
|
9
|
+
import { FileStorageProvider } from "./file/file-storage";
|
|
10
|
+
import { databasePath, fileRoot } from "./paths";
|
|
11
|
+
import { SQLiteStorageProvider } from "./sqlite/sqlite-storage";
|
|
12
|
+
import type { StorageProvider } from "./types";
|
|
13
|
+
|
|
14
|
+
/** Cria o provider configurado pelo projeto. */
|
|
15
|
+
export function createStorage(config: HoundsmanConfig): StorageProvider {
|
|
16
|
+
return config.storage?.provider === "file"
|
|
17
|
+
? new FileStorageProvider(fileRoot(config))
|
|
18
|
+
: new SQLiteStorageProvider(databasePath(config));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Provider SQLite em memória (testes). */
|
|
22
|
+
export function createMemoryStorage(): StorageProvider {
|
|
23
|
+
return new SQLiteStorageProvider(":memory:");
|
|
24
|
+
}
|