@planu/cli 0.44.0 → 0.45.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/dist/config/license-plans.json +8 -2
- package/dist/config/registry-categories.json +20 -0
- package/dist/config/registry-stacks.json +16 -0
- package/dist/engine/github/spec-linker.d.ts.map +1 -1
- package/dist/engine/github/spec-linker.js +1 -0
- package/dist/engine/github/spec-linker.js.map +1 -1
- package/dist/engine/safety/file-mutex.d.ts.map +1 -1
- package/dist/engine/safety/file-mutex.js +6 -4
- package/dist/engine/safety/file-mutex.js.map +1 -1
- package/dist/engine/spec-registry/adapter.d.ts +9 -0
- package/dist/engine/spec-registry/adapter.d.ts.map +1 -0
- package/dist/engine/spec-registry/adapter.js +303 -0
- package/dist/engine/spec-registry/adapter.js.map +1 -0
- package/dist/engine/spec-registry/client.d.ts +8 -0
- package/dist/engine/spec-registry/client.d.ts.map +1 -0
- package/dist/engine/spec-registry/client.js +194 -0
- package/dist/engine/spec-registry/client.js.map +1 -0
- package/dist/engine/spec-registry/index.d.ts +7 -0
- package/dist/engine/spec-registry/index.d.ts.map +1 -0
- package/dist/engine/spec-registry/index.js +7 -0
- package/dist/engine/spec-registry/index.js.map +1 -0
- package/dist/engine/spec-registry/packager.d.ts +24 -0
- package/dist/engine/spec-registry/packager.d.ts.map +1 -0
- package/dist/engine/spec-registry/packager.js +122 -0
- package/dist/engine/spec-registry/packager.js.map +1 -0
- package/dist/engine/spec-registry/scorer.d.ts +10 -0
- package/dist/engine/spec-registry/scorer.d.ts.map +1 -0
- package/dist/engine/spec-registry/scorer.js +151 -0
- package/dist/engine/spec-registry/scorer.js.map +1 -0
- package/dist/engine/spec-registry/validator.d.ts +11 -0
- package/dist/engine/spec-registry/validator.d.ts.map +1 -0
- package/dist/engine/spec-registry/validator.js +144 -0
- package/dist/engine/spec-registry/validator.js.map +1 -0
- package/dist/engine/webhook/signature.js +2 -2
- package/dist/engine/webhook/signature.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/base-store.d.ts.map +1 -1
- package/dist/storage/base-store.js +2 -1
- package/dist/storage/base-store.js.map +1 -1
- package/dist/storage/usage-store.d.ts.map +1 -1
- package/dist/storage/usage-store.js +1 -0
- package/dist/storage/usage-store.js.map +1 -1
- package/dist/tools/register-spec-registry-tools.d.ts +3 -0
- package/dist/tools/register-spec-registry-tools.d.ts.map +1 -0
- package/dist/tools/register-spec-registry-tools.js +91 -0
- package/dist/tools/register-spec-registry-tools.js.map +1 -0
- package/dist/tools/registry/auth.d.ts +17 -0
- package/dist/tools/registry/auth.d.ts.map +1 -0
- package/dist/tools/registry/auth.js +132 -0
- package/dist/tools/registry/auth.js.map +1 -0
- package/dist/tools/registry/index.d.ts +5 -0
- package/dist/tools/registry/index.d.ts.map +1 -0
- package/dist/tools/registry/index.js +6 -0
- package/dist/tools/registry/index.js.map +1 -0
- package/dist/tools/registry/install.d.ts +7 -0
- package/dist/tools/registry/install.d.ts.map +1 -0
- package/dist/tools/registry/install.js +125 -0
- package/dist/tools/registry/install.js.map +1 -0
- package/dist/tools/registry/publish.d.ts +7 -0
- package/dist/tools/registry/publish.d.ts.map +1 -0
- package/dist/tools/registry/publish.js +96 -0
- package/dist/tools/registry/publish.js.map +1 -0
- package/dist/tools/registry/search.d.ts +7 -0
- package/dist/tools/registry/search.d.ts.map +1 -0
- package/dist/tools/registry/search.js +79 -0
- package/dist/tools/registry/search.js.map +1 -0
- package/dist/tools/schemas/index.d.ts +1 -0
- package/dist/tools/schemas/index.d.ts.map +1 -1
- package/dist/tools/schemas/index.js +1 -0
- package/dist/tools/schemas/index.js.map +1 -1
- package/dist/tools/schemas/spec-registry-schemas.d.ts +39 -0
- package/dist/tools/schemas/spec-registry-schemas.d.ts.map +1 -0
- package/dist/tools/schemas/spec-registry-schemas.js +72 -0
- package/dist/tools/schemas/spec-registry-schemas.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/spec-registry.d.ts +194 -0
- package/dist/types/spec-registry.d.ts.map +1 -0
- package/dist/types/spec-registry.js +3 -0
- package/dist/types/spec-registry.js.map +1 -0
- package/package.json +1 -1
- package/src/config/license-plans.json +8 -2
- package/src/config/registry-categories.json +20 -0
- package/src/config/registry-stacks.json +16 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// engine/spec-registry/client.ts — AC-2: Registry API Client
|
|
2
|
+
// HTTP client for the Planu spec registry. Uses node:https/http only (no external deps).
|
|
3
|
+
import * as https from 'node:https';
|
|
4
|
+
import * as http from 'node:http';
|
|
5
|
+
const DEFAULT_BASE_URL = 'https://registry.planu.dev/api/v1';
|
|
6
|
+
const MAX_RETRIES = 3;
|
|
7
|
+
const BASE_DELAY_MS = 1000;
|
|
8
|
+
/** Build a URL with query parameters from filters. */
|
|
9
|
+
function buildSearchUrl(baseUrl, filters) {
|
|
10
|
+
const url = new URL(`${baseUrl}/specs`);
|
|
11
|
+
if (filters.query) {
|
|
12
|
+
url.searchParams.set('q', filters.query);
|
|
13
|
+
}
|
|
14
|
+
if (filters.stack) {
|
|
15
|
+
url.searchParams.set('stack', filters.stack);
|
|
16
|
+
}
|
|
17
|
+
if (filters.category) {
|
|
18
|
+
url.searchParams.set('category', filters.category);
|
|
19
|
+
}
|
|
20
|
+
if (filters.sort) {
|
|
21
|
+
url.searchParams.set('sort', filters.sort);
|
|
22
|
+
}
|
|
23
|
+
if (filters.page !== undefined) {
|
|
24
|
+
url.searchParams.set('page', String(filters.page));
|
|
25
|
+
}
|
|
26
|
+
if (filters.limit !== undefined) {
|
|
27
|
+
url.searchParams.set('limit', String(filters.limit));
|
|
28
|
+
}
|
|
29
|
+
if (filters.difficulty?.min !== undefined) {
|
|
30
|
+
url.searchParams.set('difficulty_min', String(filters.difficulty.min));
|
|
31
|
+
}
|
|
32
|
+
if (filters.difficulty?.max !== undefined) {
|
|
33
|
+
url.searchParams.set('difficulty_max', String(filters.difficulty.max));
|
|
34
|
+
}
|
|
35
|
+
return url.toString();
|
|
36
|
+
}
|
|
37
|
+
/** Choose http or https module based on URL protocol. */
|
|
38
|
+
function getTransport(url) {
|
|
39
|
+
return url.startsWith('https') ? https : http;
|
|
40
|
+
}
|
|
41
|
+
/** Make an HTTP request with retry and exponential backoff. */
|
|
42
|
+
async function request(url, options, body) {
|
|
43
|
+
let lastError;
|
|
44
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
45
|
+
if (attempt > 0) {
|
|
46
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
47
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const result = await executeRequest(url, options, body);
|
|
51
|
+
// Don't retry client errors (4xx) except 429
|
|
52
|
+
if (result.status >= 400 && result.status < 500 && result.status !== 429) {
|
|
53
|
+
const message = tryParseErrorMessage(result.data);
|
|
54
|
+
throw new Error(`Registry API error ${result.status}: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
// Retry server errors (5xx) and 429
|
|
57
|
+
if (result.status >= 500 || result.status === 429) {
|
|
58
|
+
lastError = new Error(`Registry API error ${result.status}: server error`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof Error && error.message.startsWith('Registry API error 4')) {
|
|
65
|
+
throw error; // Don't retry 4xx
|
|
66
|
+
}
|
|
67
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Registry API request failed after ${MAX_RETRIES} attempts: ${lastError?.message ?? 'unknown error'}`);
|
|
71
|
+
}
|
|
72
|
+
/** Execute a single HTTP request. */
|
|
73
|
+
function executeRequest(url, options, body) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const transport = getTransport(url);
|
|
76
|
+
const req = transport.request(url, options, (res) => {
|
|
77
|
+
const chunks = [];
|
|
78
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
79
|
+
res.on('end', () => {
|
|
80
|
+
resolve({
|
|
81
|
+
status: res.statusCode ?? 0,
|
|
82
|
+
data: Buffer.concat(chunks),
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
/* v8 ignore start -- network error/timeout handlers */
|
|
87
|
+
req.on('error', (err) => {
|
|
88
|
+
reject(new Error(`Network error: ${err.message}`));
|
|
89
|
+
});
|
|
90
|
+
req.setTimeout(30_000, () => {
|
|
91
|
+
req.destroy();
|
|
92
|
+
reject(new Error('Request timed out after 30s'));
|
|
93
|
+
});
|
|
94
|
+
/* v8 ignore stop */
|
|
95
|
+
if (body) {
|
|
96
|
+
req.write(body);
|
|
97
|
+
}
|
|
98
|
+
req.end();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Try to parse a JSON error message from response body. */
|
|
102
|
+
/* v8 ignore start -- error parsing defensive branches */
|
|
103
|
+
function tryParseErrorMessage(data) {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(data.toString('utf-8'));
|
|
106
|
+
return parsed.message ?? parsed.error ?? data.toString('utf-8').slice(0, 200);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return data.toString('utf-8').slice(0, 200);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/* v8 ignore stop */
|
|
113
|
+
/** Build common request headers. */
|
|
114
|
+
function buildHeaders(token, contentType) {
|
|
115
|
+
const headers = {
|
|
116
|
+
'User-Agent': 'planu-cli',
|
|
117
|
+
Accept: 'application/json',
|
|
118
|
+
};
|
|
119
|
+
if (token) {
|
|
120
|
+
headers.Authorization = `Bearer ${token}`;
|
|
121
|
+
}
|
|
122
|
+
if (contentType) {
|
|
123
|
+
headers['Content-Type'] = contentType;
|
|
124
|
+
}
|
|
125
|
+
return headers;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Create a registry API client instance.
|
|
129
|
+
* @param token - Optional authentication token for write operations.
|
|
130
|
+
* @returns Object with methods for interacting with the registry API.
|
|
131
|
+
*/
|
|
132
|
+
export function createRegistryClient(token) {
|
|
133
|
+
const baseUrl = process.env.PLANU_REGISTRY_URL ?? DEFAULT_BASE_URL;
|
|
134
|
+
const options = { token, baseUrl };
|
|
135
|
+
return {
|
|
136
|
+
/** Search specs in the registry with optional filters. */
|
|
137
|
+
async searchSpecs(filters) {
|
|
138
|
+
const url = buildSearchUrl(options.baseUrl ?? DEFAULT_BASE_URL, filters);
|
|
139
|
+
const result = await request(url, {
|
|
140
|
+
method: 'GET',
|
|
141
|
+
headers: buildHeaders(options.token),
|
|
142
|
+
});
|
|
143
|
+
return JSON.parse(result.data.toString('utf-8'));
|
|
144
|
+
},
|
|
145
|
+
/** Get metadata for a specific spec by org and name. */
|
|
146
|
+
async getSpec(org, name, version) {
|
|
147
|
+
const versionSuffix = version ? `/${version}` : '';
|
|
148
|
+
const url = `${options.baseUrl}/specs/${encodeURIComponent(org)}/${encodeURIComponent(name)}${versionSuffix}`;
|
|
149
|
+
const result = await request(url, {
|
|
150
|
+
method: 'GET',
|
|
151
|
+
headers: buildHeaders(options.token),
|
|
152
|
+
});
|
|
153
|
+
return JSON.parse(result.data.toString('utf-8'));
|
|
154
|
+
},
|
|
155
|
+
/** Download a spec tarball from the registry. */
|
|
156
|
+
async downloadSpec(org, name, version) {
|
|
157
|
+
const url = `${options.baseUrl}/specs/${encodeURIComponent(org)}/${encodeURIComponent(name)}/${version}/download`;
|
|
158
|
+
const result = await request(url, {
|
|
159
|
+
method: 'GET',
|
|
160
|
+
headers: buildHeaders(options.token),
|
|
161
|
+
});
|
|
162
|
+
return result.data;
|
|
163
|
+
},
|
|
164
|
+
/** Publish a spec tarball to the registry. Requires authentication. */
|
|
165
|
+
async publishSpec(tarball, meta) {
|
|
166
|
+
if (!options.token) {
|
|
167
|
+
throw new Error('Authentication required to publish specs. Run planu login first.');
|
|
168
|
+
}
|
|
169
|
+
const payload = JSON.stringify({ tarball: tarball.toString('base64'), meta });
|
|
170
|
+
const url = `${options.baseUrl}/specs`;
|
|
171
|
+
const result = await request(url, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: buildHeaders(options.token, 'application/json'),
|
|
174
|
+
}, payload);
|
|
175
|
+
return JSON.parse(result.data.toString('utf-8'));
|
|
176
|
+
},
|
|
177
|
+
/** Rate a spec in the registry. Requires authentication. */
|
|
178
|
+
async rateSpec(org, name, rating) {
|
|
179
|
+
if (!options.token) {
|
|
180
|
+
throw new Error('Authentication required to rate specs. Run planu login first.');
|
|
181
|
+
}
|
|
182
|
+
if (rating < 1 || rating > 5 || !Number.isInteger(rating)) {
|
|
183
|
+
throw new Error('Rating must be an integer between 1 and 5');
|
|
184
|
+
}
|
|
185
|
+
const url = `${options.baseUrl}/specs/${encodeURIComponent(org)}/${encodeURIComponent(name)}/rate`;
|
|
186
|
+
const payload = JSON.stringify({ rating });
|
|
187
|
+
await request(url, {
|
|
188
|
+
method: 'POST',
|
|
189
|
+
headers: buildHeaders(options.token, 'application/json'),
|
|
190
|
+
}, payload);
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/engine/spec-registry/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createRegistryClient } from './client.js';
|
|
2
|
+
export type { RegistryClient } from '../../types/index.js';
|
|
3
|
+
export { packSpec, unpackSpec, validatePackage } from './packager.js';
|
|
4
|
+
export { validateForPublish } from './validator.js';
|
|
5
|
+
export { detectStack, adaptSpec } from './adapter.js';
|
|
6
|
+
export { calculateCompletenessScore } from './scorer.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/spec-registry/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// engine/spec-registry/index.ts — Barrel export for spec registry engine modules.
|
|
2
|
+
export { createRegistryClient } from './client.js';
|
|
3
|
+
export { packSpec, unpackSpec, validatePackage } from './packager.js';
|
|
4
|
+
export { validateForPublish } from './validator.js';
|
|
5
|
+
export { detectStack, adaptSpec } from './adapter.js';
|
|
6
|
+
export { calculateCompletenessScore } from './scorer.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/spec-registry/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pack a spec directory into a gzipped archive buffer.
|
|
3
|
+
* Only includes recognized spec files (spec.md, technical.md, progress.md, planu-registry.json).
|
|
4
|
+
* @param specDir - Absolute path to the spec directory.
|
|
5
|
+
* @returns Gzipped buffer containing the archived spec files.
|
|
6
|
+
*/
|
|
7
|
+
export declare function packSpec(specDir: string): Promise<Buffer>;
|
|
8
|
+
/**
|
|
9
|
+
* Unpack a gzipped archive buffer into a target directory.
|
|
10
|
+
* Creates the target directory if it does not exist.
|
|
11
|
+
* @param tarball - Gzipped buffer to extract.
|
|
12
|
+
* @param targetDir - Absolute path to extract files into.
|
|
13
|
+
*/
|
|
14
|
+
export declare function unpackSpec(tarball: Buffer, targetDir: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Validate a package buffer for structural correctness and size limits.
|
|
17
|
+
* @param tarball - Gzipped buffer to validate.
|
|
18
|
+
* @returns Validation result with errors array.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validatePackage(tarball: Buffer): Promise<{
|
|
21
|
+
valid: boolean;
|
|
22
|
+
errors: string[];
|
|
23
|
+
}>;
|
|
24
|
+
//# sourceMappingURL=packager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packager.d.ts","sourceRoot":"","sources":["../../../src/engine/spec-registry/packager.ts"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA6B/D;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BlF;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA0C/C"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// engine/spec-registry/packager.ts — AC-3: Spec Package Format
|
|
2
|
+
// Creates and extracts gzipped JSON archives for spec packages.
|
|
3
|
+
// Format: gzip({ files: { [relativePath]: contentString } })
|
|
4
|
+
import { gzip, gunzip } from 'node:zlib';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import * as fs from 'node:fs/promises';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
const gzipAsync = promisify(gzip);
|
|
9
|
+
const gunzipAsync = promisify(gunzip);
|
|
10
|
+
/** Maximum package size in bytes (500 KB). */
|
|
11
|
+
const MAX_PACKAGE_SIZE = 500 * 1024;
|
|
12
|
+
/** Files that may be included in a spec package. */
|
|
13
|
+
const ALLOWED_FILES = ['spec.md', 'technical.md', 'progress.md', 'planu-registry.json'];
|
|
14
|
+
/**
|
|
15
|
+
* Pack a spec directory into a gzipped archive buffer.
|
|
16
|
+
* Only includes recognized spec files (spec.md, technical.md, progress.md, planu-registry.json).
|
|
17
|
+
* @param specDir - Absolute path to the spec directory.
|
|
18
|
+
* @returns Gzipped buffer containing the archived spec files.
|
|
19
|
+
*/
|
|
20
|
+
export async function packSpec(specDir) {
|
|
21
|
+
const archive = { files: {} };
|
|
22
|
+
for (const fileName of ALLOWED_FILES) {
|
|
23
|
+
const filePath = path.join(specDir, fileName);
|
|
24
|
+
try {
|
|
25
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
26
|
+
archive.files[fileName] = content;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Optional files may not exist — skip silently
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (Object.keys(archive.files).length === 0) {
|
|
33
|
+
throw new Error(`No spec files found in ${specDir}. Expected at least one of: ${ALLOWED_FILES.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
const json = JSON.stringify(archive);
|
|
36
|
+
const compressed = await gzipAsync(Buffer.from(json, 'utf-8'));
|
|
37
|
+
if (compressed.length > MAX_PACKAGE_SIZE) {
|
|
38
|
+
throw new Error(`Package size ${compressed.length} bytes exceeds limit of ${MAX_PACKAGE_SIZE} bytes (500 KB)`);
|
|
39
|
+
}
|
|
40
|
+
return compressed;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Unpack a gzipped archive buffer into a target directory.
|
|
44
|
+
* Creates the target directory if it does not exist.
|
|
45
|
+
* @param tarball - Gzipped buffer to extract.
|
|
46
|
+
* @param targetDir - Absolute path to extract files into.
|
|
47
|
+
*/
|
|
48
|
+
export async function unpackSpec(tarball, targetDir) {
|
|
49
|
+
const decompressed = await gunzipAsync(tarball);
|
|
50
|
+
const archive = JSON.parse(decompressed.toString('utf-8'));
|
|
51
|
+
if (!archive.files || typeof archive.files !== 'object') {
|
|
52
|
+
throw new Error('Invalid archive format: missing files object');
|
|
53
|
+
}
|
|
54
|
+
// Validate paths before writing anything
|
|
55
|
+
for (const filePath of Object.keys(archive.files)) {
|
|
56
|
+
assertSafePath(filePath);
|
|
57
|
+
}
|
|
58
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
59
|
+
for (const [filePath, content] of Object.entries(archive.files)) {
|
|
60
|
+
const fullPath = path.join(targetDir, filePath);
|
|
61
|
+
// Double-check resolved path stays within target
|
|
62
|
+
const resolved = path.resolve(fullPath);
|
|
63
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
64
|
+
/* v8 ignore next 2 -- path traversal guard, tested separately */
|
|
65
|
+
if (!resolved.startsWith(resolvedTarget + path.sep) && resolved !== resolvedTarget) {
|
|
66
|
+
throw new Error(`Path traversal detected: ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
await fs.writeFile(fullPath, content, 'utf-8');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate a package buffer for structural correctness and size limits.
|
|
73
|
+
* @param tarball - Gzipped buffer to validate.
|
|
74
|
+
* @returns Validation result with errors array.
|
|
75
|
+
*/
|
|
76
|
+
export async function validatePackage(tarball) {
|
|
77
|
+
const errors = [];
|
|
78
|
+
// Check compressed size
|
|
79
|
+
if (tarball.length > MAX_PACKAGE_SIZE) {
|
|
80
|
+
errors.push(`Package size ${tarball.length} bytes exceeds limit of ${MAX_PACKAGE_SIZE} bytes`);
|
|
81
|
+
}
|
|
82
|
+
// Try to decompress and parse
|
|
83
|
+
let archive;
|
|
84
|
+
try {
|
|
85
|
+
const decompressed = await gunzipAsync(tarball);
|
|
86
|
+
archive = JSON.parse(decompressed.toString('utf-8'));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
errors.push('Failed to decompress or parse package. Expected gzipped JSON.');
|
|
90
|
+
return { valid: false, errors };
|
|
91
|
+
}
|
|
92
|
+
/* v8 ignore start -- defensive guard for malformed archives */
|
|
93
|
+
if (!archive.files || typeof archive.files !== 'object') {
|
|
94
|
+
errors.push('Invalid archive format: missing "files" object');
|
|
95
|
+
return { valid: false, errors };
|
|
96
|
+
}
|
|
97
|
+
/* v8 ignore stop */
|
|
98
|
+
// Check for path traversal
|
|
99
|
+
for (const filePath of Object.keys(archive.files)) {
|
|
100
|
+
if (filePath.includes('..') || path.isAbsolute(filePath)) {
|
|
101
|
+
errors.push(`Unsafe path detected: ${filePath}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Check required files
|
|
105
|
+
if (!archive.files['spec.md']) {
|
|
106
|
+
errors.push('Missing required file: spec.md');
|
|
107
|
+
}
|
|
108
|
+
if (!archive.files['planu-registry.json']) {
|
|
109
|
+
errors.push('Missing required file: planu-registry.json');
|
|
110
|
+
}
|
|
111
|
+
return { valid: errors.length === 0, errors };
|
|
112
|
+
}
|
|
113
|
+
/** Assert that a file path is safe (no traversal, not absolute). */
|
|
114
|
+
function assertSafePath(filePath) {
|
|
115
|
+
if (filePath.includes('..')) {
|
|
116
|
+
throw new Error(`Path traversal detected in archive: ${filePath}`);
|
|
117
|
+
}
|
|
118
|
+
if (path.isAbsolute(filePath)) {
|
|
119
|
+
throw new Error(`Absolute path detected in archive: ${filePath}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=packager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packager.js","sourceRoot":"","sources":["../../../src/engine/spec-registry/packager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ScoreBreakdown } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Calculate a completeness score (0-100) for a spec directory.
|
|
4
|
+
* Reads spec.md and technical.md, evaluates 6 quality categories.
|
|
5
|
+
*/
|
|
6
|
+
export declare function calculateCompletenessScore(specDir: string): Promise<{
|
|
7
|
+
score: number;
|
|
8
|
+
breakdown: ScoreBreakdown;
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=scorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.d.ts","sourceRoot":"","sources":["../../../src/engine/spec-registry/scorer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAmJ3D;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,cAAc,CAAA;CAAE,CAAC,CAwBvD"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// engine/spec-registry/scorer.ts — Spec completeness scorer for SPEC-128.
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Helpers
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
async function readFileOrEmpty(path) {
|
|
8
|
+
try {
|
|
9
|
+
return await readFile(path, 'utf-8');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Count lines matching `- [ ]` or `- [x]` (acceptance criteria checkboxes). */
|
|
16
|
+
function scoreAcceptanceCriteria(specContent) {
|
|
17
|
+
const matches = specContent.match(/^[\s]*- \[[x ]\]/gm);
|
|
18
|
+
const count = matches?.length ?? 0;
|
|
19
|
+
if (count >= 8) {
|
|
20
|
+
return 20;
|
|
21
|
+
}
|
|
22
|
+
if (count >= 4) {
|
|
23
|
+
return 15;
|
|
24
|
+
}
|
|
25
|
+
if (count >= 1) {
|
|
26
|
+
return 10;
|
|
27
|
+
}
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
/** Check for a risk section header and whether it has content. */
|
|
31
|
+
function scoreRisks(specContent) {
|
|
32
|
+
const riskHeaderIdx = specContent.search(/^#{1,4}\s+.*(?:riesgo|risk)/im);
|
|
33
|
+
if (riskHeaderIdx === -1) {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
// Grab text after the risk header until the next header or end
|
|
37
|
+
const afterHeader = specContent.slice(riskHeaderIdx);
|
|
38
|
+
const nextHeader = afterHeader.search(/\n#{1,4}\s+/);
|
|
39
|
+
const section = nextHeader === -1 ? afterHeader : afterHeader.slice(0, nextHeader);
|
|
40
|
+
const bodyLines = section
|
|
41
|
+
.split('\n')
|
|
42
|
+
.slice(1)
|
|
43
|
+
.filter((l) => l.trim().length > 0);
|
|
44
|
+
return bodyLines.length > 0 ? 15 : 5;
|
|
45
|
+
}
|
|
46
|
+
/** Check for testing strategy mentions. */
|
|
47
|
+
function scoreTestingStrategy(specContent, technicalContent) {
|
|
48
|
+
const combined = `${specContent}\n${technicalContent}`;
|
|
49
|
+
// Look for a dedicated testing section header
|
|
50
|
+
const hasTestSection = /^#{1,4}\s+.*(?:test|prueba)/im.test(combined);
|
|
51
|
+
if (hasTestSection) {
|
|
52
|
+
return 15;
|
|
53
|
+
}
|
|
54
|
+
// Look for inline test references
|
|
55
|
+
const testMentions = combined.match(/\b(?:test|prueba|jest|vitest|coverage)\b/gi);
|
|
56
|
+
if (testMentions && testMentions.length >= 2) {
|
|
57
|
+
return 8;
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
/** Check for a files/archivos listing. */
|
|
62
|
+
function scoreKeyFiles(specContent, technicalContent) {
|
|
63
|
+
const combined = `${specContent}\n${technicalContent}`;
|
|
64
|
+
const filesHeaderIdx = combined.search(/^#{1,4}\s+.*(?:archivos|files)/im);
|
|
65
|
+
if (filesHeaderIdx === -1) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
const afterHeader = combined.slice(filesHeaderIdx);
|
|
69
|
+
const nextHeader = afterHeader.search(/\n#{1,4}\s+/);
|
|
70
|
+
const section = nextHeader === -1 ? afterHeader : afterHeader.slice(0, nextHeader);
|
|
71
|
+
// Count file-like entries (lines with paths or table rows with file refs)
|
|
72
|
+
const fileEntries = section.match(/[`|]\S+\.\w{1,5}[`|]|^\s*[-|]\s*\S+\.\w{1,5}/gm);
|
|
73
|
+
const count = fileEntries?.length ?? 0;
|
|
74
|
+
if (count >= 3) {
|
|
75
|
+
return 15;
|
|
76
|
+
}
|
|
77
|
+
if (count >= 1) {
|
|
78
|
+
return 8;
|
|
79
|
+
}
|
|
80
|
+
/* v8 ignore next -- files header present but no file-like entries */
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
/** Score technical.md based on existence and length. */
|
|
84
|
+
function scoreTechnicalCompleteness(technicalContent) {
|
|
85
|
+
if (!technicalContent) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
const lines = technicalContent.split('\n');
|
|
89
|
+
const lineCount = lines.length;
|
|
90
|
+
// Check for metadata table (lines with `|`)
|
|
91
|
+
const hasMetaTable = lines.some((l) => /^\|.*\|.*\|/.test(l));
|
|
92
|
+
if (hasMetaTable && lineCount > 50) {
|
|
93
|
+
return 20;
|
|
94
|
+
}
|
|
95
|
+
if (lineCount > 50) {
|
|
96
|
+
return 15;
|
|
97
|
+
}
|
|
98
|
+
if (lineCount > 10) {
|
|
99
|
+
return 10;
|
|
100
|
+
}
|
|
101
|
+
return 5;
|
|
102
|
+
}
|
|
103
|
+
/** Parse YAML frontmatter and check for required fields. */
|
|
104
|
+
function scoreFrontmatter(specContent) {
|
|
105
|
+
const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(specContent);
|
|
106
|
+
if (!fmMatch) {
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
const fmBlock = fmMatch[1] ?? '';
|
|
110
|
+
const requiredFields = ['id', 'title', 'type', 'status', 'priority', 'difficulty'];
|
|
111
|
+
const presentCount = requiredFields.filter((field) => new RegExp(`^${field}\\s*:`, 'm').test(fmBlock)).length;
|
|
112
|
+
if (presentCount >= 6) {
|
|
113
|
+
return 15;
|
|
114
|
+
}
|
|
115
|
+
if (presentCount >= 4) {
|
|
116
|
+
return 10;
|
|
117
|
+
}
|
|
118
|
+
if (presentCount >= 2) {
|
|
119
|
+
return 5;
|
|
120
|
+
}
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Public API
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
/**
|
|
127
|
+
* Calculate a completeness score (0-100) for a spec directory.
|
|
128
|
+
* Reads spec.md and technical.md, evaluates 6 quality categories.
|
|
129
|
+
*/
|
|
130
|
+
export async function calculateCompletenessScore(specDir) {
|
|
131
|
+
const [specContent, technicalContent] = await Promise.all([
|
|
132
|
+
readFileOrEmpty(join(specDir, 'spec.md')),
|
|
133
|
+
readFileOrEmpty(join(specDir, 'technical.md')),
|
|
134
|
+
]);
|
|
135
|
+
const breakdown = {
|
|
136
|
+
acceptanceCriteria: scoreAcceptanceCriteria(specContent),
|
|
137
|
+
risksDocumented: scoreRisks(specContent),
|
|
138
|
+
testingStrategy: scoreTestingStrategy(specContent, technicalContent),
|
|
139
|
+
keyFiles: scoreKeyFiles(specContent, technicalContent),
|
|
140
|
+
technicalCompleteness: scoreTechnicalCompleteness(technicalContent),
|
|
141
|
+
frontmatterFields: scoreFrontmatter(specContent),
|
|
142
|
+
};
|
|
143
|
+
const score = breakdown.acceptanceCriteria +
|
|
144
|
+
breakdown.risksDocumented +
|
|
145
|
+
breakdown.testingStrategy +
|
|
146
|
+
breakdown.keyFiles +
|
|
147
|
+
breakdown.technicalCompleteness +
|
|
148
|
+
breakdown.frontmatterFields;
|
|
149
|
+
return { score, breakdown };
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../../src/engine/spec-registry/scorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a spec directory for publishing to the registry.
|
|
3
|
+
* Checks file existence, frontmatter fields, manifest structure, semver format, and size limits.
|
|
4
|
+
* @param specDir - Absolute path to the spec directory to validate.
|
|
5
|
+
* @returns Validation result with detailed error messages for each failure.
|
|
6
|
+
*/
|
|
7
|
+
export declare function validateForPublish(specDir: string): Promise<{
|
|
8
|
+
valid: boolean;
|
|
9
|
+
errors: string[];
|
|
10
|
+
}>;
|
|
11
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/engine/spec-registry/validator.ts"],"names":[],"mappings":"AAkCA;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAqB/C"}
|