Missed by Phase B (d600f6ed) which swept src/ headers but not scripts/ headers.
All 3 follow the Phase B precedent pattern:
- scripts/check-attack-surface.js (the inst_084 validator hook itself)
- scripts/sync-prod-audit-logs.js
- scripts/migrate-to-schema-v3.js
Two header formats encountered:
- Standard Apache 2.0 JS block header (first two files): full block swap to
EUPL-1.2 equivalent with Licence/British spelling and EC canonical URL.
- Brief JSDoc-style reference (migrate-to-schema-v3.js): short-form swap
with Licence reference + URL line.
Other scripts/ files with Apache text references NOT in scope here:
- scripts/relicense-apache-to-eupl.js (DATA: Apache patterns are search
targets for the relicense tool itself)
- scripts/fix-markdown-licences.js (DATA: Apache regex patterns for a
migration script's find-and-replace)
- scripts/migrate-licence-to-cc-by-4.js (DATA: Apache source patterns
for a different migration workflow)
- scripts/upload-document.js (DATA: Apache-2.0 is a valid SPDX tag for
uploadable documents; retained as valid metadata option)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
204 lines
7.8 KiB
JavaScript
204 lines
7.8 KiB
JavaScript
/**
|
|
* Migrate Tractatus instruction-history.json from Schema v1.0 to v3.0
|
|
*
|
|
* Schema v3.0 combines best of v1.0 and v2.0:
|
|
* - Preserves v1.0: explicitness, temporal_scope, verification_required
|
|
* - Adds v2.0/v3.0: category, title, description, context, rationale, etc.
|
|
* - Adds v3.0: securityClassification
|
|
*
|
|
* Copyright (c) 2025 John G Stroh. All rights reserved.
|
|
* Licensed under the European Union Public Licence, Version 1.2 (EUPL-1.2)
|
|
* https://interoperable-europe.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const instructionPath = path.join(__dirname, '..', '.claude', 'instruction-history.json');
|
|
const backupPath = path.join(__dirname, '..', '.claude', 'instruction-history.json.v1.0.backup');
|
|
|
|
// Category mapping heuristics
|
|
function inferCategory(instruction) {
|
|
const text = instruction.text.toLowerCase();
|
|
const quadrant = instruction.quadrant || '';
|
|
|
|
if (text.includes('security') || text.includes('credential') || text.includes('vault')) return 'SECURITY';
|
|
if (text.includes('privacy') || text.includes('consent') || text.includes('gdpr')) return 'PRIVACY';
|
|
if (text.includes('multi-tenant') || text.includes('tenant')) return 'MULTI_TENANCY';
|
|
if (text.includes('deploy') || text.includes('production') || text.includes('release')) return 'DEPLOYMENT';
|
|
if (text.includes('git') || text.includes('commit') || text.includes('branch')) return 'GIT_VERSION_CONTROL';
|
|
if (text.includes('test') || text.includes('coverage') || text.includes('unittest')) return 'TESTING';
|
|
if (text.includes('document') || text.includes('readme') || text.includes('comment')) return 'DOCUMENTATION';
|
|
if (text.includes('framework') || text.includes('governance') || text.includes('instruction')) return 'FRAMEWORK_OPERATION';
|
|
if (text.includes('architecture') || text.includes('pattern') || text.includes('design')) return 'ARCHITECTURE';
|
|
if (text.includes('value') || text.includes('principle') || text.includes('ethics')) return 'VALUES_ALIGNMENT';
|
|
|
|
// Default based on quadrant
|
|
if (quadrant === 'STRATEGIC') return 'VALUES_ALIGNMENT';
|
|
if (quadrant === 'TACTICAL') return 'ARCHITECTURE';
|
|
if (quadrant === 'OPERATIONAL') return 'DEPLOYMENT';
|
|
|
|
return 'FRAMEWORK_OPERATION';
|
|
}
|
|
|
|
// Security classification heuristics
|
|
function inferSecurityClassification(instruction) {
|
|
const text = instruction.text.toLowerCase();
|
|
|
|
if (text.includes('credential') || text.includes('vault') || text.includes('secret') || text.includes('password')) {
|
|
return 'CONFIDENTIAL';
|
|
}
|
|
if (text.includes('security') || text.includes('authentication') || text.includes('authorization')) {
|
|
return 'INTERNAL';
|
|
}
|
|
if (text.includes('emergency') || text.includes('override') || text.includes('master key')) {
|
|
return 'RESTRICTED';
|
|
}
|
|
|
|
// Default to INTERNAL for framework docs
|
|
return 'INTERNAL';
|
|
}
|
|
|
|
// Generate title from text (first sentence, max 80 chars)
|
|
function generateTitle(text) {
|
|
let title = text.split(/[.!?]/)[0].trim();
|
|
if (title.length > 80) {
|
|
title = title.substring(0, 77) + '...';
|
|
}
|
|
return title;
|
|
}
|
|
|
|
// Migrate single instruction
|
|
function migrateInstruction(oldInstruction) {
|
|
const newInstruction = {
|
|
// v3.0 required fields
|
|
id: oldInstruction.id,
|
|
title: generateTitle(oldInstruction.text),
|
|
category: inferCategory(oldInstruction),
|
|
quadrant: oldInstruction.quadrant || 'OPERATIONAL',
|
|
persistence: oldInstruction.persistence || 'MEDIUM',
|
|
|
|
// v3.0 field mappings
|
|
description: oldInstruction.text, // v1.0 "text" becomes "description"
|
|
context: `Migrated from v1.0. Original timestamp: ${oldInstruction.timestamp}`,
|
|
rationale: oldInstruction.notes || 'Framework governance requirement',
|
|
trigger: 'As defined in original instruction',
|
|
action: oldInstruction.text, // Action is same as description for v1.0 rules
|
|
validation: oldInstruction.verification_required === 'MANDATORY' ?
|
|
'Verification required before proceeding' : 'Best-effort validation',
|
|
evidence: 'Session handoff logs, audit trails',
|
|
|
|
// v1.0 fields preserved in v3.0
|
|
explicitness: oldInstruction.explicitness || 0.7,
|
|
temporal_scope: oldInstruction.temporal_scope || 'PROJECT',
|
|
verification_required: oldInstruction.verification_required || 'PERIODIC',
|
|
|
|
// v3.0 new field
|
|
securityClassification: inferSecurityClassification(oldInstruction),
|
|
|
|
// v3.0 source field
|
|
source: oldInstruction.source === 'user' ? 'FRAMEWORK' :
|
|
oldInstruction.source === 'ai' ? 'PROJECT' : 'FRAMEWORK',
|
|
|
|
// Additional fields
|
|
parameters: oldInstruction.parameters || {},
|
|
relatedInstructions: [],
|
|
active: oldInstruction.active !== false,
|
|
|
|
// Metadata
|
|
metadata: {
|
|
created: oldInstruction.timestamp || '2025-10-06',
|
|
author: 'Tractatus Framework',
|
|
session_id: oldInstruction.session_id || 'migration-v1-to-v3',
|
|
original_schema: 'v1.0',
|
|
migrated: '2025-11-02',
|
|
migration_notes: oldInstruction.notes || ''
|
|
}
|
|
};
|
|
|
|
return newInstruction;
|
|
}
|
|
|
|
try {
|
|
console.log('🔄 Starting migration from Schema v1.0 to v3.0...\n');
|
|
|
|
// Read current file
|
|
const data = JSON.parse(fs.readFileSync(instructionPath, 'utf-8'));
|
|
console.log(`📊 Found ${data.instructions.length} instructions to migrate`);
|
|
|
|
// Create backup
|
|
fs.writeFileSync(backupPath, JSON.stringify(data, null, 2));
|
|
console.log(`💾 Backup created at: ${backupPath}\n`);
|
|
|
|
// Migrate all instructions
|
|
const migratedInstructions = data.instructions.map(migrateInstruction);
|
|
|
|
// Create new metadata
|
|
const newMetadata = {
|
|
version: '3.0.0',
|
|
project: 'tractatus',
|
|
description: 'Tractatus Framework - Governance instruction database',
|
|
created: '2025-10-06',
|
|
lastUpdated: '2025-11-02',
|
|
totalInstructions: migratedInstructions.length,
|
|
activeInstructions: migratedInstructions.filter(i => i.active).length,
|
|
schemaVersion: 'v3.0',
|
|
previousVersion: 'v1.0 (backed up)',
|
|
migration: {
|
|
date: '2025-11-02',
|
|
from: 'v1.0',
|
|
to: 'v3.0',
|
|
notes: 'Unified schema combining v1.0 and v2.0 features'
|
|
}
|
|
};
|
|
|
|
// Create new structure
|
|
const newData = {
|
|
metadata: newMetadata,
|
|
instructions: migratedInstructions
|
|
};
|
|
|
|
// Write migrated file
|
|
fs.writeFileSync(instructionPath, JSON.stringify(newData, null, 2));
|
|
|
|
console.log('✅ Migration complete!\n');
|
|
console.log('📈 Statistics:');
|
|
console.log(` Total instructions: ${newMetadata.totalInstructions}`);
|
|
console.log(` Active instructions: ${newMetadata.activeInstructions}`);
|
|
console.log(` Inactive instructions: ${newMetadata.totalInstructions - newMetadata.activeInstructions}`);
|
|
console.log(` Schema version: ${newMetadata.schemaVersion}`);
|
|
|
|
// Category breakdown
|
|
const categories = {};
|
|
migratedInstructions.forEach(i => {
|
|
categories[i.category] = (categories[i.category] || 0) + 1;
|
|
});
|
|
|
|
console.log('\n📊 By Category:');
|
|
Object.entries(categories).sort(([,a], [,b]) => b - a).forEach(([cat, count]) => {
|
|
console.log(` ${cat}: ${count}`);
|
|
});
|
|
|
|
// Security classification breakdown
|
|
const classifications = {};
|
|
migratedInstructions.forEach(i => {
|
|
classifications[i.securityClassification] = (classifications[i.securityClassification] || 0) + 1;
|
|
});
|
|
|
|
console.log('\n🔒 By Security Classification:');
|
|
Object.entries(classifications).sort(([,a], [,b]) => b - a).forEach(([cls, count]) => {
|
|
console.log(` ${cls}: ${count}`);
|
|
});
|
|
|
|
console.log('\n💡 Next steps:');
|
|
console.log(' 1. Review migrated instructions in .claude/instruction-history.json');
|
|
console.log(' 2. Verify categories and security classifications');
|
|
console.log(' 3. Update any incorrect inferred values');
|
|
console.log(' 4. Original v1.0 backed up at: ' + backupPath);
|
|
console.log(' 5. To restore backup: mv ' + backupPath + ' ' + instructionPath);
|
|
|
|
} catch (error) {
|
|
console.error('❌ Migration failed:', error.message);
|
|
console.error('\nStack trace:', error.stack);
|
|
process.exit(1);
|
|
}
|