@hone-ai/cli 1.18.0 → 1.19.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/hone-cli.js +975 -34
- package/lib/architect-config.js +121 -0
- package/lib/ci-gate-chooser.js +267 -0
- package/lib/doctor-admin-merge.js +3 -2
- package/lib/doctor-architecture.js +213 -0
- package/lib/eval-evidence.js +213 -0
- package/lib/git-env.js +94 -0
- package/lib/git-helpers.js +3 -2
- package/lib/pipeline-config.js +80 -0
- package/lib/pipeline-status.js +12 -1
- package/lib/refresh-knowledge.js +7 -0
- package/lib/release-review-cache.js +162 -0
- package/lib/release-review-config.js +4 -2
- package/lib/schedule-cron.js +141 -0
- package/lib/skill-eval-runner.js +667 -0
- package/lib/stack-paths.js +84 -6
- package/lib/story-classifier-extract.js +2 -1
- package/package.json +1 -1
package/lib/stack-paths.js
CHANGED
|
@@ -235,10 +235,72 @@ function parsePlatformConfigPaths(yamlText) {
|
|
|
235
235
|
return out;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Parse adopter-discovered `platform.metadata_types.config[].path` entries
|
|
240
|
+
* from a `.pipeline-config.yml` text. HC-018: closes the classifier loop
|
|
241
|
+
* by feeding setup-time-discovered CONFIG metadata locations into
|
|
242
|
+
* `classifyPathsForRepo` so adopter platforms (Salesforce, NetSuite, dbt,
|
|
243
|
+
* Terraform, ...) classify as CONFIG without needing to be hardcoded
|
|
244
|
+
* into STACK_PATH_TABLE.
|
|
245
|
+
*
|
|
246
|
+
* The YAML shape emitted by cli/lib/config-augment.js is:
|
|
247
|
+
* platform:
|
|
248
|
+
* metadata_types:
|
|
249
|
+
* config:
|
|
250
|
+
* - { type: customObjects, path: "force-app/main/default/objects/", count: 18 }
|
|
251
|
+
*
|
|
252
|
+
* Some discover paths are comma-joined multi-paths
|
|
253
|
+
* (e.g. "force-app/main/default/classes, force-app/main/default/triggers")
|
|
254
|
+
* — those are split into individual entries.
|
|
255
|
+
*
|
|
256
|
+
* Returned values are raw filesystem paths (NOT regex). The caller
|
|
257
|
+
* anchors + escapes them before unioning into `extraConfigPaths`.
|
|
258
|
+
*
|
|
259
|
+
* @param {string} yamlText
|
|
260
|
+
* @returns {string[]} raw path strings discovered under metadata_types.config[]
|
|
261
|
+
*/
|
|
262
|
+
function parsePlatformMetadataTypesConfigPaths(yamlText) {
|
|
263
|
+
if (typeof yamlText !== 'string' || yamlText.length === 0) return [];
|
|
264
|
+
const blockMatch = yamlText.match(/^platform:\s*\n((?:[ \t]+.*\n?)*)/m);
|
|
265
|
+
if (!blockMatch) return [];
|
|
266
|
+
const block = blockMatch[1];
|
|
267
|
+
// metadata_types: <body> — body lines indented strictly more than the key
|
|
268
|
+
const mtMatch = block.match(/^([ \t]*)metadata_types:\s*\n((?:\1[ \t]+.*\n?)*)/m);
|
|
269
|
+
if (!mtMatch) return [];
|
|
270
|
+
const mtBody = mtMatch[2];
|
|
271
|
+
// config: <body> under metadata_types — same indent-bounded scan
|
|
272
|
+
const cfgMatch = mtBody.match(/^([ \t]*)config:\s*\n((?:\1[ \t]+.*\n?)*)/m);
|
|
273
|
+
if (!cfgMatch) return [];
|
|
274
|
+
const cfgBody = cfgMatch[2];
|
|
275
|
+
// Match each inline-mapping item: `- { ..., path: "VALUE", ... }`
|
|
276
|
+
// (also accepts single-quoted and bare-token paths)
|
|
277
|
+
const out = [];
|
|
278
|
+
const re = /-\s*\{[^}]*\bpath:\s*(?:"([^"]+)"|'([^']+)'|([^,}\s]+))/g;
|
|
279
|
+
let m;
|
|
280
|
+
while ((m = re.exec(cfgBody)) !== null) {
|
|
281
|
+
const raw = (m[1] || m[2] || m[3] || '').trim();
|
|
282
|
+
if (!raw) continue;
|
|
283
|
+
// Discover code sometimes joins multiple paths with ", " — split them.
|
|
284
|
+
for (const piece of raw.split(/\s*,\s*/)) {
|
|
285
|
+
const v = piece.trim();
|
|
286
|
+
if (v) out.push(v);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Escape a literal filesystem path so it can be used as a RegExp pattern.
|
|
294
|
+
*/
|
|
295
|
+
function escapeRegexLiteral(s) {
|
|
296
|
+
return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
297
|
+
}
|
|
298
|
+
|
|
238
299
|
/**
|
|
239
300
|
* Higher-level classifier composing platform fingerprint detection +
|
|
240
|
-
* adopter-supplied `.pipeline-config.yml platform.config_paths` +
|
|
241
|
-
*
|
|
301
|
+
* adopter-supplied `.pipeline-config.yml platform.config_paths` +
|
|
302
|
+
* setup-discovered `platform.metadata_types.config[].path` (HC-018) +
|
|
303
|
+
* the pure `classifyPathsForStack` core.
|
|
242
304
|
*
|
|
243
305
|
* Pure-helper-with-injected-I/O shape (same as compliance-check.js):
|
|
244
306
|
* caller wraps `fs.existsSync` and `fs.readFileSync` so the helper can
|
|
@@ -249,7 +311,12 @@ function parsePlatformConfigPaths(yamlText) {
|
|
|
249
311
|
* @param {string} opts.repoRoot - absolute path to repo root
|
|
250
312
|
* @param {(relativePath: string) => boolean} [opts.fileExists] - filesystem check
|
|
251
313
|
* @param {(relativePath: string) => string|null} [opts.readFile] - file reader
|
|
252
|
-
* @returns {{
|
|
314
|
+
* @returns {{
|
|
315
|
+
* category: string,
|
|
316
|
+
* detectedPlatforms: string[],
|
|
317
|
+
* configPathsUsed: string[],
|
|
318
|
+
* metadataConfigPathsUsed: string[]
|
|
319
|
+
* }}
|
|
253
320
|
*/
|
|
254
321
|
function classifyPathsForRepo(opts = {}) {
|
|
255
322
|
const { paths, repoRoot, fileExists, readFile } = opts;
|
|
@@ -261,19 +328,28 @@ function classifyPathsForRepo(opts = {}) {
|
|
|
261
328
|
detectedPlatforms = detectPlatforms({ repoRoot, fileExists });
|
|
262
329
|
} catch { /* defensive — if the module is unavailable, treat as no platforms */ }
|
|
263
330
|
|
|
264
|
-
// Read adopter-supplied platform.config_paths
|
|
331
|
+
// Read adopter-supplied platform.config_paths + discovered metadata_types
|
|
332
|
+
// from .pipeline-config.yml (best-effort)
|
|
265
333
|
let configPathsUsed = [];
|
|
334
|
+
let metadataConfigPathsUsed = [];
|
|
266
335
|
try {
|
|
267
336
|
if (typeof readFile === 'function') {
|
|
268
337
|
const yamlText = readFile('.pipeline-config.yml');
|
|
269
338
|
if (typeof yamlText === 'string' && yamlText.length > 0) {
|
|
270
339
|
configPathsUsed = parsePlatformConfigPaths(yamlText);
|
|
340
|
+
metadataConfigPathsUsed = parsePlatformMetadataTypesConfigPaths(yamlText);
|
|
271
341
|
}
|
|
272
342
|
}
|
|
273
343
|
} catch { /* defensive — bad YAML / missing file is a no-op */ }
|
|
274
344
|
|
|
275
|
-
|
|
276
|
-
|
|
345
|
+
// metadata_types paths are raw filesystem prefixes — anchor + escape
|
|
346
|
+
// so a discovered `force-app/main/default/objects/` is matched as a
|
|
347
|
+
// path prefix, not as a free-form regex.
|
|
348
|
+
const metadataPatterns = metadataConfigPathsUsed.map(p => '^' + escapeRegexLiteral(p));
|
|
349
|
+
const extraConfigPaths = configPathsUsed.concat(metadataPatterns);
|
|
350
|
+
|
|
351
|
+
const category = classifyPathsForStack(paths, { extraConfigPaths });
|
|
352
|
+
return { category, detectedPlatforms, configPathsUsed, metadataConfigPathsUsed };
|
|
277
353
|
}
|
|
278
354
|
|
|
279
355
|
module.exports = {
|
|
@@ -282,4 +358,6 @@ module.exports = {
|
|
|
282
358
|
classifyPathsForStack,
|
|
283
359
|
classifyPathsForRepo,
|
|
284
360
|
parsePlatformConfigPaths,
|
|
361
|
+
parsePlatformMetadataTypesConfigPaths,
|
|
362
|
+
escapeRegexLiteral,
|
|
285
363
|
};
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const { execSync } = require('node:child_process');
|
|
22
|
+
const { gitEnv } = require('./git-env');
|
|
22
23
|
const fs = require('node:fs');
|
|
23
24
|
const path = require('node:path');
|
|
24
25
|
|
|
@@ -181,7 +182,7 @@ function extractRecentlyModifiedOverlap(repoRoot, touchedFiles, windowDays) {
|
|
|
181
182
|
try {
|
|
182
183
|
const out = execSync(
|
|
183
184
|
`git log --since="${w} days ago" --format=%H -- ${JSON.stringify(rel)}`,
|
|
184
|
-
{ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
185
|
+
{ cwd: repoRoot, env: gitEnv(), encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
185
186
|
).trim();
|
|
186
187
|
if (out) return true;
|
|
187
188
|
} catch {
|