@ryuenn3123/agentic-senior-core 1.9.2 → 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.
@@ -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`.
@@ -236,10 +236,11 @@ coverage/
236
236
  .nyc_output/
237
237
  *.lcov
238
238
 
239
- # ── Runtime Data ──
239
+ # ── Runtime & Backup Data ──
240
240
  *.pid
241
241
  *.seed
242
242
  *.pid.lock
243
+ .agentic-backup/
243
244
 
244
245
  # ── Secrets & Keys ──
245
246
  *.pem
@@ -1,19 +1,14 @@
1
1
  {
2
- "cliVersion": "1.9.2",
3
- "generatedAt": "2026-04-08T01:58:51.014Z",
4
- "operationMode": "init",
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": 16396,
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.2
4
- Timestamp: 2026-04-08T01:58:51.011Z
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.2
4
- Timestamp: 2026-04-08T01:58:51.011Z
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 fs.writeFile(currentRulesPath, plannedRulesContent, 'utf8');
142
- await fs.writeFile(path.join(resolvedTargetDirectoryPath, '.windsurfrules'), plannedRulesContent, 'utf8');
143
- await writeSelectedPolicy(resolvedTargetDirectoryPath, selectedProfileName);
144
-
145
- const setupDurationMs = Date.now() - setupStartedAt;
146
- await writeOnboardingReport({
147
- targetDirectoryPath: resolvedTargetDirectoryPath,
148
- selectedProfileName,
149
- selectedProfilePack: existingOnboardingReport?.selectedProfilePack || null,
150
- selectedStackFileName,
151
- selectedBlueprintFileName,
152
- includeCiGuardrails,
153
- setupDurationMs,
154
- projectDetection,
155
- operationMode: 'upgrade',
156
- });
157
-
158
- console.log('\nUpgrade complete.');
159
- console.log(`- Rules rewritten: ${isRulesContentChanged ? 'yes' : 'no (metadata refreshed)'}`);
160
- console.log(`- Setup time: ${formatDuration(setupDurationMs)}`);
161
- console.log('- Updated files: .cursorrules, .windsurfrules, .agent-context/state/onboarding-report.json');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuenn3123/agentic-senior-core",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "type": "module",
5
5
  "description": "Force your AI Agent to code like a Staff Engineer, not a Junior.",
6
6
  "bin": {
@@ -51,4 +51,4 @@
51
51
  "validate": "node ./scripts/validate.mjs",
52
52
  "test": "node --test ./tests/cli-smoke.test.mjs ./tests/llm-judge.test.mjs ./tests/enterprise-ops.test.mjs ./tests/skill-tier-gate.test.mjs"
53
53
  }
54
- }
54
+ }
@@ -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');