@promovaweb/specsfy 0.7.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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +248 -0
- package/bin/specsfy.cjs +3 -0
- package/dist/backlog.d.ts +17 -0
- package/dist/backlog.d.ts.map +1 -0
- package/dist/backlog.js +73 -0
- package/dist/backlog.js.map +1 -0
- package/dist/catalog.d.ts +33 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +135 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +258 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/filesystem.d.ts +19 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +67 -0
- package/dist/filesystem.js.map +1 -0
- package/dist/github.d.ts +8 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +36 -0
- package/dist/github.js.map +1 -0
- package/dist/installer.d.ts +48 -0
- package/dist/installer.d.ts.map +1 -0
- package/dist/installer.js +613 -0
- package/dist/installer.js.map +1 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +13 -0
- package/dist/main.js.map +1 -0
- package/dist/progress.d.ts +46 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +148 -0
- package/dist/progress.js.map +1 -0
- package/dist/project-testing.d.ts +27 -0
- package/dist/project-testing.d.ts.map +1 -0
- package/dist/project-testing.js +150 -0
- package/dist/project-testing.js.map +1 -0
- package/dist/skill-lock.d.ts +23 -0
- package/dist/skill-lock.d.ts.map +1 -0
- package/dist/skill-lock.js +80 -0
- package/dist/skill-lock.js.map +1 -0
- package/dist/tui.d.ts +141 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +1219 -0
- package/dist/tui.js.map +1 -0
- package/dist/updater.d.ts +41 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +221 -0
- package/dist/updater.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +58 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processo executável do Specsfy CLI.
|
|
3
|
+
*/
|
|
4
|
+
import { runCli } from "./cli.js";
|
|
5
|
+
void runCli()
|
|
6
|
+
.then((exitCode) => {
|
|
7
|
+
process.exitCode = exitCode;
|
|
8
|
+
})
|
|
9
|
+
.catch((error) => {
|
|
10
|
+
console.error(`erro: ${error instanceof Error ? error.message : String(error)}`);
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,KAAK,MAAM,EAAE;KACV,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;IACjB,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,SAAS,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projeção de progresso das especificações Markdown.
|
|
3
|
+
*/
|
|
4
|
+
/** Métricas e conteúdo de uma especificação. */
|
|
5
|
+
export interface SpecProgress {
|
|
6
|
+
slug: string;
|
|
7
|
+
title: string;
|
|
8
|
+
path: string;
|
|
9
|
+
content: string;
|
|
10
|
+
status: string;
|
|
11
|
+
definition_gate: string;
|
|
12
|
+
plan_gate: string;
|
|
13
|
+
delivery_gate: string;
|
|
14
|
+
passed_gates: number;
|
|
15
|
+
total_gates: number;
|
|
16
|
+
completed_tasks: number;
|
|
17
|
+
pending_tasks: number;
|
|
18
|
+
total_tasks: number;
|
|
19
|
+
completed_items: number;
|
|
20
|
+
pending_items: number;
|
|
21
|
+
total_items: number;
|
|
22
|
+
percent: number;
|
|
23
|
+
}
|
|
24
|
+
/** Totais consolidados de uma coleção de specs. */
|
|
25
|
+
export interface ProgressSummary {
|
|
26
|
+
total_specs: number;
|
|
27
|
+
completed_specs: number;
|
|
28
|
+
completed_tasks: number;
|
|
29
|
+
pending_tasks: number;
|
|
30
|
+
total_tasks: number;
|
|
31
|
+
completed_items: number;
|
|
32
|
+
pending_items: number;
|
|
33
|
+
total_items: number;
|
|
34
|
+
passed_gates: number;
|
|
35
|
+
total_gates: number;
|
|
36
|
+
percent: number;
|
|
37
|
+
}
|
|
38
|
+
/** Lê specs no layout canônico e no layout legado. */
|
|
39
|
+
export declare function scanSpecs(project: string): Promise<SpecProgress[]>;
|
|
40
|
+
/** Consolida as métricas das specs fornecidas. */
|
|
41
|
+
export declare function summarizeSpecs(specs: SpecProgress[]): ProgressSummary;
|
|
42
|
+
/** Calcula o fingerprint dos caminhos e conteúdos de spec. */
|
|
43
|
+
export declare function specsFingerprint(project: string): Promise<string>;
|
|
44
|
+
/** Remove o conteúdo bruto antes de serializar a resposta pública. */
|
|
45
|
+
export declare function serializeSpec(spec: SpecProgress): Omit<SpecProgress, "content">;
|
|
46
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../src/progress.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sDAAsD;AACtD,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAGxE;AAED,kDAAkD;AAClD,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,eAAe,CAwBrE;AAED,8DAA8D;AAC9D,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAcvE;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAG/E"}
|
package/dist/progress.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projeção de progresso das especificações Markdown.
|
|
3
|
+
*/
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
6
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
7
|
+
import { isFile, resolvePath } from "./filesystem.js";
|
|
8
|
+
const FIELD = /^\*\*([^*]+)\*\*:\s*(.+?)\s*$/;
|
|
9
|
+
const TABLE_FIELD = /^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*$/;
|
|
10
|
+
const CHECKLIST_ITEM = /^\s*[-*+]\s*\[([ xX])\]\s+(.+?)\s*$/;
|
|
11
|
+
const TASK_ID = /^(?:\*\*|`)?T\d+\b/i;
|
|
12
|
+
const GATE_NAMES = ["definition gate", "plan gate", "delivery gate"];
|
|
13
|
+
/** Lê specs no layout canônico e no layout legado. */
|
|
14
|
+
export async function scanSpecs(project) {
|
|
15
|
+
const root = resolvePath(project);
|
|
16
|
+
return Promise.all((await specPaths(root)).map(parseSpec));
|
|
17
|
+
}
|
|
18
|
+
/** Consolida as métricas das specs fornecidas. */
|
|
19
|
+
export function summarizeSpecs(specs) {
|
|
20
|
+
const completedItems = sum(specs, "completed_items");
|
|
21
|
+
const totalItems = sum(specs, "total_items");
|
|
22
|
+
const completedTasks = sum(specs, "completed_tasks");
|
|
23
|
+
const totalTasks = sum(specs, "total_tasks");
|
|
24
|
+
const passedGates = sum(specs, "passed_gates");
|
|
25
|
+
const totalGates = sum(specs, "total_gates");
|
|
26
|
+
return {
|
|
27
|
+
total_specs: specs.length,
|
|
28
|
+
completed_specs: specs.filter((spec) => spec.status.toLowerCase() === "complete")
|
|
29
|
+
.length,
|
|
30
|
+
completed_tasks: completedTasks,
|
|
31
|
+
pending_tasks: totalTasks - completedTasks,
|
|
32
|
+
total_tasks: totalTasks,
|
|
33
|
+
completed_items: completedItems,
|
|
34
|
+
pending_items: totalItems - completedItems,
|
|
35
|
+
total_items: totalItems,
|
|
36
|
+
passed_gates: passedGates,
|
|
37
|
+
total_gates: totalGates,
|
|
38
|
+
percent: ratio(totalItems ? completedItems : passedGates, totalItems ? totalItems : totalGates),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Calcula o fingerprint dos caminhos e conteúdos de spec. */
|
|
42
|
+
export async function specsFingerprint(project) {
|
|
43
|
+
const root = resolvePath(project);
|
|
44
|
+
const digest = createHash("sha256");
|
|
45
|
+
for (const path of await specPaths(root)) {
|
|
46
|
+
digest.update(relative(root, path));
|
|
47
|
+
digest.update("\0");
|
|
48
|
+
try {
|
|
49
|
+
digest.update(await readFile(path));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
digest.update("\0");
|
|
55
|
+
}
|
|
56
|
+
return digest.digest("hex");
|
|
57
|
+
}
|
|
58
|
+
/** Remove o conteúdo bruto antes de serializar a resposta pública. */
|
|
59
|
+
export function serializeSpec(spec) {
|
|
60
|
+
const { content: _content, ...serialized } = spec;
|
|
61
|
+
return serialized;
|
|
62
|
+
}
|
|
63
|
+
async function specPaths(root) {
|
|
64
|
+
const paths = new Set();
|
|
65
|
+
for (const parent of [
|
|
66
|
+
join(root, "specs", "specs"),
|
|
67
|
+
join(root, "specs"),
|
|
68
|
+
]) {
|
|
69
|
+
let entries;
|
|
70
|
+
try {
|
|
71
|
+
entries = await readdir(parent, { withFileTypes: true });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (!entry.isDirectory())
|
|
78
|
+
continue;
|
|
79
|
+
const path = join(parent, entry.name, "spec.md");
|
|
80
|
+
if (await isFile(path))
|
|
81
|
+
paths.add(path);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...paths].sort();
|
|
85
|
+
}
|
|
86
|
+
async function parseSpec(path) {
|
|
87
|
+
const content = await readFile(path, "utf8");
|
|
88
|
+
const slug = basename(dirname(path));
|
|
89
|
+
let title = slug;
|
|
90
|
+
let completedTasks = 0;
|
|
91
|
+
let totalTasks = 0;
|
|
92
|
+
let completedItems = 0;
|
|
93
|
+
let totalItems = 0;
|
|
94
|
+
const fields = new Map();
|
|
95
|
+
for (const line of content.split(/\r?\n/u)) {
|
|
96
|
+
if (line.startsWith("# ") && title === slug)
|
|
97
|
+
title = line.slice(2).trim();
|
|
98
|
+
const field = FIELD.exec(line) ?? TABLE_FIELD.exec(line);
|
|
99
|
+
if (field?.[1] && field[2]) {
|
|
100
|
+
fields.set(field[1].trim().toLowerCase(), field[2].trim());
|
|
101
|
+
}
|
|
102
|
+
const item = CHECKLIST_ITEM.exec(line);
|
|
103
|
+
if (!item)
|
|
104
|
+
continue;
|
|
105
|
+
const done = item[1]?.toLowerCase() === "x";
|
|
106
|
+
totalItems += 1;
|
|
107
|
+
completedItems += Number(done);
|
|
108
|
+
if (TASK_ID.test(item[2] ?? "")) {
|
|
109
|
+
totalTasks += 1;
|
|
110
|
+
completedTasks += Number(done);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const passedGates = GATE_NAMES.filter((name) => fields.get(name)?.toLowerCase() === "passed").length;
|
|
114
|
+
const totalGates = GATE_NAMES.length;
|
|
115
|
+
return {
|
|
116
|
+
slug,
|
|
117
|
+
title,
|
|
118
|
+
path,
|
|
119
|
+
content,
|
|
120
|
+
status: fields.get("status") ?? "Unknown",
|
|
121
|
+
definition_gate: fields.get("definition gate") ?? "Unknown",
|
|
122
|
+
plan_gate: fields.get("plan gate") ?? "Unknown",
|
|
123
|
+
delivery_gate: fields.get("delivery gate") ?? "Unknown",
|
|
124
|
+
passed_gates: passedGates,
|
|
125
|
+
total_gates: totalGates,
|
|
126
|
+
completed_tasks: completedTasks,
|
|
127
|
+
pending_tasks: totalTasks - completedTasks,
|
|
128
|
+
total_tasks: totalTasks,
|
|
129
|
+
completed_items: completedItems,
|
|
130
|
+
pending_items: totalItems - completedItems,
|
|
131
|
+
total_items: totalItems,
|
|
132
|
+
percent: ratio(totalItems ? completedItems : passedGates, totalItems ? totalItems : totalGates),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function sum(specs, key) {
|
|
136
|
+
return specs.reduce((total, spec) => total + spec[key], 0);
|
|
137
|
+
}
|
|
138
|
+
function ratio(completed, total) {
|
|
139
|
+
if (!total)
|
|
140
|
+
return 0;
|
|
141
|
+
const value = (completed * 100) / total;
|
|
142
|
+
const floor = Math.floor(value);
|
|
143
|
+
const fraction = value - floor;
|
|
144
|
+
if (fraction !== 0.5)
|
|
145
|
+
return Math.round(value);
|
|
146
|
+
return floor % 2 === 0 ? floor : floor + 1;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../src/progress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecção e execução segura dos testes do projeto consumidor.
|
|
3
|
+
*/
|
|
4
|
+
/** Comando de teste detectado sem interpretação por shell. */
|
|
5
|
+
export interface TestCommand {
|
|
6
|
+
label: string;
|
|
7
|
+
argv: readonly string[];
|
|
8
|
+
cwd: string;
|
|
9
|
+
display: string;
|
|
10
|
+
}
|
|
11
|
+
/** Resultado de uma execução do runner do consumidor. */
|
|
12
|
+
export interface TestRun {
|
|
13
|
+
command: TestCommand;
|
|
14
|
+
exit_code: number;
|
|
15
|
+
duration_seconds: number;
|
|
16
|
+
summary_lines: string[];
|
|
17
|
+
}
|
|
18
|
+
/** Detecta Laravel com Pest sem executar código do projeto. */
|
|
19
|
+
export declare function detectProjectTestCommand(project: string): Promise<TestCommand>;
|
|
20
|
+
/** Executa o runner detectado, transmite linhas e preserva o exit code. */
|
|
21
|
+
export declare function runProjectTests(project: string, emit?: (line: string) => void, signal?: AbortSignal): Promise<TestRun>;
|
|
22
|
+
/** Formata uma linha JSON estruturada do Pest ou preserva texto comum. */
|
|
23
|
+
export declare function presentOutputLine(line: string): {
|
|
24
|
+
lines: string[];
|
|
25
|
+
summary: string[];
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=project-testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-testing.d.ts","sourceRoot":"","sources":["../src/project-testing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,yDAAyD;AACzD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,+DAA+D;AAC/D,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAmBtB;AAED,2EAA2E;AAC3E,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAkB,EAC1C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAuClB;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC/C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAyCA"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecção e execução segura dos testes do projeto consumidor.
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
import { performance } from "node:perf_hooks";
|
|
9
|
+
import { isFile, pathExists, resolvePath } from "./filesystem.js";
|
|
10
|
+
import { assertConsumerProject } from "./skill-lock.js";
|
|
11
|
+
/** Detecta Laravel com Pest sem executar código do projeto. */
|
|
12
|
+
export async function detectProjectTestCommand(project) {
|
|
13
|
+
const root = resolvePath(project);
|
|
14
|
+
if (!(await pathExists(root))) {
|
|
15
|
+
throw new Error(`projeto não encontrado: ${root}`);
|
|
16
|
+
}
|
|
17
|
+
await assertConsumerProject(root);
|
|
18
|
+
if ((await isFile(join(root, "artisan"))) && (await usesPest(root))) {
|
|
19
|
+
const argv = ["php", "artisan", "test"];
|
|
20
|
+
return {
|
|
21
|
+
label: "Laravel Pest",
|
|
22
|
+
argv,
|
|
23
|
+
cwd: root,
|
|
24
|
+
display: argv.join(" "),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
throw new Error("Pest não foi detectado no projeto. " +
|
|
28
|
+
"É esperado um projeto Laravel com artisan e pestphp/pest.");
|
|
29
|
+
}
|
|
30
|
+
/** Executa o runner detectado, transmite linhas e preserva o exit code. */
|
|
31
|
+
export async function runProjectTests(project, emit = console.log, signal) {
|
|
32
|
+
const command = await detectProjectTestCommand(project);
|
|
33
|
+
const startedAt = performance.now();
|
|
34
|
+
const lines = [];
|
|
35
|
+
let structuredSummary = [];
|
|
36
|
+
const [executable, ...arguments_] = command.argv;
|
|
37
|
+
if (!executable)
|
|
38
|
+
throw new Error("comando de teste vazio");
|
|
39
|
+
const child = spawn(executable, arguments_, {
|
|
40
|
+
cwd: command.cwd,
|
|
41
|
+
env: { ...process.env, NO_COLOR: process.env.NO_COLOR ?? "1" },
|
|
42
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
43
|
+
signal,
|
|
44
|
+
});
|
|
45
|
+
if (!child.stdout)
|
|
46
|
+
throw new Error("não foi possível capturar a saída do Pest");
|
|
47
|
+
const consume = (line) => {
|
|
48
|
+
const presented = presentOutputLine(line);
|
|
49
|
+
lines.push(...presented.lines);
|
|
50
|
+
if (presented.summary.length)
|
|
51
|
+
structuredSummary = presented.summary;
|
|
52
|
+
for (const item of presented.lines)
|
|
53
|
+
emit(item);
|
|
54
|
+
};
|
|
55
|
+
createInterface({ input: child.stdout, crlfDelay: Infinity }).on("line", consume);
|
|
56
|
+
if (child.stderr) {
|
|
57
|
+
createInterface({ input: child.stderr, crlfDelay: Infinity }).on("line", consume);
|
|
58
|
+
}
|
|
59
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
60
|
+
child.once("error", reject);
|
|
61
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
command,
|
|
65
|
+
exit_code: exitCode,
|
|
66
|
+
duration_seconds: (performance.now() - startedAt) / 1000,
|
|
67
|
+
summary_lines: structuredSummary.length
|
|
68
|
+
? structuredSummary
|
|
69
|
+
: summaryLines(lines),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Formata uma linha JSON estruturada do Pest ou preserva texto comum. */
|
|
73
|
+
export function presentOutputLine(line) {
|
|
74
|
+
let payload;
|
|
75
|
+
try {
|
|
76
|
+
payload = JSON.parse(line);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return { lines: [line], summary: [] };
|
|
80
|
+
}
|
|
81
|
+
if (!payload ||
|
|
82
|
+
typeof payload !== "object" ||
|
|
83
|
+
payload.tool !== "pest" ||
|
|
84
|
+
typeof payload.result !== "string") {
|
|
85
|
+
return { lines: [line], summary: [] };
|
|
86
|
+
}
|
|
87
|
+
const value = payload;
|
|
88
|
+
const total = integer(value.tests);
|
|
89
|
+
const passed = integer(value.passed);
|
|
90
|
+
const errors = integer(value.errors);
|
|
91
|
+
const assertions = integer(value.assertions);
|
|
92
|
+
const duration = integer(value.duration_ms);
|
|
93
|
+
const summary = [
|
|
94
|
+
`Tests: ${total} total · ${passed} passed · ${errors} errors`,
|
|
95
|
+
`Assertions: ${assertions}`,
|
|
96
|
+
`Duration: ${(duration / 1000).toFixed(2)}s`,
|
|
97
|
+
];
|
|
98
|
+
const lines = [];
|
|
99
|
+
if (Array.isArray(value.error_details)) {
|
|
100
|
+
for (const detail of value.error_details) {
|
|
101
|
+
if (!detail || typeof detail !== "object")
|
|
102
|
+
continue;
|
|
103
|
+
const error = detail;
|
|
104
|
+
lines.push(`FAIL ${String(error.test ?? "Teste desconhecido")}`, ` ${String(error.file ?? "arquivo desconhecido")}:${String(error.line ?? "?")}`, ` ${String(error.message ?? "Erro sem mensagem")}`, "");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!lines.length)
|
|
108
|
+
lines.push(summary[0]);
|
|
109
|
+
return { lines, summary };
|
|
110
|
+
}
|
|
111
|
+
async function usesPest(root) {
|
|
112
|
+
if ((await isFile(join(root, "tests", "Pest.php"))) ||
|
|
113
|
+
(await isFile(join(root, "vendor", "bin", "pest")))) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
const path = join(root, "composer.json");
|
|
117
|
+
if (!(await isFile(path)))
|
|
118
|
+
return false;
|
|
119
|
+
let payload;
|
|
120
|
+
try {
|
|
121
|
+
payload = JSON.parse(await readFile(path, "utf8"));
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
+
throw new Error(`composer.json inválido em ${root}: ${message}`);
|
|
126
|
+
}
|
|
127
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
128
|
+
return false;
|
|
129
|
+
const dependencies = new Set();
|
|
130
|
+
for (const section of ["require", "require-dev"]) {
|
|
131
|
+
const value = payload[section];
|
|
132
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
133
|
+
for (const dependency of Object.keys(value))
|
|
134
|
+
dependencies.add(dependency);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return dependencies.has("pestphp/pest");
|
|
138
|
+
}
|
|
139
|
+
function summaryLines(lines) {
|
|
140
|
+
const markers = ["tests:", "duration:", "passed", "failed", "skipped", "todo"];
|
|
141
|
+
return lines
|
|
142
|
+
.map((line) => line.trim())
|
|
143
|
+
.filter((line) => line.length > 0 &&
|
|
144
|
+
markers.some((marker) => line.toLowerCase().includes(marker)))
|
|
145
|
+
.slice(-3);
|
|
146
|
+
}
|
|
147
|
+
function integer(value) {
|
|
148
|
+
return Number.isInteger(value) ? value : 0;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=project-testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-testing.js","sourceRoot":"","sources":["../src/project-testing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contrato do `skills-lock.json` gerado pelo gerenciador oficial de skills.
|
|
3
|
+
*/
|
|
4
|
+
export declare const LOCK_FILENAME = "skills-lock.json";
|
|
5
|
+
/** Estrutura mínima compatível com o CLI `skills`. */
|
|
6
|
+
export interface SkillsLock {
|
|
7
|
+
version: 1;
|
|
8
|
+
skills: Record<string, unknown>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** Cria um lock vazio em memória. */
|
|
12
|
+
export declare function emptySkillsLock(): SkillsLock;
|
|
13
|
+
/** Lê o lock ou materializa a estrutura mínima quando ele não existe. */
|
|
14
|
+
export declare function ensureSkillsLock(project: string): Promise<SkillsLock>;
|
|
15
|
+
/** Lê e valida o lock oficial sem criá-lo. */
|
|
16
|
+
export declare function readSkillsLock(project: string): Promise<SkillsLock>;
|
|
17
|
+
/** Retorna os nomes registrados no lock. */
|
|
18
|
+
export declare function installedSkillNames(project: string, create?: boolean): Promise<Set<string>>;
|
|
19
|
+
/** Retorna um fingerprint barato do lock para polling da TUI. */
|
|
20
|
+
export declare function skillsLockFingerprint(project: string): Promise<string>;
|
|
21
|
+
/** Impede instalação na raiz oficial do monorepo Specsfy. */
|
|
22
|
+
export declare function assertConsumerProject(project: string): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=skill-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-lock.d.ts","sourceRoot":"","sources":["../src/skill-lock.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,eAAO,MAAM,aAAa,qBAAqB,CAAC;AAEhD,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qCAAqC;AACrC,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAED,yEAAyE;AACzE,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAQ3E;AAED,8CAA8C;AAC9C,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsBzE;AAED,4CAA4C;AAC5C,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,UAAO,GACZ,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAKtB;AAED,iEAAiE;AACjE,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5E;AAED,6DAA6D;AAC7D,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW1E"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contrato do `skills-lock.json` gerado pelo gerenciador oficial de skills.
|
|
3
|
+
*/
|
|
4
|
+
import { stat } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { isFile, readJson, resolvePath, writeTextAtomic, } from "./filesystem.js";
|
|
7
|
+
export const LOCK_FILENAME = "skills-lock.json";
|
|
8
|
+
/** Cria um lock vazio em memória. */
|
|
9
|
+
export function emptySkillsLock() {
|
|
10
|
+
return { version: 1, skills: {} };
|
|
11
|
+
}
|
|
12
|
+
/** Lê o lock ou materializa a estrutura mínima quando ele não existe. */
|
|
13
|
+
export async function ensureSkillsLock(project) {
|
|
14
|
+
const root = resolvePath(project);
|
|
15
|
+
await assertConsumerProject(root);
|
|
16
|
+
const path = join(root, LOCK_FILENAME);
|
|
17
|
+
if (!(await isFile(path))) {
|
|
18
|
+
await writeTextAtomic(path, `${JSON.stringify(emptySkillsLock(), null, 2)}\n`);
|
|
19
|
+
}
|
|
20
|
+
return readSkillsLock(root);
|
|
21
|
+
}
|
|
22
|
+
/** Lê e valida o lock oficial sem criá-lo. */
|
|
23
|
+
export async function readSkillsLock(project) {
|
|
24
|
+
const root = resolvePath(project);
|
|
25
|
+
const path = join(root, LOCK_FILENAME);
|
|
26
|
+
if (!(await isFile(path)))
|
|
27
|
+
return emptySkillsLock();
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
payload = await readJson(path);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
throw new Error(`lock inválido: ${path}: ${message}`);
|
|
35
|
+
}
|
|
36
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
37
|
+
throw new Error(`lock inválido: ${path}`);
|
|
38
|
+
}
|
|
39
|
+
const lock = payload;
|
|
40
|
+
if (lock.version !== 1) {
|
|
41
|
+
throw new Error(`versão de lock não suportada em ${path}`);
|
|
42
|
+
}
|
|
43
|
+
if (!lock.skills || typeof lock.skills !== "object" || Array.isArray(lock.skills)) {
|
|
44
|
+
throw new Error(`campo skills inválido em ${path}`);
|
|
45
|
+
}
|
|
46
|
+
return lock;
|
|
47
|
+
}
|
|
48
|
+
/** Retorna os nomes registrados no lock. */
|
|
49
|
+
export async function installedSkillNames(project, create = true) {
|
|
50
|
+
const payload = create
|
|
51
|
+
? await ensureSkillsLock(project)
|
|
52
|
+
: await readSkillsLock(project);
|
|
53
|
+
return new Set(Object.keys(payload.skills));
|
|
54
|
+
}
|
|
55
|
+
/** Retorna um fingerprint barato do lock para polling da TUI. */
|
|
56
|
+
export async function skillsLockFingerprint(project) {
|
|
57
|
+
const path = join(resolvePath(project), LOCK_FILENAME);
|
|
58
|
+
if (!(await isFile(path)))
|
|
59
|
+
return "missing";
|
|
60
|
+
try {
|
|
61
|
+
const metadata = await stat(path, { bigint: true });
|
|
62
|
+
return `${metadata.mtimeNs}:${metadata.size}`;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return "unreadable";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Impede instalação na raiz oficial do monorepo Specsfy. */
|
|
69
|
+
export async function assertConsumerProject(project) {
|
|
70
|
+
const path = join(resolvePath(project), "AGENTS.md");
|
|
71
|
+
if (!(await isFile(path)))
|
|
72
|
+
return;
|
|
73
|
+
const { readFile } = await import("node:fs/promises");
|
|
74
|
+
const content = (await readFile(path, "utf8")).toLowerCase();
|
|
75
|
+
if (content.includes("monorepo oficial do specsfy") ||
|
|
76
|
+
content.includes("workspace specsfy dev")) {
|
|
77
|
+
throw new Error("operação recusada no monorepo oficial do Specsfy");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=skill-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-lock.js","sourceRoot":"","sources":["../src/skill-lock.ts"],"names":[],"mappings":"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"}
|