@ryuenn3123/agentic-senior-core 1.9.3 → 1.9.4
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/.agent-context/marketplace/trust-tiers.json +114 -0
- package/.agent-context/review-checklists/marketplace-acceptance.md +60 -0
- package/.agent-context/rules/security.md +2 -1
- package/.agent-context/state/onboarding-report.json +5 -10
- package/.cursorrules +4 -3
- package/.windsurfrules +4 -3
- package/bin/agentic-senior-core.js +6 -0
- package/lib/cli/backup.mjs +126 -0
- package/lib/cli/commands/init.mjs +25 -0
- package/lib/cli/commands/rollback.mjs +57 -0
- package/lib/cli/commands/upgrade.mjs +49 -21
- package/lib/cli/preflight.mjs +97 -0
- package/lib/cli/rollback.mjs +67 -0
- package/lib/cli/utils.mjs +1 -0
- package/package.json +1 -1
- package/scripts/validate.mjs +62 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "trust-tiers-v1",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Defines trust tiers for marketplace artifacts. Each tier sets the minimum evidence and quality thresholds required for acceptance.",
|
|
5
|
+
"tiers": {
|
|
6
|
+
"verified": {
|
|
7
|
+
"displayName": "Verified",
|
|
8
|
+
"description": "Meets all quality gates, has complete evidence bundles, and is actively maintained. Ready for production use.",
|
|
9
|
+
"badge": "verified",
|
|
10
|
+
"minimumScore": 85,
|
|
11
|
+
"requirements": {
|
|
12
|
+
"hasReadme": true,
|
|
13
|
+
"hasTests": true,
|
|
14
|
+
"hasEvidence": true,
|
|
15
|
+
"hasCompatManifest": true,
|
|
16
|
+
"hasChangelog": true,
|
|
17
|
+
"minDocScore": 80,
|
|
18
|
+
"minTestScore": 80,
|
|
19
|
+
"minEvidenceScore": 80,
|
|
20
|
+
"minMaintenanceScore": 80,
|
|
21
|
+
"maxDaysSinceUpdate": 90,
|
|
22
|
+
"forbiddenContentClean": true
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"community": {
|
|
26
|
+
"displayName": "Community",
|
|
27
|
+
"description": "Has basic documentation and some evidence. Functional but may lack comprehensive test coverage or recent maintenance.",
|
|
28
|
+
"badge": "community",
|
|
29
|
+
"minimumScore": 50,
|
|
30
|
+
"requirements": {
|
|
31
|
+
"hasReadme": true,
|
|
32
|
+
"hasTests": false,
|
|
33
|
+
"hasEvidence": false,
|
|
34
|
+
"hasCompatManifest": false,
|
|
35
|
+
"hasChangelog": false,
|
|
36
|
+
"minDocScore": 50,
|
|
37
|
+
"minTestScore": 0,
|
|
38
|
+
"minEvidenceScore": 0,
|
|
39
|
+
"minMaintenanceScore": 0,
|
|
40
|
+
"maxDaysSinceUpdate": 365,
|
|
41
|
+
"forbiddenContentClean": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"experimental": {
|
|
45
|
+
"displayName": "Experimental",
|
|
46
|
+
"description": "Newly submitted or under development. No quality guarantees. Use at own risk.",
|
|
47
|
+
"badge": "experimental",
|
|
48
|
+
"minimumScore": 0,
|
|
49
|
+
"requirements": {
|
|
50
|
+
"hasReadme": true,
|
|
51
|
+
"hasTests": false,
|
|
52
|
+
"hasEvidence": false,
|
|
53
|
+
"hasCompatManifest": false,
|
|
54
|
+
"hasChangelog": false,
|
|
55
|
+
"minDocScore": 0,
|
|
56
|
+
"minTestScore": 0,
|
|
57
|
+
"minEvidenceScore": 0,
|
|
58
|
+
"minMaintenanceScore": 0,
|
|
59
|
+
"maxDaysSinceUpdate": null,
|
|
60
|
+
"forbiddenContentClean": true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"scorecard": {
|
|
65
|
+
"description": "Weighted scorecard used to calculate the trust score of marketplace artifacts. Total weight sums to 100.",
|
|
66
|
+
"dimensions": {
|
|
67
|
+
"documentation": {
|
|
68
|
+
"weight": 25,
|
|
69
|
+
"description": "Quality and completeness of documentation. Checks README, inline docs, usage examples, and API descriptions.",
|
|
70
|
+
"gates": [
|
|
71
|
+
"README.md exists and is non-empty",
|
|
72
|
+
"Contains usage examples or getting started section",
|
|
73
|
+
"Contains API or configuration reference",
|
|
74
|
+
"Contains license declaration",
|
|
75
|
+
"No placeholder or TODO-only content"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"tests": {
|
|
79
|
+
"weight": 25,
|
|
80
|
+
"description": "Test coverage and quality. Checks for test files, pass rate, and coverage of core functionality.",
|
|
81
|
+
"gates": [
|
|
82
|
+
"At least one test file exists",
|
|
83
|
+
"Tests pass without errors",
|
|
84
|
+
"Core functions have corresponding tests",
|
|
85
|
+
"No skipped or TODO tests in critical paths",
|
|
86
|
+
"Test command is documented"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
"evidence": {
|
|
90
|
+
"weight": 25,
|
|
91
|
+
"description": "Completeness of evidence bundles. Checks for compatibility manifests, SBOM excerpts, and validation reports.",
|
|
92
|
+
"gates": [
|
|
93
|
+
"Evidence bundle directory exists",
|
|
94
|
+
"Compatibility manifest is present",
|
|
95
|
+
"Validation report is present",
|
|
96
|
+
"SBOM excerpt or dependency list is present",
|
|
97
|
+
"Evidence files are not stale (updated within maxDaysSinceUpdate)"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
"maintenance": {
|
|
101
|
+
"weight": 25,
|
|
102
|
+
"description": "Maintenance activity and health. Checks for recent updates, changelog entries, and responsiveness.",
|
|
103
|
+
"gates": [
|
|
104
|
+
"Last update within maxDaysSinceUpdate threshold",
|
|
105
|
+
"CHANGELOG.md exists with recent entries",
|
|
106
|
+
"No known security vulnerabilities in dependencies",
|
|
107
|
+
"Version follows semantic versioning",
|
|
108
|
+
"Owner or maintainer is declared"
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"totalWeight": 100
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Marketplace Acceptance Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist to evaluate marketplace artifact submissions. Every gate must be verified before assigning a trust tier.
|
|
4
|
+
|
|
5
|
+
## Tier Assignment Rule
|
|
6
|
+
|
|
7
|
+
- **Verified**: all 20 gates pass, composite score >= 85
|
|
8
|
+
- **Community**: gates 1-4 pass, composite score >= 50
|
|
9
|
+
- **Experimental**: gate 1 passes, composite score >= 0
|
|
10
|
+
- **Rejected**: gate 1 fails or forbidden content detected
|
|
11
|
+
|
|
12
|
+
## Documentation (25%)
|
|
13
|
+
|
|
14
|
+
- [ ] 1. README.md exists and contains at least 200 characters of non-boilerplate content
|
|
15
|
+
- [ ] 2. README includes a usage example or getting-started section
|
|
16
|
+
- [ ] 3. README includes an API or configuration reference
|
|
17
|
+
- [ ] 4. License declaration is present (LICENSE file or header)
|
|
18
|
+
- [ ] 5. No placeholder-only content (no files that are entirely TODO or stub)
|
|
19
|
+
|
|
20
|
+
## Tests (25%)
|
|
21
|
+
|
|
22
|
+
- [ ] 6. At least one test file exists in a recognized test directory
|
|
23
|
+
- [ ] 7. All tests pass without errors when executed
|
|
24
|
+
- [ ] 8. Core exported functions have corresponding test cases
|
|
25
|
+
- [ ] 9. No skipped or disabled tests in critical code paths
|
|
26
|
+
- [ ] 10. Test execution command is documented in README or package metadata
|
|
27
|
+
|
|
28
|
+
## Evidence (25%)
|
|
29
|
+
|
|
30
|
+
- [ ] 11. Evidence bundle directory exists at artifact root (e.g. `evidence/` or `.evidence/`)
|
|
31
|
+
- [ ] 12. Compatibility manifest is present declaring supported runtimes and IDE versions
|
|
32
|
+
- [ ] 13. Validation report from the most recent CI run is present
|
|
33
|
+
- [ ] 14. Dependency list or SBOM excerpt is present
|
|
34
|
+
- [ ] 15. Evidence files are current (updated within the tier's maxDaysSinceUpdate threshold)
|
|
35
|
+
|
|
36
|
+
## Maintenance (25%)
|
|
37
|
+
|
|
38
|
+
- [ ] 16. Last meaningful update is within the tier's maxDaysSinceUpdate threshold
|
|
39
|
+
- [ ] 17. CHANGELOG.md exists with at least one entry for the current major version
|
|
40
|
+
- [ ] 18. No known critical or high-severity vulnerabilities in direct dependencies
|
|
41
|
+
- [ ] 19. Version follows semantic versioning (MAJOR.MINOR.PATCH)
|
|
42
|
+
- [ ] 20. Owner or maintainer is declared in package metadata or README
|
|
43
|
+
|
|
44
|
+
## Security (Mandatory, All Tiers)
|
|
45
|
+
|
|
46
|
+
- [ ] Forbidden content scan passes (no hardcoded secrets, API keys, or private paths)
|
|
47
|
+
- [ ] No `eval()` or dynamic code execution in published assets
|
|
48
|
+
- [ ] No network calls to undeclared external endpoints
|
|
49
|
+
|
|
50
|
+
## Scoring Notes
|
|
51
|
+
|
|
52
|
+
Composite score is calculated as:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
score = (doc_score * 0.25) + (test_score * 0.25) + (evidence_score * 0.25) + (maintenance_score * 0.25)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each dimension score is: (gates_passed / total_gates_in_dimension) * 100
|
|
59
|
+
|
|
60
|
+
Tier thresholds are defined in `.agent-context/marketplace/trust-tiers.json`.
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cliVersion": "1.9.
|
|
3
|
-
"generatedAt": "2026-04-
|
|
4
|
-
"operationMode": "
|
|
2
|
+
"cliVersion": "1.9.4",
|
|
3
|
+
"generatedAt": "2026-04-08T03:01:45.024Z",
|
|
4
|
+
"operationMode": "upgrade",
|
|
5
5
|
"selectedProfile": "beginner",
|
|
6
6
|
"selectedProfilePack": null,
|
|
7
|
-
"selectedPreset": "frontend-web",
|
|
8
7
|
"selectedStack": "typescript.md",
|
|
9
8
|
"selectedBlueprint": "api-nextjs.md",
|
|
10
9
|
"ciGuardrailsEnabled": true,
|
|
11
|
-
"setupDurationMs":
|
|
12
|
-
"selectedSkillDomains": [
|
|
13
|
-
"frontend",
|
|
14
|
-
"fullstack",
|
|
15
|
-
"cli"
|
|
16
|
-
],
|
|
10
|
+
"setupDurationMs": 605,
|
|
11
|
+
"selectedSkillDomains": [],
|
|
17
12
|
"autoDetection": {
|
|
18
13
|
"recommendedStack": "typescript.md",
|
|
19
14
|
"recommendedBlueprint": "api-nextjs.md",
|
package/.cursorrules
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
|
|
2
2
|
|
|
3
|
-
Generated by Agentic-Senior-Core CLI v1.9.
|
|
4
|
-
Timestamp: 2026-04-
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v1.9.4
|
|
4
|
+
Timestamp: 2026-04-08T03:01:44.455Z
|
|
5
5
|
Selected profile: beginner
|
|
6
6
|
Selected policy file: .agent-context/policies/llm-judge-threshold.json
|
|
7
7
|
|
|
@@ -2278,10 +2278,11 @@ coverage/
|
|
|
2278
2278
|
.nyc_output/
|
|
2279
2279
|
*.lcov
|
|
2280
2280
|
|
|
2281
|
-
# ── Runtime Data ──
|
|
2281
|
+
# ── Runtime & Backup Data ──
|
|
2282
2282
|
*.pid
|
|
2283
2283
|
*.seed
|
|
2284
2284
|
*.pid.lock
|
|
2285
|
+
.agentic-backup/
|
|
2285
2286
|
|
|
2286
2287
|
# ── Secrets & Keys ──
|
|
2287
2288
|
*.pem
|
package/.windsurfrules
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
|
|
2
2
|
|
|
3
|
-
Generated by Agentic-Senior-Core CLI v1.9.
|
|
4
|
-
Timestamp: 2026-04-
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v1.9.4
|
|
4
|
+
Timestamp: 2026-04-08T03:01:44.455Z
|
|
5
5
|
Selected profile: beginner
|
|
6
6
|
Selected policy file: .agent-context/policies/llm-judge-threshold.json
|
|
7
7
|
|
|
@@ -2278,10 +2278,11 @@ coverage/
|
|
|
2278
2278
|
.nyc_output/
|
|
2279
2279
|
*.lcov
|
|
2280
2280
|
|
|
2281
|
-
# ── Runtime Data ──
|
|
2281
|
+
# ── Runtime & Backup Data ──
|
|
2282
2282
|
*.pid
|
|
2283
2283
|
*.seed
|
|
2284
2284
|
*.pid.lock
|
|
2285
|
+
.agentic-backup/
|
|
2285
2286
|
|
|
2286
2287
|
# ── Secrets & Keys ──
|
|
2287
2288
|
*.pem
|
|
@@ -10,6 +10,7 @@ import { exit } from 'node:process';
|
|
|
10
10
|
import { CLI_VERSION } from '../lib/cli/constants.mjs';
|
|
11
11
|
import { printUsage } from '../lib/cli/utils.mjs';
|
|
12
12
|
import { runLaunchCommand } from '../lib/cli/commands/launch.mjs';
|
|
13
|
+
import { runRollbackCommand } from '../lib/cli/commands/rollback.mjs';
|
|
13
14
|
import { runInitCommand, parseInitArguments } from '../lib/cli/commands/init.mjs';
|
|
14
15
|
import { runUpgradeCommand, parseUpgradeArguments } from '../lib/cli/commands/upgrade.mjs';
|
|
15
16
|
import { runSkillCommand } from '../lib/cli/skill-selector.mjs';
|
|
@@ -50,6 +51,11 @@ async function main() {
|
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
if (commandArgument === 'rollback') {
|
|
55
|
+
await runRollbackCommand(commandArguments);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
console.error(`Unknown command: ${commandArgument}`);
|
|
54
60
|
printUsage();
|
|
55
61
|
exit(1);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { pathExists, ensureDirectory } from './utils.mjs';
|
|
5
|
+
|
|
6
|
+
const BACKUP_DIR_NAME = '.agentic-backup';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Calculates a SHA-256 hash of a file's contents.
|
|
10
|
+
*/
|
|
11
|
+
async function hashFile(filePath) {
|
|
12
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
13
|
+
const hashSum = crypto.createHash('sha256');
|
|
14
|
+
hashSum.update(fileBuffer);
|
|
15
|
+
return hashSum.digest('hex');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Helper to get all files in a directory recursively.
|
|
20
|
+
*/
|
|
21
|
+
async function getFilesInDirectory(dirPath) {
|
|
22
|
+
const files = [];
|
|
23
|
+
try {
|
|
24
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === BACKUP_DIR_NAME) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
30
|
+
if (entry.isDirectory()) {
|
|
31
|
+
const nestedFiles = await getFilesInDirectory(fullPath);
|
|
32
|
+
files.push(...nestedFiles);
|
|
33
|
+
} else {
|
|
34
|
+
files.push(fullPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code !== 'ENOENT') {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return files;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a backup of specific paths in the target directory.
|
|
47
|
+
* Currently backs up: .cursorrules, .windsurfrules, and the entire .agent-context directory.
|
|
48
|
+
* @param {string} targetDirectoryPath
|
|
49
|
+
*/
|
|
50
|
+
export async function createBackup(targetDirectoryPath) {
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
const backupRoot = path.join(targetDirectoryPath, BACKUP_DIR_NAME);
|
|
53
|
+
const objectsDir = path.join(backupRoot, 'objects');
|
|
54
|
+
|
|
55
|
+
await ensureDirectory(objectsDir);
|
|
56
|
+
|
|
57
|
+
const pathsToTrack = [
|
|
58
|
+
path.join(targetDirectoryPath, '.cursorrules'),
|
|
59
|
+
path.join(targetDirectoryPath, '.windsurfrules'),
|
|
60
|
+
path.join(targetDirectoryPath, '.agent-context')
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const resolvedFilesToTrack = [];
|
|
64
|
+
|
|
65
|
+
for (const trackPath of pathsToTrack) {
|
|
66
|
+
try {
|
|
67
|
+
const stats = await fs.stat(trackPath);
|
|
68
|
+
if (stats.isDirectory()) {
|
|
69
|
+
const nested = await getFilesInDirectory(trackPath);
|
|
70
|
+
resolvedFilesToTrack.push(...nested);
|
|
71
|
+
} else if (stats.isFile()) {
|
|
72
|
+
resolvedFilesToTrack.push(trackPath);
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.code !== 'ENOENT') {
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
// If it doesn't exist, we track it as missing so that a rollback knows to delete it
|
|
79
|
+
// if it gets created during the run. Wait, tracking everything that doesn't exist
|
|
80
|
+
// is infinite. We should record the paths we intend to write to as "absent" state.
|
|
81
|
+
resolvedFilesToTrack.push(trackPath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const manifest = {
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
files: {}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
for (const filePath of resolvedFilesToTrack) {
|
|
91
|
+
const relativePath = path.relative(targetDirectoryPath, filePath);
|
|
92
|
+
// Ignore any backup operations within the backup directory itself
|
|
93
|
+
if (relativePath.startsWith(BACKUP_DIR_NAME)) continue;
|
|
94
|
+
|
|
95
|
+
if (await pathExists(filePath)) {
|
|
96
|
+
// File exists: hash and backup
|
|
97
|
+
const fileHash = await hashFile(filePath);
|
|
98
|
+
const destObjectPath = path.join(objectsDir, fileHash);
|
|
99
|
+
|
|
100
|
+
if (!(await pathExists(destObjectPath))) {
|
|
101
|
+
await fs.copyFile(filePath, destObjectPath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const stats = await fs.stat(filePath);
|
|
105
|
+
manifest.files[relativePath] = {
|
|
106
|
+
action: 'restore',
|
|
107
|
+
hash: fileHash,
|
|
108
|
+
mtimeMs: stats.mtimeMs
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
// File did not exist before operation: must be deleted on rollback
|
|
112
|
+
manifest.files[relativePath] = {
|
|
113
|
+
action: 'delete'
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const manifestPath = path.join(backupRoot, 'manifest.json');
|
|
119
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
manifestPath,
|
|
123
|
+
backupRoot,
|
|
124
|
+
durationMs: Date.now() - startTime
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -32,6 +32,9 @@ import { collectProfilePacks, findProfilePackByInput } from '../profile-packs.mj
|
|
|
32
32
|
import { inferSkillDomainNamesFromSelection } from '../skill-selector.mjs';
|
|
33
33
|
import { detectProjectContext, buildDetectionSummary, formatDetectionCandidates } from '../detector.mjs';
|
|
34
34
|
import { compileDynamicContext, writeSelectedPolicy, writeOnboardingReport } from '../compiler.mjs';
|
|
35
|
+
import { runPreflightChecks } from '../preflight.mjs';
|
|
36
|
+
import { createBackup } from '../backup.mjs';
|
|
37
|
+
import { performRollback } from '../rollback.mjs';
|
|
35
38
|
|
|
36
39
|
export { REPO_ROOT } from '../constants.mjs';
|
|
37
40
|
|
|
@@ -142,6 +145,15 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
142
145
|
const setupStartedAt = Date.now();
|
|
143
146
|
await ensureDirectory(resolvedTargetDirectoryPath);
|
|
144
147
|
|
|
148
|
+
const preflightResult = await runPreflightChecks(resolvedTargetDirectoryPath, 'init');
|
|
149
|
+
if (!preflightResult.passed) {
|
|
150
|
+
console.error('\n[FATAL] Preflight checks failed. Initializing here would cause errors or data loss:');
|
|
151
|
+
console.error(JSON.stringify(preflightResult, null, 2));
|
|
152
|
+
throw new Error('Preflight checks failed.');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const backup = await createBackup(resolvedTargetDirectoryPath);
|
|
156
|
+
|
|
145
157
|
const userInterface = createInterface({ input: stdin, output: stdout });
|
|
146
158
|
|
|
147
159
|
try {
|
|
@@ -333,6 +345,19 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
333
345
|
console.log('\nPlain-language summary:');
|
|
334
346
|
console.log(`I prepared a ${selectedProfile.displayName.toLowerCase()} governance pack for a ${toTitleCase(selectedResolvedStackFileName)} project using the ${toTitleCase(selectedResolvedBlueprintFileName)} blueprint.`);
|
|
335
347
|
console.log('Your AI tools will now receive one compiled rulebook plus the original source rules, and your review threshold is stored in .agent-context/policies/llm-judge-threshold.json.');
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error('\n[FATAL] An error occurred during initialization. Attempting automatic rollback...');
|
|
350
|
+
try {
|
|
351
|
+
const rollbackReport = await performRollback(resolvedTargetDirectoryPath);
|
|
352
|
+
if (rollbackReport.success) {
|
|
353
|
+
console.error('[OK] Automatic rollback successful. The directory has been restored.');
|
|
354
|
+
} else {
|
|
355
|
+
console.error('[WARN] Automatic rollback completed with errors. You may need to manually clean up.');
|
|
356
|
+
}
|
|
357
|
+
} catch (rbError) {
|
|
358
|
+
console.error(`[FATAL] Automatic rollback failed: ${rbError.message}`);
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
336
361
|
} finally {
|
|
337
362
|
userInterface.close();
|
|
338
363
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { performRollback } from '../rollback.mjs';
|
|
3
|
+
import { ensureDirectory } from '../utils.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Command module for `rollback`
|
|
7
|
+
*/
|
|
8
|
+
export async function runRollbackCommand(commandArguments) {
|
|
9
|
+
// Parse simple argument <targetDir>
|
|
10
|
+
const targetDirectoryArgument = commandArguments.length > 0 ? commandArguments[0] : null;
|
|
11
|
+
|
|
12
|
+
if (!targetDirectoryArgument) {
|
|
13
|
+
console.error('Usage: agentic-senior-core rollback <target-directory>');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryArgument);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await ensureDirectory(resolvedTargetDirectoryPath);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(`[FATAL] Cannot access target directory: ${resolvedTargetDirectoryPath}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`Starting manual rollback for directory: ${resolvedTargetDirectoryPath} ...\n`);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const report = await performRollback(resolvedTargetDirectoryPath);
|
|
30
|
+
|
|
31
|
+
if (report.success) {
|
|
32
|
+
console.log('[OK] Rollback successful.\n');
|
|
33
|
+
} else {
|
|
34
|
+
console.error('[WARN] Rollback completed with errors.\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('Restored files:');
|
|
38
|
+
if (report.restoredFiles.length === 0) console.log(' (none)');
|
|
39
|
+
report.restoredFiles.forEach(f => console.log(` + ${f}`));
|
|
40
|
+
|
|
41
|
+
console.log('\nDeleted (new) files:');
|
|
42
|
+
if (report.deletedFiles.length === 0) console.log(' (none)');
|
|
43
|
+
report.deletedFiles.forEach(f => console.log(` - ${f}`));
|
|
44
|
+
|
|
45
|
+
if (report.errors.length > 0) {
|
|
46
|
+
console.error('\nErrors encountered:');
|
|
47
|
+
report.errors.forEach(e => console.error(` ! ${e}`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const exitCode = report.success ? 0 : 1;
|
|
51
|
+
process.exit(exitCode);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('\n[FATAL] Rollback operation failed:');
|
|
54
|
+
console.error(error.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -32,6 +32,10 @@ import {
|
|
|
32
32
|
loadOnboardingReportIfExists,
|
|
33
33
|
} from '../compiler.mjs';
|
|
34
34
|
|
|
35
|
+
import { runPreflightChecks } from '../preflight.mjs';
|
|
36
|
+
import { createBackup } from '../backup.mjs';
|
|
37
|
+
import { performRollback } from '../rollback.mjs';
|
|
38
|
+
|
|
35
39
|
export function parseUpgradeArguments(commandArguments) {
|
|
36
40
|
const parsedUpgradeOptions = {
|
|
37
41
|
targetDirectory: '.',
|
|
@@ -68,6 +72,13 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
68
72
|
const setupStartedAt = Date.now();
|
|
69
73
|
await ensureDirectory(resolvedTargetDirectoryPath);
|
|
70
74
|
|
|
75
|
+
const preflightResult = await runPreflightChecks(resolvedTargetDirectoryPath, 'upgrade');
|
|
76
|
+
if (!preflightResult.passed) {
|
|
77
|
+
console.error('\n[FATAL] Preflight checks failed. Upgrading here would cause errors or data loss:');
|
|
78
|
+
console.error(JSON.stringify(preflightResult, null, 2));
|
|
79
|
+
throw new Error('Preflight checks failed.');
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
const userInterface = createInterface({ input: stdin, output: stdout });
|
|
72
83
|
|
|
73
84
|
try {
|
|
@@ -138,27 +149,44 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
138
149
|
return;
|
|
139
150
|
}
|
|
140
151
|
|
|
141
|
-
await
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
152
|
+
const backup = await createBackup(resolvedTargetDirectoryPath);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await fs.writeFile(currentRulesPath, plannedRulesContent, 'utf8');
|
|
156
|
+
await fs.writeFile(path.join(resolvedTargetDirectoryPath, '.windsurfrules'), plannedRulesContent, 'utf8');
|
|
157
|
+
await writeSelectedPolicy(resolvedTargetDirectoryPath, selectedProfileName);
|
|
158
|
+
|
|
159
|
+
const setupDurationMs = Date.now() - setupStartedAt;
|
|
160
|
+
await writeOnboardingReport({
|
|
161
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
162
|
+
selectedProfileName,
|
|
163
|
+
selectedProfilePack: existingOnboardingReport?.selectedProfilePack || null,
|
|
164
|
+
selectedStackFileName,
|
|
165
|
+
selectedBlueprintFileName,
|
|
166
|
+
includeCiGuardrails,
|
|
167
|
+
setupDurationMs,
|
|
168
|
+
projectDetection,
|
|
169
|
+
operationMode: 'upgrade',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log('\nUpgrade complete.');
|
|
173
|
+
console.log(`- Rules rewritten: ${isRulesContentChanged ? 'yes' : 'no (metadata refreshed)'}`);
|
|
174
|
+
console.log(`- Setup time: ${formatDuration(setupDurationMs)}`);
|
|
175
|
+
console.log('- Updated files: .cursorrules, .windsurfrules, .agent-context/state/onboarding-report.json');
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('\n[FATAL] An error occurred during upgrade. Attempting automatic rollback...');
|
|
178
|
+
try {
|
|
179
|
+
const rollbackReport = await performRollback(resolvedTargetDirectoryPath);
|
|
180
|
+
if (rollbackReport.success) {
|
|
181
|
+
console.error('[OK] Automatic rollback successful. The directory has been restored.');
|
|
182
|
+
} else {
|
|
183
|
+
console.error('[WARN] Automatic rollback completed with errors. You may need to manually clean up.');
|
|
184
|
+
}
|
|
185
|
+
} catch (rbError) {
|
|
186
|
+
console.error(`[FATAL] Automatic rollback failed: ${rbError.message}`);
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
162
190
|
} finally {
|
|
163
191
|
userInterface.close();
|
|
164
192
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { pathExists } from './utils.mjs';
|
|
5
|
+
|
|
6
|
+
const MINIMUM_NODE_VERSION = 18;
|
|
7
|
+
const MINIMUM_FREE_DISK_SPACE_MB = 5;
|
|
8
|
+
|
|
9
|
+
export async function runPreflightChecks(targetDirectoryPath, operationMode) {
|
|
10
|
+
const result = {
|
|
11
|
+
passed: true,
|
|
12
|
+
checks: [],
|
|
13
|
+
errors: [],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Check 1: Node Version
|
|
17
|
+
const nodeVersionString = process.version;
|
|
18
|
+
const majorVersion = parseInt(nodeVersionString.slice(1).split('.')[0], 10);
|
|
19
|
+
if (majorVersion >= MINIMUM_NODE_VERSION) {
|
|
20
|
+
result.checks.push(`Node.js version is compatible: ${nodeVersionString}`);
|
|
21
|
+
} else {
|
|
22
|
+
result.passed = false;
|
|
23
|
+
result.errors.push(`Node.js version ${nodeVersionString} is unsupported. Minimum required is v${MINIMUM_NODE_VERSION}.x`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check 2: Write Permissions
|
|
27
|
+
try {
|
|
28
|
+
await fs.access(targetDirectoryPath, constants.W_OK);
|
|
29
|
+
result.checks.push(`Target directory is writable: ${targetDirectoryPath}`);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.code === 'ENOENT') {
|
|
32
|
+
// Target directory does not exist yet. Ensure its parent is writable so we can create it
|
|
33
|
+
const parentDir = path.dirname(targetDirectoryPath);
|
|
34
|
+
try {
|
|
35
|
+
await fs.access(parentDir, constants.W_OK);
|
|
36
|
+
result.checks.push(`Target parent directory is writable: ${parentDir}`);
|
|
37
|
+
} catch (parentErr) {
|
|
38
|
+
result.passed = false;
|
|
39
|
+
result.errors.push(`Target directory does not exist and parent is read-only: ${parentDir}`);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
result.passed = false;
|
|
43
|
+
result.errors.push(`Target directory is read-only: ${targetDirectoryPath}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check 3: Disk Space
|
|
48
|
+
try {
|
|
49
|
+
const statfsString = typeof fs.statfs === 'function';
|
|
50
|
+
// Use fs.statfs if available (Node 18.15+)
|
|
51
|
+
if (statfsString) {
|
|
52
|
+
// If directory doesn't exist, check its parent
|
|
53
|
+
const pathToCheck = await pathExists(targetDirectoryPath) ? targetDirectoryPath : path.dirname(targetDirectoryPath);
|
|
54
|
+
const diskStats = await fs.statfs(pathToCheck);
|
|
55
|
+
// diskStats.bavail is free blocks available to unprivileged user
|
|
56
|
+
// diskStats.bsize is block size
|
|
57
|
+
const freeBytes = Number(diskStats.bavail) * Number(diskStats.bsize);
|
|
58
|
+
const freeMb = freeBytes / (1024 * 1024);
|
|
59
|
+
|
|
60
|
+
if (freeMb >= MINIMUM_FREE_DISK_SPACE_MB) {
|
|
61
|
+
result.checks.push(`Sufficient free disk space available: ${freeMb.toFixed(2)} MB`);
|
|
62
|
+
} else {
|
|
63
|
+
result.passed = false;
|
|
64
|
+
result.errors.push(`Insufficient free disk space. Required: ${MINIMUM_FREE_DISK_SPACE_MB} MB, Available: ${freeMb.toFixed(2)} MB`);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
result.checks.push('Skipping disk space check (fs.statfs requires Node.js v18.15+)');
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
result.checks.push(`Skipping disk space check due to standard library limitation: ${error.message}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check 4: Conflicting Files
|
|
74
|
+
if (operationMode === 'init') {
|
|
75
|
+
const potentiallyConflictingPaths = [
|
|
76
|
+
path.join(targetDirectoryPath, '.cursorrules'),
|
|
77
|
+
path.join(targetDirectoryPath, '.windsurfrules'),
|
|
78
|
+
path.join(targetDirectoryPath, '.agent-context'),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const conflictingFound = [];
|
|
82
|
+
for (const conflictPath of potentiallyConflictingPaths) {
|
|
83
|
+
if (await pathExists(conflictPath)) {
|
|
84
|
+
conflictingFound.push(path.basename(conflictPath));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (conflictingFound.length > 0) {
|
|
89
|
+
result.passed = false;
|
|
90
|
+
result.errors.push(`Conflicting governance files already exist during init: ${conflictingFound.join(', ')}. Use upgrade instead.`);
|
|
91
|
+
} else {
|
|
92
|
+
result.checks.push('No conflicting governance files found.');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathExists, ensureDirectory } from './utils.mjs';
|
|
4
|
+
|
|
5
|
+
const BACKUP_DIR_NAME = '.agentic-backup';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Performs a rollback of a target directory using the backup manifest.
|
|
9
|
+
* @param {string} targetDirectoryPath
|
|
10
|
+
*/
|
|
11
|
+
export async function performRollback(targetDirectoryPath) {
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
const backupRoot = path.join(targetDirectoryPath, BACKUP_DIR_NAME);
|
|
14
|
+
const manifestPath = path.join(backupRoot, 'manifest.json');
|
|
15
|
+
const objectsDir = path.join(backupRoot, 'objects');
|
|
16
|
+
|
|
17
|
+
if (!(await pathExists(manifestPath))) {
|
|
18
|
+
throw new Error(`Rollback failed: Backup manifest not found at ${manifestPath}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const manifestData = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
22
|
+
const report = {
|
|
23
|
+
restoredFiles: [],
|
|
24
|
+
deletedFiles: [],
|
|
25
|
+
errors: []
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
for (const [relativePath, fileManifest] of Object.entries(manifestData.files)) {
|
|
29
|
+
const destinationPath = path.join(targetDirectoryPath, relativePath);
|
|
30
|
+
|
|
31
|
+
if (fileManifest.action === 'restore') {
|
|
32
|
+
const sourceObjectPath = path.join(objectsDir, fileManifest.hash);
|
|
33
|
+
if (!(await pathExists(sourceObjectPath))) {
|
|
34
|
+
report.errors.push(`Missing backup object for file: ${relativePath} (Expected hash: ${fileManifest.hash})`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await ensureDirectory(path.dirname(destinationPath));
|
|
40
|
+
await fs.copyFile(sourceObjectPath, destinationPath);
|
|
41
|
+
report.restoredFiles.push(relativePath);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
report.errors.push(`Failed to restore ${relativePath}: ${err.message}`);
|
|
44
|
+
}
|
|
45
|
+
} else if (fileManifest.action === 'delete') {
|
|
46
|
+
// File did not exist before, but might exist now
|
|
47
|
+
if (await pathExists(destinationPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const stats = await fs.stat(destinationPath);
|
|
50
|
+
if (stats.isDirectory()) {
|
|
51
|
+
await fs.rm(destinationPath, { recursive: true, force: true });
|
|
52
|
+
} else {
|
|
53
|
+
await fs.unlink(destinationPath);
|
|
54
|
+
}
|
|
55
|
+
report.deletedFiles.push(relativePath);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
report.errors.push(`Failed to delete dynamically added file ${relativePath}: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
report.durationMs = Date.now() - startTime;
|
|
64
|
+
report.success = report.errors.length === 0;
|
|
65
|
+
|
|
66
|
+
return report;
|
|
67
|
+
}
|
package/lib/cli/utils.mjs
CHANGED
|
@@ -27,6 +27,7 @@ export function printUsage() {
|
|
|
27
27
|
console.log(' agentic-senior-core launch');
|
|
28
28
|
console.log(' agentic-senior-core init [target-directory] [--preset <name>] [--profile <beginner|balanced|strict>] [--profile-pack <name>] [--stack <name>] [--blueprint <name>] [--ci <true|false>] [--newbie]');
|
|
29
29
|
console.log(' agentic-senior-core upgrade [target-directory] [--dry-run] [--yes]');
|
|
30
|
+
console.log(' agentic-senior-core rollback [target-directory]');
|
|
30
31
|
console.log(' agentic-senior-core skill [domain] [--tier <standard|advance|expert|above>] [--json]');
|
|
31
32
|
console.log(' agentic-senior-core --version');
|
|
32
33
|
console.log('');
|
package/package.json
CHANGED
package/scripts/validate.mjs
CHANGED
|
@@ -137,6 +137,7 @@ async function validateRequiredFiles() {
|
|
|
137
137
|
'tests/enterprise-ops.test.mjs',
|
|
138
138
|
'LICENSE',
|
|
139
139
|
'.gitignore',
|
|
140
|
+
'.agent-context/marketplace/trust-tiers.json',
|
|
140
141
|
];
|
|
141
142
|
|
|
142
143
|
for (const requiredFilePath of requiredFiles) {
|
|
@@ -218,6 +219,7 @@ async function validateRuleFiles() {
|
|
|
218
219
|
'review-checklists/security-audit.md',
|
|
219
220
|
'review-checklists/performance-audit.md',
|
|
220
221
|
'review-checklists/architecture-review.md',
|
|
222
|
+
'review-checklists/marketplace-acceptance.md',
|
|
221
223
|
'skills/README.md',
|
|
222
224
|
'skills/frontend/README.md',
|
|
223
225
|
'skills/backend/README.md',
|
|
@@ -596,6 +598,65 @@ async function validateMcpConfiguration() {
|
|
|
596
598
|
}
|
|
597
599
|
}
|
|
598
600
|
|
|
601
|
+
async function validateTrustTierSchema() {
|
|
602
|
+
console.log('\nChecking marketplace trust tier schema...');
|
|
603
|
+
|
|
604
|
+
const trustTierPath = join(AGENT_CONTEXT_DIR, 'marketplace', 'trust-tiers.json');
|
|
605
|
+
const trustTierContent = await readTextFile(trustTierPath);
|
|
606
|
+
const trustTierSchema = JSON.parse(trustTierContent);
|
|
607
|
+
|
|
608
|
+
const expectedTierNames = ['verified', 'community', 'experimental'];
|
|
609
|
+
for (const expectedTierName of expectedTierNames) {
|
|
610
|
+
if (trustTierSchema.tiers?.[expectedTierName]) {
|
|
611
|
+
pass(`Trust tier "${expectedTierName}" is defined`);
|
|
612
|
+
} else {
|
|
613
|
+
fail(`Trust tier "${expectedTierName}" is missing from trust-tiers.json`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const scorecardDimensions = trustTierSchema.scorecard?.dimensions;
|
|
618
|
+
if (!scorecardDimensions || typeof scorecardDimensions !== 'object') {
|
|
619
|
+
fail('Trust tier scorecard must define dimensions');
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const dimensionNames = Object.keys(scorecardDimensions);
|
|
624
|
+
let totalWeight = 0;
|
|
625
|
+
|
|
626
|
+
for (const dimensionName of dimensionNames) {
|
|
627
|
+
const dimensionWeight = scorecardDimensions[dimensionName].weight;
|
|
628
|
+
if (typeof dimensionWeight !== 'number' || dimensionWeight <= 0) {
|
|
629
|
+
fail(`Scorecard dimension "${dimensionName}" must have a positive weight`);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
totalWeight += dimensionWeight;
|
|
633
|
+
pass(`Scorecard dimension "${dimensionName}" weight: ${dimensionWeight}`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (totalWeight === 100) {
|
|
637
|
+
pass(`Scorecard weights sum to 100`);
|
|
638
|
+
} else {
|
|
639
|
+
fail(`Scorecard weights must sum to 100 (got ${totalWeight})`);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
for (const dimensionName of dimensionNames) {
|
|
643
|
+
const gates = scorecardDimensions[dimensionName].gates;
|
|
644
|
+
if (!Array.isArray(gates) || gates.length === 0) {
|
|
645
|
+
fail(`Scorecard dimension "${dimensionName}" must define at least one gate`);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
pass(`Scorecard dimension "${dimensionName}" has ${gates.length} gates`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
for (const [tierName, tierDefinition] of Object.entries(trustTierSchema.tiers)) {
|
|
652
|
+
if (typeof tierDefinition.minimumScore !== 'number') {
|
|
653
|
+
fail(`Tier "${tierName}" must define a numeric minimumScore`);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
pass(`Tier "${tierName}" minimumScore: ${tierDefinition.minimumScore}`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
599
660
|
async function main() {
|
|
600
661
|
console.log('===============================================');
|
|
601
662
|
console.log(' Agentic-Senior-Core Repository Validator');
|
|
@@ -613,6 +674,7 @@ async function main() {
|
|
|
613
674
|
await validateVersionConsistency();
|
|
614
675
|
await validateDocumentationFlow();
|
|
615
676
|
await validateMcpConfiguration();
|
|
677
|
+
await validateTrustTierSchema();
|
|
616
678
|
|
|
617
679
|
console.log('\n===============================================');
|
|
618
680
|
console.log(' RESULTS');
|