@nerviq/cli 1.11.0 → 1.12.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 +97 -19
- package/bin/cli.js +618 -182
- package/package.json +2 -2
- package/src/activity.js +49 -9
- package/src/adoption-advisor.js +299 -0
- package/src/aider/techniques.js +16 -11
- package/src/analyze.js +128 -0
- package/src/anti-patterns.js +13 -0
- package/src/audit.js +97 -22
- package/src/behavioral-drift.js +801 -0
- package/src/continuous-ops.js +681 -0
- package/src/cost-tracking.js +61 -0
- package/src/cursor/techniques.js +17 -12
- package/src/deep-review.js +83 -0
- package/src/diff-only.js +280 -0
- package/src/doctor.js +118 -55
- package/src/governance.js +59 -43
- package/src/hook-validation.js +342 -0
- package/src/index.js +5 -0
- package/src/integrations.js +42 -5
- package/src/mcp-validation.js +337 -0
- package/src/opencode/techniques.js +12 -7
- package/src/operating-profile.js +574 -0
- package/src/org.js +97 -13
- package/src/plans.js +192 -8
- package/src/platform-change-manifest.js +86 -0
- package/src/policy-layers.js +210 -0
- package/src/profiles.js +4 -1
- package/src/prompt-injection.js +74 -0
- package/src/repo-archetype.js +386 -0
- package/src/setup.js +34 -0
- package/src/source-urls.js +132 -132
- package/src/supplemental-checks.js +13 -12
- package/src/techniques/api.js +407 -0
- package/src/techniques/automation.js +316 -0
- package/src/techniques/compliance.js +257 -0
- package/src/techniques/hygiene.js +294 -0
- package/src/techniques/instructions.js +243 -0
- package/src/techniques/observability.js +226 -0
- package/src/techniques/optimization.js +142 -0
- package/src/techniques/quality.js +317 -0
- package/src/techniques/security.js +237 -0
- package/src/techniques/shared.js +443 -0
- package/src/techniques/stacks.js +2294 -0
- package/src/techniques/tools.js +106 -0
- package/src/techniques/workflow.js +413 -0
- package/src/techniques.js +78 -5607
- package/src/watch.js +18 -0
- package/src/windsurf/techniques.js +17 -12
package/src/org.js
CHANGED
|
@@ -1,48 +1,119 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { resolvePolicyLayers, applyPolicyLayersToOptions } = require('./policy-layers');
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
function summarizePolicyCoverage(contract) {
|
|
6
|
+
const validLayers = (contract?.layers || []).filter((layer) => layer.valid);
|
|
7
|
+
return {
|
|
8
|
+
layerCount: validLayers.length,
|
|
9
|
+
layerKeys: validLayers.map((layer) => layer.layer),
|
|
10
|
+
org: validLayers.some((layer) => layer.layer === 'org'),
|
|
11
|
+
team: validLayers.some((layer) => layer.layer === 'team'),
|
|
12
|
+
repo: validLayers.some((layer) => layer.layer === 'repo'),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildScoreBands(repos) {
|
|
17
|
+
const bands = {
|
|
18
|
+
strong: 0,
|
|
19
|
+
developing: 0,
|
|
20
|
+
bootstrap: 0,
|
|
21
|
+
unknown: 0,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (const repo of repos) {
|
|
25
|
+
if (typeof repo.score !== 'number') {
|
|
26
|
+
bands.unknown += 1;
|
|
27
|
+
} else if (repo.score >= 70) {
|
|
28
|
+
bands.strong += 1;
|
|
29
|
+
} else if (repo.score >= 40) {
|
|
30
|
+
bands.developing += 1;
|
|
31
|
+
} else {
|
|
32
|
+
bands.bootstrap += 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return bands;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildTopEvidence(repos) {
|
|
40
|
+
const counts = new Map();
|
|
41
|
+
for (const repo of repos) {
|
|
42
|
+
const key = repo.topActionKey;
|
|
43
|
+
if (!key) continue;
|
|
44
|
+
counts.set(key, (counts.get(key) || 0) + 1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [...counts.entries()]
|
|
48
|
+
.map(([key, repoCount]) => ({ key, repoCount }))
|
|
49
|
+
.sort((a, b) => b.repoCount - a.repoCount)
|
|
50
|
+
.slice(0, 5);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function scanOrg(dirs, options = {}) {
|
|
5
54
|
const { audit } = require('./audit');
|
|
6
55
|
const targets = Array.isArray(dirs) ? dirs : [];
|
|
7
56
|
const repos = [];
|
|
57
|
+
const fallbackPlatform = options.platform || 'claude';
|
|
8
58
|
|
|
9
59
|
for (const dir of targets) {
|
|
10
|
-
const
|
|
11
|
-
|
|
60
|
+
const resolvedDir = path.resolve(dir);
|
|
61
|
+
const policyContract = resolvePolicyLayers(resolvedDir);
|
|
62
|
+
const policyCoverage = summarizePolicyCoverage(policyContract);
|
|
63
|
+
|
|
64
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
12
65
|
repos.push({
|
|
13
66
|
name: path.basename(dir),
|
|
14
|
-
dir:
|
|
15
|
-
platform,
|
|
67
|
+
dir: resolvedDir,
|
|
68
|
+
platform: fallbackPlatform,
|
|
69
|
+
scoreType: 'live-repo-audit-score',
|
|
16
70
|
score: null,
|
|
17
71
|
passed: 0,
|
|
18
72
|
total: 0,
|
|
19
73
|
topAction: null,
|
|
74
|
+
topActionKey: null,
|
|
75
|
+
policyCoverage,
|
|
76
|
+
policyLayers: policyContract,
|
|
20
77
|
error: 'directory not found',
|
|
21
78
|
});
|
|
22
79
|
continue;
|
|
23
80
|
}
|
|
24
81
|
|
|
82
|
+
const repoOptions = applyPolicyLayersToOptions(policyContract, {
|
|
83
|
+
...options,
|
|
84
|
+
dir: resolvedDir,
|
|
85
|
+
silent: true,
|
|
86
|
+
});
|
|
87
|
+
|
|
25
88
|
try {
|
|
26
|
-
const result = await audit(
|
|
89
|
+
const result = await audit(repoOptions);
|
|
27
90
|
repos.push({
|
|
28
|
-
name: path.basename(
|
|
29
|
-
dir:
|
|
30
|
-
platform,
|
|
91
|
+
name: path.basename(resolvedDir),
|
|
92
|
+
dir: resolvedDir,
|
|
93
|
+
platform: repoOptions.platform || fallbackPlatform,
|
|
94
|
+
scoreType: 'live-repo-audit-score',
|
|
31
95
|
score: result.score,
|
|
32
96
|
passed: result.passed,
|
|
33
97
|
total: result.checkCount,
|
|
34
98
|
topAction: result.topNextActions?.[0]?.name || null,
|
|
99
|
+
topActionKey: result.topNextActions?.[0]?.key || null,
|
|
100
|
+
policyCoverage,
|
|
101
|
+
policyLayers: policyContract,
|
|
35
102
|
result,
|
|
36
103
|
});
|
|
37
104
|
} catch (error) {
|
|
38
105
|
repos.push({
|
|
39
|
-
name: path.basename(
|
|
40
|
-
dir:
|
|
41
|
-
platform,
|
|
106
|
+
name: path.basename(resolvedDir),
|
|
107
|
+
dir: resolvedDir,
|
|
108
|
+
platform: repoOptions.platform || fallbackPlatform,
|
|
109
|
+
scoreType: 'live-repo-audit-score',
|
|
42
110
|
score: null,
|
|
43
111
|
passed: 0,
|
|
44
112
|
total: 0,
|
|
45
113
|
topAction: null,
|
|
114
|
+
topActionKey: null,
|
|
115
|
+
policyCoverage,
|
|
116
|
+
policyLayers: policyContract,
|
|
46
117
|
error: error.message,
|
|
47
118
|
});
|
|
48
119
|
}
|
|
@@ -54,11 +125,24 @@ async function scanOrg(dirs, platform = 'claude') {
|
|
|
54
125
|
: 0;
|
|
55
126
|
|
|
56
127
|
return {
|
|
57
|
-
platform,
|
|
128
|
+
platform: fallbackPlatform,
|
|
58
129
|
repoCount: repos.length,
|
|
59
130
|
averageScore,
|
|
131
|
+
scoreType: 'org-live-average-score',
|
|
132
|
+
scoreSemantics: {
|
|
133
|
+
repoScoreType: 'live-repo-audit-score',
|
|
134
|
+
rollupScoreType: 'org-live-average-score',
|
|
135
|
+
note: 'Repo rows are live per-repo audits. The org average is a rollup across those live repo scores, not a snapshot score or benchmark projection.',
|
|
136
|
+
},
|
|
60
137
|
maxScore: validScores.length > 0 ? Math.max(...validScores) : 0,
|
|
61
138
|
minScore: validScores.length > 0 ? Math.min(...validScores) : 0,
|
|
139
|
+
scoreBands: buildScoreBands(repos),
|
|
140
|
+
policyCoverage: {
|
|
141
|
+
orgPolicyRepos: repos.filter((repo) => repo.policyCoverage.org).length,
|
|
142
|
+
teamPolicyRepos: repos.filter((repo) => repo.policyCoverage.team).length,
|
|
143
|
+
repoPolicyRepos: repos.filter((repo) => repo.policyCoverage.repo).length,
|
|
144
|
+
},
|
|
145
|
+
topEvidence: buildTopEvidence(repos),
|
|
62
146
|
repos,
|
|
63
147
|
};
|
|
64
148
|
}
|
package/src/plans.js
CHANGED
|
@@ -59,6 +59,32 @@ const FALLBACK_TEMPLATE_BY_KEY = {
|
|
|
59
59
|
agentsHaveMaxTurns: 'agents',
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
const VERIFICATION_TRIGGER_KEYS = new Set([
|
|
63
|
+
'verificationLoop',
|
|
64
|
+
'testCommand',
|
|
65
|
+
'lintCommand',
|
|
66
|
+
'buildCommand',
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const GOVERNANCE_TRIGGER_KEYS = new Set([
|
|
70
|
+
'permissionDeny',
|
|
71
|
+
'secretsProtection',
|
|
72
|
+
'preToolUseHook',
|
|
73
|
+
'postToolUseHook',
|
|
74
|
+
'sessionStartHook',
|
|
75
|
+
'hooksInSettings',
|
|
76
|
+
'settingsPermissions',
|
|
77
|
+
'securityReview',
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const AUTOMATION_TRIGGER_KEYS = new Set([
|
|
81
|
+
'customCommands',
|
|
82
|
+
'skills',
|
|
83
|
+
'agents',
|
|
84
|
+
'multipleAgents',
|
|
85
|
+
'multipleMcpServers',
|
|
86
|
+
]);
|
|
87
|
+
|
|
62
88
|
function previewContent(content) {
|
|
63
89
|
return content.split('\n').slice(0, 12).join('\n');
|
|
64
90
|
}
|
|
@@ -372,9 +398,140 @@ function toProposal(templateKey, triggers, templateFiles, ctx) {
|
|
|
372
398
|
};
|
|
373
399
|
}
|
|
374
400
|
|
|
401
|
+
function proposalMatchesTriggerKeys(proposal, keySet) {
|
|
402
|
+
return (proposal.triggers || []).some((trigger) => keySet.has(trigger.key));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function collectCampaignProposals(proposals, predicate) {
|
|
406
|
+
return proposals.filter((proposal) => proposal.readyToApply && predicate(proposal));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function buildCampaigns(bundle) {
|
|
410
|
+
const proposals = Array.isArray(bundle?.proposals) ? bundle.proposals : [];
|
|
411
|
+
const campaigns = [];
|
|
412
|
+
const maturity = bundle?.projectSummary?.maturity || 'unknown';
|
|
413
|
+
|
|
414
|
+
const starterBaseline = collectCampaignProposals(
|
|
415
|
+
proposals,
|
|
416
|
+
(proposal) => ['claude-md', 'commands', 'rules', 'hooks', 'agents'].includes(proposal.id),
|
|
417
|
+
);
|
|
418
|
+
if (starterBaseline.length > 0) {
|
|
419
|
+
campaigns.push({
|
|
420
|
+
key: 'starter-baseline',
|
|
421
|
+
label: 'Starter baseline',
|
|
422
|
+
summary: 'Establish the managed baseline surfaces Nerviq expects before deeper upgrades.',
|
|
423
|
+
proposalIds: starterBaseline.map((proposal) => proposal.id),
|
|
424
|
+
milestone: maturity === 'mature' ? 'pre-upgrade' : 'baseline',
|
|
425
|
+
focusAreas: ['config-drift', 'maturity-opportunity'],
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const verificationClosure = collectCampaignProposals(
|
|
430
|
+
proposals,
|
|
431
|
+
(proposal) => proposalMatchesTriggerKeys(proposal, VERIFICATION_TRIGGER_KEYS),
|
|
432
|
+
);
|
|
433
|
+
if (verificationClosure.length > 0) {
|
|
434
|
+
campaigns.push({
|
|
435
|
+
key: 'verification-closure',
|
|
436
|
+
label: 'Verification closure',
|
|
437
|
+
summary: 'Close missing test/lint/build loops so audits can be verified continuously.',
|
|
438
|
+
proposalIds: verificationClosure.map((proposal) => proposal.id),
|
|
439
|
+
milestone: 'post-fix',
|
|
440
|
+
focusAreas: ['config-drift', 'maturity-opportunity'],
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const governanceHardening = collectCampaignProposals(
|
|
445
|
+
proposals,
|
|
446
|
+
(proposal) => proposal.id === 'hooks' || proposalMatchesTriggerKeys(proposal, GOVERNANCE_TRIGGER_KEYS),
|
|
447
|
+
);
|
|
448
|
+
if (governanceHardening.length > 0) {
|
|
449
|
+
campaigns.push({
|
|
450
|
+
key: 'governance-hardening',
|
|
451
|
+
label: 'Governance hardening',
|
|
452
|
+
summary: 'Tighten permissions, hooks, secret protection, and reviewable safety defaults.',
|
|
453
|
+
proposalIds: governanceHardening.map((proposal) => proposal.id),
|
|
454
|
+
milestone: 'pre-upgrade',
|
|
455
|
+
focusAreas: ['policy-drift', 'config-drift'],
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const reviewableAutomation = collectCampaignProposals(
|
|
460
|
+
proposals,
|
|
461
|
+
(proposal) => proposal.id === 'agents' || proposal.id === 'commands' || proposalMatchesTriggerKeys(proposal, AUTOMATION_TRIGGER_KEYS),
|
|
462
|
+
);
|
|
463
|
+
if (reviewableAutomation.length > 0) {
|
|
464
|
+
campaigns.push({
|
|
465
|
+
key: 'reviewable-automation',
|
|
466
|
+
label: 'Reviewable automation',
|
|
467
|
+
summary: 'Add automation surfaces that keep upgrades inspectable instead of ad-hoc.',
|
|
468
|
+
proposalIds: reviewableAutomation.map((proposal) => proposal.id),
|
|
469
|
+
milestone: 'pre-upgrade',
|
|
470
|
+
focusAreas: ['maturity-opportunity', 'config-drift'],
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return campaigns.filter((campaign, index, all) =>
|
|
475
|
+
all.findIndex((item) => item.key === campaign.key) === index,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function filterBundleByCampaigns(bundle, campaignKeys = []) {
|
|
480
|
+
if (!Array.isArray(campaignKeys) || campaignKeys.length === 0) {
|
|
481
|
+
return bundle;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const available = new Map((bundle.campaigns || []).map((campaign) => [campaign.key, campaign]));
|
|
485
|
+
const missing = campaignKeys.filter((key) => !available.has(key));
|
|
486
|
+
if (missing.length > 0) {
|
|
487
|
+
throw new Error(`unknown campaign(s): ${missing.join(', ')}`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const selectedIds = new Set();
|
|
491
|
+
for (const key of campaignKeys) {
|
|
492
|
+
for (const proposalId of available.get(key).proposalIds || []) {
|
|
493
|
+
selectedIds.add(proposalId);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
...bundle,
|
|
499
|
+
selectedCampaigns: campaignKeys,
|
|
500
|
+
proposals: bundle.proposals.filter((proposal) => selectedIds.has(proposal.id)),
|
|
501
|
+
campaigns: (bundle.campaigns || []).filter((campaign) => campaignKeys.includes(campaign.key)),
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function resolveSelectionSet(bundle, options = {}) {
|
|
506
|
+
const proposalIds = new Set((bundle.proposals || []).map((proposal) => proposal.id));
|
|
507
|
+
const onlySet = options.only && options.only.length > 0 ? new Set(options.only) : null;
|
|
508
|
+
const campaignSet = options.campaigns && options.campaigns.length > 0
|
|
509
|
+
? new Set((bundle.campaigns || []).flatMap((campaign) => {
|
|
510
|
+
if (!options.campaigns.includes(campaign.key)) return [];
|
|
511
|
+
return campaign.proposalIds || [];
|
|
512
|
+
}))
|
|
513
|
+
: null;
|
|
514
|
+
|
|
515
|
+
if (onlySet && campaignSet) {
|
|
516
|
+
return new Set([...onlySet].filter((proposalId) => campaignSet.has(proposalId) && proposalIds.has(proposalId)));
|
|
517
|
+
}
|
|
518
|
+
if (onlySet) {
|
|
519
|
+
return new Set([...onlySet].filter((proposalId) => proposalIds.has(proposalId)));
|
|
520
|
+
}
|
|
521
|
+
if (campaignSet) {
|
|
522
|
+
return new Set([...campaignSet].filter((proposalId) => proposalIds.has(proposalId)));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
|
|
375
528
|
async function buildProposalBundle(options) {
|
|
376
529
|
if (options.platform === 'codex') {
|
|
377
|
-
|
|
530
|
+
const bundle = await buildCodexProposalBundle(options);
|
|
531
|
+
return filterBundleByCampaigns({
|
|
532
|
+
...bundle,
|
|
533
|
+
campaigns: buildCampaigns(bundle),
|
|
534
|
+
}, options.campaigns);
|
|
378
535
|
}
|
|
379
536
|
|
|
380
537
|
const ctx = new ProjectContext(options.dir);
|
|
@@ -405,7 +562,7 @@ async function buildProposalBundle(options) {
|
|
|
405
562
|
return impactB - impactA;
|
|
406
563
|
});
|
|
407
564
|
|
|
408
|
-
return {
|
|
565
|
+
return filterBundleByCampaigns({
|
|
409
566
|
schemaVersion: 1,
|
|
410
567
|
generatedBy: `nerviq@${version}`,
|
|
411
568
|
createdAt: new Date().toISOString(),
|
|
@@ -416,7 +573,8 @@ async function buildProposalBundle(options) {
|
|
|
416
573
|
riskNotes: report.riskNotes,
|
|
417
574
|
mcpPreflightWarnings,
|
|
418
575
|
proposals,
|
|
419
|
-
|
|
576
|
+
campaigns: buildCampaigns({ proposals, projectSummary: report.projectSummary }),
|
|
577
|
+
}, options.campaigns);
|
|
420
578
|
}
|
|
421
579
|
|
|
422
580
|
function printProposalBundle(bundle, options = {}) {
|
|
@@ -429,8 +587,19 @@ function printProposalBundle(bundle, options = {}) {
|
|
|
429
587
|
console.log(' nerviq plan');
|
|
430
588
|
console.log(' ═══════════════════════════════════════');
|
|
431
589
|
console.log(` ${bundle.projectSummary.name} | maturity=${bundle.projectSummary.maturity} | score=${bundle.projectSummary.score}/100`);
|
|
590
|
+
if (bundle.projectSummary.archetype) {
|
|
591
|
+
console.log(` archetype=${bundle.projectSummary.archetype} | workflow=${bundle.projectSummary.workflow || 'unknown'} | risk=${bundle.projectSummary.riskLevel || 'unknown'}`);
|
|
592
|
+
}
|
|
593
|
+
if (bundle.projectSummary.operatingProfile) {
|
|
594
|
+
console.log(` operating-profile=${bundle.projectSummary.operatingProfile}`);
|
|
595
|
+
}
|
|
432
596
|
console.log('');
|
|
433
597
|
|
|
598
|
+
if (bundle.selectedCampaigns && bundle.selectedCampaigns.length > 0) {
|
|
599
|
+
console.log(` Selected campaigns: ${bundle.selectedCampaigns.join(', ')}`);
|
|
600
|
+
console.log('');
|
|
601
|
+
}
|
|
602
|
+
|
|
434
603
|
if (bundle.mcpPreflightWarnings && bundle.mcpPreflightWarnings.length > 0) {
|
|
435
604
|
console.log(' MCP Preflight Warnings');
|
|
436
605
|
for (const warning of bundle.mcpPreflightWarnings) {
|
|
@@ -445,6 +614,16 @@ function printProposalBundle(bundle, options = {}) {
|
|
|
445
614
|
return;
|
|
446
615
|
}
|
|
447
616
|
|
|
617
|
+
if (bundle.campaigns && bundle.campaigns.length > 0) {
|
|
618
|
+
console.log(' Upgrade campaigns');
|
|
619
|
+
for (const campaign of bundle.campaigns) {
|
|
620
|
+
console.log(` - ${campaign.key} (${campaign.milestone})`);
|
|
621
|
+
console.log(` ${campaign.label} — ${campaign.summary}`);
|
|
622
|
+
console.log(` proposals: ${campaign.proposalIds.join(', ')}`);
|
|
623
|
+
}
|
|
624
|
+
console.log('');
|
|
625
|
+
}
|
|
626
|
+
|
|
448
627
|
console.log(' Proposal Bundles');
|
|
449
628
|
for (const proposal of bundle.proposals) {
|
|
450
629
|
const applyState = proposal.readyToApply ? 'ready' : 'manual-review';
|
|
@@ -536,9 +715,12 @@ function applyRuntimeSettingsOverlays(bundle, options) {
|
|
|
536
715
|
|
|
537
716
|
function resolvePlan(bundle, options) {
|
|
538
717
|
if (options.planFile) {
|
|
539
|
-
return
|
|
718
|
+
return filterBundleByCampaigns(
|
|
719
|
+
applyRuntimeSettingsOverlays(JSON.parse(fs.readFileSync(options.planFile, 'utf8')), options),
|
|
720
|
+
options.campaigns,
|
|
721
|
+
);
|
|
540
722
|
}
|
|
541
|
-
return applyRuntimeSettingsOverlays(bundle, options);
|
|
723
|
+
return filterBundleByCampaigns(applyRuntimeSettingsOverlays(bundle, options), options.campaigns);
|
|
542
724
|
}
|
|
543
725
|
|
|
544
726
|
async function applyProposalBundle(options) {
|
|
@@ -546,9 +728,7 @@ async function applyProposalBundle(options) {
|
|
|
546
728
|
const bundle = resolvePlan(liveBundle, options);
|
|
547
729
|
const mcpPreflightWarnings = getMcpPackPreflight(options.mcpPacks || [])
|
|
548
730
|
.filter(item => item.missingEnvVars.length > 0);
|
|
549
|
-
const selectedIds =
|
|
550
|
-
? new Set(options.only)
|
|
551
|
-
: null;
|
|
731
|
+
const selectedIds = resolveSelectionSet(bundle, options);
|
|
552
732
|
const selected = bundle.proposals.filter(proposal => {
|
|
553
733
|
if (selectedIds && !selectedIds.has(proposal.id)) return false;
|
|
554
734
|
return proposal.readyToApply;
|
|
@@ -606,6 +786,7 @@ async function applyProposalBundle(options) {
|
|
|
606
786
|
return {
|
|
607
787
|
proposalCount: bundle.proposals.length,
|
|
608
788
|
appliedProposalIds: selected.map(item => item.id),
|
|
789
|
+
selectedCampaigns: bundle.selectedCampaigns || options.campaigns || [],
|
|
609
790
|
createdFiles,
|
|
610
791
|
patchedFiles: patchedFiles.map(file => file.path),
|
|
611
792
|
skippedFiles,
|
|
@@ -629,6 +810,9 @@ function printApplyResult(result, options = {}) {
|
|
|
629
810
|
console.log(' Dry-run only. No files were written.');
|
|
630
811
|
}
|
|
631
812
|
console.log(` Applied proposal bundles: ${result.appliedProposalIds.join(', ') || 'none'}`);
|
|
813
|
+
if (result.selectedCampaigns && result.selectedCampaigns.length > 0) {
|
|
814
|
+
console.log(` Campaigns: ${result.selectedCampaigns.join(', ')}`);
|
|
815
|
+
}
|
|
632
816
|
console.log(` Created files: ${result.createdFiles.join(', ') || 'none'}`);
|
|
633
817
|
console.log(` Patched files: ${result.patchedFiles.join(', ') || 'none'}`);
|
|
634
818
|
if (result.mcpPreflightWarnings && result.mcpPreflightWarnings.length > 0) {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const claudeFreshness = require('./freshness');
|
|
4
|
+
const codexFreshness = require('./codex/freshness');
|
|
5
|
+
const cursorFreshness = require('./cursor/freshness');
|
|
6
|
+
const copilotFreshness = require('./copilot/freshness');
|
|
7
|
+
const geminiFreshness = require('./gemini/freshness');
|
|
8
|
+
const windsurfFreshness = require('./windsurf/freshness');
|
|
9
|
+
const aiderFreshness = require('./aider/freshness');
|
|
10
|
+
const opencodeFreshness = require('./opencode/freshness');
|
|
11
|
+
|
|
12
|
+
const DAILY_FRESHNESS_WORKFLOW = {
|
|
13
|
+
workflow: '.github/workflows/freshness-check.yml',
|
|
14
|
+
cadence: 'Daily at 06:00 UTC plus manual dispatch',
|
|
15
|
+
issuePolicy: 'Open or refresh GitHub issues for stale P0 sources without failing the main CI pipeline.',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const PLATFORM_CHANGE_MANIFEST = [
|
|
19
|
+
{ key: 'claude', label: 'Claude Code', modulePath: 'src/freshness.js', freshness: claudeFreshness },
|
|
20
|
+
{ key: 'codex', label: 'Codex', modulePath: 'src/codex/freshness.js', freshness: codexFreshness },
|
|
21
|
+
{ key: 'cursor', label: 'Cursor', modulePath: 'src/cursor/freshness.js', freshness: cursorFreshness },
|
|
22
|
+
{ key: 'copilot', label: 'Copilot', modulePath: 'src/copilot/freshness.js', freshness: copilotFreshness },
|
|
23
|
+
{ key: 'gemini', label: 'Gemini CLI', modulePath: 'src/gemini/freshness.js', freshness: geminiFreshness },
|
|
24
|
+
{ key: 'windsurf', label: 'Windsurf', modulePath: 'src/windsurf/freshness.js', freshness: windsurfFreshness },
|
|
25
|
+
{ key: 'aider', label: 'Aider', modulePath: 'src/aider/freshness.js', freshness: aiderFreshness },
|
|
26
|
+
{ key: 'opencode', label: 'OpenCode', modulePath: 'src/opencode/freshness.js', freshness: opencodeFreshness },
|
|
27
|
+
].map((entry) => {
|
|
28
|
+
const sources = (entry.freshness.P0_SOURCES || []).map((source) => ({
|
|
29
|
+
key: source.key,
|
|
30
|
+
label: source.label,
|
|
31
|
+
url: source.url,
|
|
32
|
+
stalenessThresholdDays: source.stalenessThresholdDays,
|
|
33
|
+
verifiedAt: source.verifiedAt || null,
|
|
34
|
+
}));
|
|
35
|
+
const thresholds = [...new Set(sources.map((source) => source.stalenessThresholdDays))].sort((a, b) => a - b);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
key: entry.key,
|
|
39
|
+
label: entry.label,
|
|
40
|
+
modulePath: entry.modulePath,
|
|
41
|
+
trackedSources: sources,
|
|
42
|
+
trackedSourceCount: sources.length,
|
|
43
|
+
reviewCadence: {
|
|
44
|
+
automation: DAILY_FRESHNESS_WORKFLOW.cadence,
|
|
45
|
+
thresholdsDays: thresholds,
|
|
46
|
+
manualExpectation: thresholds.includes(14)
|
|
47
|
+
? 'Review high-volatility sources weekly and verify any stale source immediately.'
|
|
48
|
+
: 'Review sources at least monthly and verify any stale source immediately.',
|
|
49
|
+
},
|
|
50
|
+
freshnessWorkflow: {
|
|
51
|
+
...DAILY_FRESHNESS_WORKFLOW,
|
|
52
|
+
manualTrigger: true,
|
|
53
|
+
},
|
|
54
|
+
updateTriggers: (entry.freshness.PROPAGATION_CHECKLIST || []).map((item) => ({
|
|
55
|
+
trigger: item.trigger,
|
|
56
|
+
targets: item.targets || [],
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function getPlatformChangeManifest() {
|
|
62
|
+
return JSON.parse(JSON.stringify(PLATFORM_CHANGE_MANIFEST));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function summarizePlatformChangeManifest() {
|
|
66
|
+
const manifest = getPlatformChangeManifest();
|
|
67
|
+
return {
|
|
68
|
+
platformCount: manifest.length,
|
|
69
|
+
trackedSourceCount: manifest.reduce((sum, entry) => sum + entry.trackedSourceCount, 0),
|
|
70
|
+
workflow: DAILY_FRESHNESS_WORKFLOW,
|
|
71
|
+
platforms: manifest.map((entry) => ({
|
|
72
|
+
key: entry.key,
|
|
73
|
+
label: entry.label,
|
|
74
|
+
trackedSourceCount: entry.trackedSourceCount,
|
|
75
|
+
thresholdDays: entry.reviewCadence.thresholdsDays,
|
|
76
|
+
updateTriggerCount: entry.updateTriggers.length,
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
DAILY_FRESHNESS_WORKFLOW,
|
|
83
|
+
PLATFORM_CHANGE_MANIFEST,
|
|
84
|
+
getPlatformChangeManifest,
|
|
85
|
+
summarizePlatformChangeManifest,
|
|
86
|
+
};
|