telos-framework 0.1.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.
@@ -0,0 +1,142 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+
4
+ const CONFIG_TEMPLATES = {
5
+ eslint: {
6
+ filename: '.eslintrc.json',
7
+ content: {
8
+ env: {
9
+ browser: true,
10
+ es2021: true,
11
+ node: true
12
+ },
13
+ extends: ['eslint:recommended'],
14
+ parserOptions: {
15
+ ecmaVersion: 'latest',
16
+ sourceType: 'module'
17
+ },
18
+ rules: {}
19
+ }
20
+ },
21
+
22
+ prettier: {
23
+ filename: '.prettierrc.json',
24
+ content: {
25
+ semi: true,
26
+ trailingComma: 'es5',
27
+ singleQuote: true,
28
+ printWidth: 100,
29
+ tabWidth: 2
30
+ }
31
+ },
32
+
33
+ vitest: {
34
+ filename: 'vitest.config.js',
35
+ content: `import { defineConfig } from 'vitest/config';
36
+
37
+ export default defineConfig({
38
+ test: {
39
+ globals: true,
40
+ environment: 'node',
41
+ coverage: {
42
+ provider: 'v8',
43
+ reporter: ['text', 'json', 'html']
44
+ }
45
+ }
46
+ });
47
+ `
48
+ },
49
+
50
+ playwright: {
51
+ filename: 'playwright.config.js',
52
+ content: `import { defineConfig } from '@playwright/test';
53
+
54
+ export default defineConfig({
55
+ testDir: './tests/e2e',
56
+ fullyParallel: true,
57
+ forbidOnly: !!process.env.CI,
58
+ retries: process.env.CI ? 2 : 0,
59
+ workers: process.env.CI ? 1 : undefined,
60
+ reporter: 'html',
61
+ use: {
62
+ trace: 'on-first-retry',
63
+ },
64
+ projects: [
65
+ {
66
+ name: 'chromium',
67
+ use: { browserName: 'chromium' },
68
+ },
69
+ ],
70
+ });
71
+ `
72
+ }
73
+ };
74
+
75
+ async function writeToolConfig(tool, projectRoot, options = {}) {
76
+ const template = CONFIG_TEMPLATES[tool.toLowerCase()];
77
+
78
+ if (!template) {
79
+ throw new Error(`No configuration template for tool: ${tool}`);
80
+ }
81
+
82
+ const configPath = path.join(projectRoot, template.filename);
83
+
84
+ try {
85
+ await fs.access(configPath);
86
+ if (!options.overwrite) {
87
+ return {
88
+ written: false,
89
+ path: configPath,
90
+ reason: 'Config already exists'
91
+ };
92
+ }
93
+ } catch (error) {
94
+ }
95
+
96
+ const content = typeof template.content === 'string'
97
+ ? template.content
98
+ : JSON.stringify(template.content, null, 2);
99
+
100
+ await fs.writeFile(configPath, content, 'utf8');
101
+
102
+ return {
103
+ written: true,
104
+ path: configPath,
105
+ tool
106
+ };
107
+ }
108
+
109
+ async function writeAllConfigs(tools, projectRoot, options = {}) {
110
+ const results = [];
111
+
112
+ for (const tool of tools) {
113
+ try {
114
+ const result = await writeToolConfig(tool, projectRoot, options);
115
+ results.push(result);
116
+ } catch (error) {
117
+ results.push({
118
+ written: false,
119
+ tool,
120
+ error: error.message
121
+ });
122
+ }
123
+ }
124
+
125
+ return results;
126
+ }
127
+
128
+ function getConfigTemplate(tool) {
129
+ return CONFIG_TEMPLATES[tool.toLowerCase()] || null;
130
+ }
131
+
132
+ function hasConfigTemplate(tool) {
133
+ return tool.toLowerCase() in CONFIG_TEMPLATES;
134
+ }
135
+
136
+ module.exports = {
137
+ writeToolConfig,
138
+ writeAllConfigs,
139
+ getConfigTemplate,
140
+ hasConfigTemplate,
141
+ CONFIG_TEMPLATES
142
+ };
@@ -0,0 +1,166 @@
1
+ const { exec } = require('child_process');
2
+ const { promisify } = require('util');
3
+ const execAsync = promisify(exec);
4
+
5
+ const TOOL_COMMANDS = {
6
+ eslint: {
7
+ lint: (files = '.') => `eslint ${files}`,
8
+ fix: (files = '.') => `eslint --fix ${files}`,
9
+ check: (file) => `eslint ${file}`
10
+ },
11
+
12
+ prettier: {
13
+ format: (files = '.') => `prettier --write ${files}`,
14
+ check: (files = '.') => `prettier --check ${files}`
15
+ },
16
+
17
+ vitest: {
18
+ run: (args = '') => `vitest run ${args}`,
19
+ watch: () => 'vitest',
20
+ coverage: () => 'vitest run --coverage'
21
+ },
22
+
23
+ jest: {
24
+ run: (args = '') => `jest ${args}`,
25
+ watch: () => 'jest --watch',
26
+ coverage: () => 'jest --coverage'
27
+ },
28
+
29
+ playwright: {
30
+ test: (args = '') => `playwright test ${args}`,
31
+ ui: () => 'playwright test --ui',
32
+ debug: (test) => `playwright test --debug ${test || ''}`
33
+ },
34
+
35
+ git: {
36
+ status: () => 'git status',
37
+ add: (files = '.') => `git add ${files}`,
38
+ commit: (message) => `git commit -m "${message}"`,
39
+ push: () => 'git push',
40
+ diff: () => 'git diff'
41
+ }
42
+ };
43
+
44
+ class ToolInvoker {
45
+ constructor(availableTools = []) {
46
+ this.availableTools = availableTools.map(t => t.name.toLowerCase());
47
+ }
48
+
49
+ hasTool(toolName) {
50
+ const name = toolName.toLowerCase();
51
+ return this.availableTools.includes(name) ||
52
+ this.availableTools.some(t => t.includes(name));
53
+ }
54
+
55
+ async invoke(tool, action, ...args) {
56
+ const toolName = tool.toLowerCase();
57
+
58
+ if (!this.hasTool(toolName)) {
59
+ throw new Error(`Tool ${tool} not available`);
60
+ }
61
+
62
+ const commands = TOOL_COMMANDS[toolName];
63
+ if (!commands || !commands[action]) {
64
+ throw new Error(`Action ${action} not supported for tool ${tool}`);
65
+ }
66
+
67
+ const command = commands[action](...args);
68
+
69
+ try {
70
+ const { stdout, stderr } = await execAsync(command);
71
+ return {
72
+ success: true,
73
+ tool,
74
+ action,
75
+ command,
76
+ stdout,
77
+ stderr
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ success: false,
82
+ tool,
83
+ action,
84
+ command,
85
+ error: error.message,
86
+ stdout: error.stdout,
87
+ stderr: error.stderr
88
+ };
89
+ }
90
+ }
91
+
92
+ async runLinter(files = '.') {
93
+ if (this.hasTool('eslint')) {
94
+ return await this.invoke('eslint', 'lint', files);
95
+ } else if (this.hasTool('prettier')) {
96
+ return await this.invoke('prettier', 'check', files);
97
+ } else {
98
+ return {
99
+ success: false,
100
+ tool: 'none',
101
+ action: 'lint',
102
+ error: 'No linter available'
103
+ };
104
+ }
105
+ }
106
+
107
+ async runTests(args = '') {
108
+ if (this.hasTool('vitest')) {
109
+ return await this.invoke('vitest', 'run', args);
110
+ } else if (this.hasTool('jest')) {
111
+ return await this.invoke('jest', 'run', args);
112
+ } else {
113
+ return {
114
+ success: false,
115
+ tool: 'none',
116
+ action: 'test',
117
+ error: 'No test framework available'
118
+ };
119
+ }
120
+ }
121
+
122
+ async runE2ETests(args = '') {
123
+ if (this.hasTool('playwright')) {
124
+ return await this.invoke('playwright', 'test', args);
125
+ } else {
126
+ return {
127
+ success: false,
128
+ tool: 'none',
129
+ action: 'e2e',
130
+ error: 'No E2E framework available'
131
+ };
132
+ }
133
+ }
134
+
135
+ async format(files = '.') {
136
+ if (this.hasTool('prettier')) {
137
+ return await this.invoke('prettier', 'format', files);
138
+ } else if (this.hasTool('eslint')) {
139
+ return await this.invoke('eslint', 'fix', files);
140
+ } else {
141
+ return {
142
+ success: false,
143
+ tool: 'none',
144
+ action: 'format',
145
+ error: 'No formatter available'
146
+ };
147
+ }
148
+ }
149
+
150
+ getAvailableActions(tool) {
151
+ const toolName = tool.toLowerCase();
152
+ const commands = TOOL_COMMANDS[toolName];
153
+
154
+ if (!commands) {
155
+ return [];
156
+ }
157
+
158
+ return Object.keys(commands);
159
+ }
160
+
161
+ getSupportedTools() {
162
+ return Object.keys(TOOL_COMMANDS);
163
+ }
164
+ }
165
+
166
+ module.exports = { ToolInvoker, TOOL_COMMANDS };
@@ -0,0 +1,74 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+
4
+ async function detectPlatforms(projectRoot) {
5
+ const platforms = [];
6
+
7
+ if (await fileExists(path.join(projectRoot, 'CLAUDE.md'))) {
8
+ platforms.push({ name: 'claude', configFile: 'CLAUDE.md' });
9
+ }
10
+
11
+ if (await fileExists(path.join(projectRoot, '.cursor'))) {
12
+ platforms.push({ name: 'cursor', configFile: '.cursor/rules/agents.md' });
13
+ }
14
+
15
+ if (await fileExists(path.join(projectRoot, '.github', 'copilot-instructions.md'))) {
16
+ platforms.push({ name: 'copilot', configFile: '.github/copilot-instructions.md' });
17
+ }
18
+
19
+ if (await fileExists(path.join(projectRoot, '.gemini'))) {
20
+ platforms.push({ name: 'gemini', configFile: '.gemini/instructions.md' });
21
+ }
22
+
23
+ if (platforms.length === 0) {
24
+ platforms.push({ name: 'claude', configFile: 'CLAUDE.md' });
25
+ }
26
+
27
+ return platforms;
28
+ }
29
+
30
+ async function fileExists(filePath) {
31
+ try {
32
+ await fs.access(filePath);
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ function getPlatformConfig(platformName) {
40
+ const configs = {
41
+ claude: {
42
+ name: 'Claude',
43
+ targets: [
44
+ { source: 'telos/content/AGENTS.md', target: 'CLAUDE.md' },
45
+ { source: 'telos/content/TELOS.md', target: 'TELOS.md' },
46
+ { source: 'telos/content/LOGOS.md', target: 'LOGOS.md' }
47
+ ]
48
+ },
49
+ cursor: {
50
+ name: 'Cursor',
51
+ targets: [
52
+ { source: 'telos/content/AGENTS.md', target: '.cursor/rules/agents.md' },
53
+ { source: 'telos/content/TELOS.md', target: '.cursor/rules/telos.md' },
54
+ { source: 'telos/content/LOGOS.md', target: '.cursor/rules/logos.md' }
55
+ ]
56
+ },
57
+ copilot: {
58
+ name: 'GitHub Copilot',
59
+ targets: [
60
+ { source: 'telos/content/AGENTS.md', target: '.github/copilot-instructions.md' }
61
+ ]
62
+ },
63
+ gemini: {
64
+ name: 'Gemini',
65
+ targets: [
66
+ { source: 'telos/content/AGENTS.md', target: '.gemini/instructions.md' }
67
+ ]
68
+ }
69
+ };
70
+
71
+ return configs[platformName] || configs.claude;
72
+ }
73
+
74
+ module.exports = { detectPlatforms, getPlatformConfig };
@@ -0,0 +1,103 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ async function createSymlinks(projectRoot, platformConfig) {
6
+ const results = [];
7
+ const isWindows = os.platform() === 'win32';
8
+
9
+ for (const target of platformConfig.targets) {
10
+ const sourcePath = path.join(projectRoot, target.source);
11
+ const targetPath = path.join(projectRoot, target.target);
12
+
13
+ try {
14
+ await ensureDirectoryExists(path.dirname(targetPath));
15
+
16
+ await removeIfExists(targetPath);
17
+
18
+ if (isWindows) {
19
+ await createWindowsSymlink(sourcePath, targetPath);
20
+ } else {
21
+ await createUnixSymlink(sourcePath, targetPath, projectRoot);
22
+ }
23
+
24
+ results.push({
25
+ source: target.source,
26
+ target: target.target,
27
+ success: true,
28
+ type: isWindows ? 'junction' : 'symlink'
29
+ });
30
+ } catch (error) {
31
+ results.push({
32
+ source: target.source,
33
+ target: target.target,
34
+ success: false,
35
+ error: error.message
36
+ });
37
+ }
38
+ }
39
+
40
+ return results;
41
+ }
42
+
43
+ async function ensureDirectoryExists(dirPath) {
44
+ await fs.mkdir(dirPath, { recursive: true });
45
+ }
46
+
47
+ async function removeIfExists(filePath) {
48
+ try {
49
+ const stats = await fs.lstat(filePath);
50
+ if (stats.isSymbolicLink() || stats.isFile()) {
51
+ await fs.unlink(filePath);
52
+ } else if (stats.isDirectory()) {
53
+ await fs.rmdir(filePath, { recursive: true });
54
+ }
55
+ } catch (error) {
56
+ }
57
+ }
58
+
59
+ async function createUnixSymlink(sourcePath, targetPath, projectRoot) {
60
+ const targetDir = path.dirname(targetPath);
61
+ const relativeSource = path.relative(targetDir, sourcePath);
62
+
63
+ await fs.symlink(relativeSource, targetPath, 'file');
64
+ }
65
+
66
+ async function createWindowsSymlink(sourcePath, targetPath) {
67
+ const { execSync } = require('child_process');
68
+
69
+ try {
70
+ execSync(`mklink "${targetPath}" "${sourcePath}"`, { stdio: 'ignore' });
71
+ } catch (error) {
72
+ await fs.copyFile(sourcePath, targetPath);
73
+ }
74
+ }
75
+
76
+ async function verifySymlinks(projectRoot, platformConfig) {
77
+ const results = [];
78
+
79
+ for (const target of platformConfig.targets) {
80
+ const targetPath = path.join(projectRoot, target.target);
81
+
82
+ try {
83
+ const stats = await fs.lstat(targetPath);
84
+ const exists = stats.isSymbolicLink() || stats.isFile();
85
+
86
+ results.push({
87
+ target: target.target,
88
+ exists,
89
+ isSymlink: stats.isSymbolicLink()
90
+ });
91
+ } catch {
92
+ results.push({
93
+ target: target.target,
94
+ exists: false,
95
+ isSymlink: false
96
+ });
97
+ }
98
+ }
99
+
100
+ return results;
101
+ }
102
+
103
+ module.exports = { createSymlinks, verifySymlinks };
@@ -0,0 +1,79 @@
1
+ function formatTelosSpec(spec) {
2
+ const { telos, via, requirements, tools } = spec;
3
+
4
+ let content = `## TELOS ALIGNMENT\n\n`;
5
+ content += `**Contributes to**: ${telos}\n\n`;
6
+
7
+ if (via && via.length > 0) {
8
+ content += `**Via**:\n`;
9
+ for (const step of via) {
10
+ content += `- ${step.level}: ${step.purpose}\n`;
11
+ }
12
+ content += `\n`;
13
+ }
14
+
15
+ content += `## ADDED Requirements\n\n`;
16
+
17
+ for (const req of requirements) {
18
+ content += `### Requirement: ${req.name}\n\n`;
19
+ content += `${req.statement}\n\n`;
20
+
21
+ if (req.scenarios && req.scenarios.length > 0) {
22
+ for (const scenario of req.scenarios) {
23
+ content += `#### Scenario: ${scenario.name}\n\n`;
24
+ content += `- **WHEN** ${scenario.when}\n`;
25
+ content += `- **THEN** ${scenario.then}\n\n`;
26
+ }
27
+ }
28
+ }
29
+
30
+ if (tools && Object.keys(tools).length > 0) {
31
+ content += `## TOOLS REQUIRED\n\n`;
32
+ for (const [level, levelTools] of Object.entries(tools)) {
33
+ if (levelTools.length > 0) {
34
+ content += `- **${level}**: ${levelTools.join(', ')}\n`;
35
+ }
36
+ }
37
+ content += `\n`;
38
+ }
39
+
40
+ return content;
41
+ }
42
+
43
+ function parseTelosSpec(content) {
44
+ const spec = {
45
+ telos: null,
46
+ via: [],
47
+ requirements: [],
48
+ tools: {}
49
+ };
50
+
51
+ const telosMatch = content.match(/\*\*Contributes to\*\*:\s*(.+)/);
52
+ if (telosMatch) {
53
+ spec.telos = telosMatch[1].trim();
54
+ }
55
+
56
+ const viaSection = content.match(/\*\*Via\*\*:\n((?:- .+\n?)+)/);
57
+ if (viaSection) {
58
+ const lines = viaSection[1].split('\n').filter(l => l.trim());
59
+ spec.via = lines.map(line => {
60
+ const match = line.match(/- (L\d+):\s*(.+)/);
61
+ if (match) {
62
+ return { level: match[1], purpose: match[2].trim() };
63
+ }
64
+ return null;
65
+ }).filter(Boolean);
66
+ }
67
+
68
+ const reqMatches = content.matchAll(/### Requirement: (.+)\n\n([^#]+)/g);
69
+ for (const match of reqMatches) {
70
+ spec.requirements.push({
71
+ name: match[1].trim(),
72
+ statement: match[2].trim()
73
+ });
74
+ }
75
+
76
+ return spec;
77
+ }
78
+
79
+ module.exports = { formatTelosSpec, parseTelosSpec };
@@ -0,0 +1,150 @@
1
+ async function decomposeToLevels(l9Goal, hierarchy) {
2
+ const decomposition = {
3
+ L9: {
4
+ level: 'L9',
5
+ goal: l9Goal,
6
+ purpose: hierarchy.L9.purpose
7
+ }
8
+ };
9
+
10
+ decomposition.L8 = {
11
+ level: 'L8',
12
+ purpose: hierarchy.L8.purpose,
13
+ parentGoal: l9Goal,
14
+ question: 'What business metrics validate this serves our Telos?'
15
+ };
16
+
17
+ decomposition.L7 = {
18
+ level: 'L7',
19
+ purpose: hierarchy.L7.purpose,
20
+ parentGoal: decomposition.L8.purpose,
21
+ question: 'What user insights do we need to track these metrics?'
22
+ };
23
+
24
+ decomposition.L6 = {
25
+ level: 'L6',
26
+ purpose: hierarchy.L6.purpose,
27
+ parentGoal: decomposition.L7.purpose,
28
+ question: 'What UX enables users to provide these insights?'
29
+ };
30
+
31
+ decomposition.L5 = {
32
+ level: 'L5',
33
+ purpose: hierarchy.L5.purpose,
34
+ parentGoal: decomposition.L6.purpose,
35
+ question: 'What workflows must function end-to-end?'
36
+ };
37
+
38
+ decomposition.L4 = {
39
+ level: 'L4',
40
+ purpose: hierarchy.L4.purpose,
41
+ parentGoal: decomposition.L5.purpose,
42
+ question: 'What API contracts enable these workflows?'
43
+ };
44
+
45
+ decomposition.L3 = {
46
+ level: 'L3',
47
+ purpose: hierarchy.L3.purpose,
48
+ parentGoal: decomposition.L4.purpose,
49
+ question: 'What components implement these contracts?'
50
+ };
51
+
52
+ decomposition.L2 = {
53
+ level: 'L2',
54
+ purpose: hierarchy.L2.purpose,
55
+ parentGoal: decomposition.L3.purpose,
56
+ question: 'What functions compose these components?'
57
+ };
58
+
59
+ decomposition.L1 = {
60
+ level: 'L1',
61
+ purpose: hierarchy.L1.purpose,
62
+ parentGoal: decomposition.L2.purpose,
63
+ question: 'What code quality standards must be met?'
64
+ };
65
+
66
+ return decomposition;
67
+ }
68
+
69
+ function buildLineage(requirement, decomposition) {
70
+ const lineage = {
71
+ requirement,
72
+ chain: []
73
+ };
74
+
75
+ const levels = ['L9', 'L8', 'L7', 'L6', 'L5', 'L4', 'L3', 'L2', 'L1'];
76
+
77
+ for (const level of levels) {
78
+ if (decomposition[level]) {
79
+ lineage.chain.push({
80
+ level,
81
+ purpose: decomposition[level].purpose,
82
+ question: decomposition[level].question
83
+ });
84
+ }
85
+ }
86
+
87
+ return lineage;
88
+ }
89
+
90
+ function generateTasksFromDecomposition(decomposition, tools) {
91
+ const tasks = [];
92
+
93
+ if (decomposition.L1) {
94
+ tasks.push({
95
+ level: 'L1',
96
+ title: 'Code Quality Validation',
97
+ description: `Ensure code meets quality standards: ${decomposition.L1.purpose}`,
98
+ tools: tools.L1 || [],
99
+ acceptance: 'All linters pass, no critical issues'
100
+ });
101
+ }
102
+
103
+ if (decomposition.L2) {
104
+ tasks.push({
105
+ level: 'L2',
106
+ title: 'Function Implementation',
107
+ description: `Implement tested functions: ${decomposition.L2.purpose}`,
108
+ tools: tools.L2 || [],
109
+ acceptance: 'All unit tests pass, >80% coverage'
110
+ });
111
+ }
112
+
113
+ if (decomposition.L3) {
114
+ tasks.push({
115
+ level: 'L3',
116
+ title: 'Component Development',
117
+ description: `Build components: ${decomposition.L3.purpose}`,
118
+ tools: tools.L3 || [],
119
+ acceptance: 'Component tests pass, renders correctly'
120
+ });
121
+ }
122
+
123
+ if (decomposition.L5) {
124
+ tasks.push({
125
+ level: 'L5',
126
+ title: 'E2E Workflow Validation',
127
+ description: `Validate workflows: ${decomposition.L5.purpose}`,
128
+ tools: tools.L5 || [],
129
+ acceptance: 'All E2E tests pass, workflows complete successfully'
130
+ });
131
+ }
132
+
133
+ if (decomposition.L9) {
134
+ tasks.push({
135
+ level: 'L9',
136
+ title: 'Telos Alignment Check',
137
+ description: `Verify alignment with ultimate purpose: ${decomposition.L9.purpose}`,
138
+ tools: [],
139
+ acceptance: 'Telos-Guardian approves alignment'
140
+ });
141
+ }
142
+
143
+ return tasks;
144
+ }
145
+
146
+ module.exports = {
147
+ decomposeToLevels,
148
+ buildLineage,
149
+ generateTasksFromDecomposition
150
+ };