syntropic 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # syntropic
2
+
3
+ A development methodology for AI-assisted coding. Works with **Claude Code**, **Cursor**, **Windsurf**, and **GitHub Copilot**.
4
+
5
+ One command gives your AI coding tools a disciplined development pipeline — so they research before they plan, plan before they build, and review before they ship.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx syntropic init my-project
11
+ ```
12
+
13
+ Or add to an existing project:
14
+
15
+ ```bash
16
+ cd my-project
17
+ npx syntropic init
18
+ ```
19
+
20
+ You'll be asked which AI tools you use. The CLI generates the right instruction files for each:
21
+
22
+ | Tool | File Generated |
23
+ |------|---------------|
24
+ | Claude Code | `CLAUDE.md` + `.claude/commands/` agents |
25
+ | Cursor | `.cursorrules` |
26
+ | Windsurf | `.windsurfrules` |
27
+ | GitHub Copilot | `.github/copilot-instructions.md` |
28
+
29
+ ## What You Get
30
+
31
+ **Evergreen Rules (EG1-EG9)** — a portable set of development principles:
32
+
33
+ - **EG1: Pre-Flight Loop** — build must pass, no localhost refs, verify after deploy
34
+ - **EG2: Ship and Iterate** — bias to action, only pause for destructive/expensive/irreversible
35
+ - **EG3: Hypothesis-Driven Debugging** — state hypothesis, test without code changes, only code if confirmed
36
+ - **EG7: Implementation Pipeline** — scale process to task size (Full / Lightweight / Minimum cycle)
37
+ - **EG8: Live Site Testing** — test page first, promote to production with approval
38
+ - **EG9: Session Continuity** — re-read rules on reset, verify state before proceeding
39
+
40
+ **Generic Agents** (Claude Code) — dev, qa, research, plan, devops, security
41
+
42
+ **Health Check** — daily GitHub Action with auto-remediation (npm audit fix PRs)
43
+
44
+ **PRISM Methodology** — track efficiency: estimate cost vs. value before complex tasks, learn what works
45
+
46
+ ## Commands
47
+
48
+ ```bash
49
+ # Scaffold a new project (interactive)
50
+ npx syntropic init my-project
51
+
52
+ # Non-interactive with flags
53
+ npx syntropic init my-project --tools claude,cursor --name "My App" --domain myapp.com --test-url /staging --prod-url /
54
+
55
+ # Add a tool to an existing project
56
+ npx syntropic add cursor
57
+ npx syntropic add windsurf copilot
58
+
59
+ # Run a local health check (EG1 pre-flight)
60
+ npx syntropic health
61
+ ```
62
+
63
+ ## Existing Projects
64
+
65
+ Running `syntropic init` in a project that already has a `CLAUDE.md` or `.cursorrules` will **append** the methodology to your existing file — it never overwrites. A `<!-- syntropic -->` marker prevents duplicate rules on re-runs.
66
+
67
+ ## The Methodology
68
+
69
+ The core idea: AI coding tools are powerful but undisciplined. Without rules, they jump straight to code, skip investigation, retry failed approaches, and introduce regressions.
70
+
71
+ Syntropic gives them a process:
72
+
73
+ 1. **Research** — understand the problem before proposing solutions
74
+ 2. **Plan** — break work into sequenced steps, identify risks
75
+ 3. **Build** — implement with minimal changes, match existing patterns
76
+ 4. **Review** — check security, quality, and correctness
77
+ 5. **Verify** — test on a live URL, not just locally
78
+
79
+ Scale the process to the task. A typo fix doesn't need research. A new feature does.
80
+
81
+ ## Philosophy
82
+
83
+ - **Ship and iterate** — momentum over perfection
84
+ - **Hypothesis-driven** — test before you code
85
+ - **Outcome-aligned** — if the user succeeds, do we succeed?
86
+ - **No workarounds** — no bypasses, no "fix later", no shortcuts without approval
87
+
88
+ ## Links
89
+
90
+ - [Website](https://www.syntropicworks.com)
91
+ - [GitHub](https://github.com/petercholford-ship-it/syntropicworks)
92
+
93
+ ## License
94
+
95
+ MIT
package/bin/syntropic.js CHANGED
@@ -15,6 +15,7 @@ const command = args[0];
15
15
 
16
16
  const COMMANDS = {
17
17
  init: () => require('../commands/init'),
18
+ add: () => require('../commands/add'),
18
19
  health: () => require('../commands/health'),
19
20
  };
20
21
 
@@ -32,6 +33,7 @@ if (!command || args.includes('--help') || args.includes('-h')) {
32
33
 
33
34
  Usage:
34
35
  syntropic init [project-name] Scaffold a new project with the Syntropic pipeline
36
+ syntropic add <tool> [tool...] Add support for another AI tool (cursor, windsurf, copilot, claude)
35
37
  syntropic health Run a local health check
36
38
  syntropic --version Show version
37
39
  syntropic --help Show this help
@@ -0,0 +1,195 @@
1
+ /**
2
+ * syntropic add <tool> [tool...]
3
+ *
4
+ * Adds Syntropic pipeline support for additional AI coding tools
5
+ * to an existing project. Creates new instruction files or appends
6
+ * rules to existing ones.
7
+ *
8
+ * Examples:
9
+ * syntropic add cursor
10
+ * syntropic add cursor windsurf copilot
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const readline = require('readline');
16
+
17
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
18
+
19
+ const TOOLS = {
20
+ claude: { label: 'Claude Code', file: 'CLAUDE.md', hasAgents: true },
21
+ cursor: { label: 'Cursor', file: '.cursorrules', hasAgents: false },
22
+ windsurf: { label: 'Windsurf', file: '.windsurfrules', hasAgents: false },
23
+ copilot: { label: 'GitHub Copilot', file: '.github/copilot-instructions.md', hasAgents: false },
24
+ };
25
+
26
+ const TEMPLATE_MAP = {
27
+ claude: ['CLAUDE.md', 'claude-append.template'],
28
+ cursor: ['cursorrules.template', 'tool-append.template'],
29
+ windsurf: ['windsurfrules.template', 'tool-append.template'],
30
+ copilot: ['copilot-instructions.template', 'tool-append.template'],
31
+ };
32
+
33
+ function ask(question) {
34
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
35
+ return new Promise((resolve) => {
36
+ rl.question(question, (answer) => {
37
+ rl.close();
38
+ resolve(answer.trim());
39
+ });
40
+ });
41
+ }
42
+
43
+ function hasSyntropicMarker(filePath) {
44
+ if (!fs.existsSync(filePath)) return false;
45
+ return fs.readFileSync(filePath, 'utf8').includes('<!-- syntropic -->');
46
+ }
47
+
48
+ function renderTemplate(templateFile, replacements) {
49
+ const templatePath = path.join(TEMPLATES_DIR, templateFile);
50
+ if (!fs.existsSync(templatePath)) return null;
51
+ let content = fs.readFileSync(templatePath, 'utf8');
52
+ for (const [key, value] of Object.entries(replacements)) {
53
+ content = content.replace(new RegExp(key, 'g'), value);
54
+ }
55
+ return content;
56
+ }
57
+
58
+ function writeOrAppend(fullTemplate, appendTemplate, destPath, replacements) {
59
+ const relPath = path.relative(process.cwd(), destPath);
60
+ const destDir = path.dirname(destPath);
61
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
62
+
63
+ if (!fs.existsSync(destPath)) {
64
+ const content = renderTemplate(fullTemplate, replacements);
65
+ if (content) fs.writeFileSync(destPath, content, 'utf8');
66
+ console.log(` create ${relPath}`);
67
+ return 'created';
68
+ } else if (hasSyntropicMarker(destPath)) {
69
+ console.log(` skip ${relPath} (Syntropic rules already present)`);
70
+ return 'skipped';
71
+ } else {
72
+ const appendContent = renderTemplate(appendTemplate, replacements);
73
+ if (appendContent) fs.appendFileSync(destPath, appendContent, 'utf8');
74
+ console.log(` append ${relPath} (added Syntropic pipeline rules)`);
75
+ return 'appended';
76
+ }
77
+ }
78
+
79
+ function detectExistingConfig(targetDir) {
80
+ // Try to read project config from existing Syntropic instruction files
81
+ for (const tool of Object.values(TOOLS)) {
82
+ const filePath = path.join(targetDir, tool.file);
83
+ if (fs.existsSync(filePath)) {
84
+ const content = fs.readFileSync(filePath, 'utf8');
85
+ const config = {};
86
+
87
+ // Extract test URL
88
+ const testMatch = content.match(/Test page:\s*(\S+)/);
89
+ if (testMatch) config.testUrl = testMatch[1];
90
+
91
+ // Extract prod URL
92
+ const prodMatch = content.match(/Production:\s*(\S+)/);
93
+ if (prodMatch) config.prodUrl = prodMatch[1];
94
+
95
+ // Extract domain
96
+ const domainMatch = content.match(/Production domain:\*?\*?\s*(\S+)/);
97
+ if (domainMatch) config.prodDomain = domainMatch[1];
98
+
99
+ // Extract project name
100
+ const nameMatch = content.match(/## Project:\s*(.+)/);
101
+ if (nameMatch) config.projectName = nameMatch[1].trim();
102
+
103
+ if (Object.keys(config).length > 0) return config;
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+
109
+ async function run(args) {
110
+ const validTools = Object.keys(TOOLS);
111
+ const requestedTools = args.map(a => a.toLowerCase()).filter(a => validTools.includes(a));
112
+
113
+ if (requestedTools.length === 0) {
114
+ console.log(`
115
+ syntropic add — Add AI tool support to an existing project
116
+
117
+ Usage:
118
+ syntropic add cursor Add Cursor support
119
+ syntropic add windsurf copilot Add multiple tools
120
+ syntropic add claude Add Claude Code support
121
+
122
+ Available tools: ${validTools.join(', ')}
123
+ `);
124
+ process.exit(0);
125
+ }
126
+
127
+ const targetDir = process.cwd();
128
+ console.log(`\n syntropic add — Adding ${requestedTools.map(t => TOOLS[t].label).join(', ')}\n`);
129
+
130
+ // Try to detect project config from existing instruction files
131
+ const existing = detectExistingConfig(targetDir);
132
+ const isInteractive = process.stdin.isTTY !== false;
133
+
134
+ const dirName = path.basename(targetDir);
135
+ const projectName = existing?.projectName || (isInteractive ? await ask(` Project name (${dirName}): `) : '') || dirName;
136
+ const testUrl = existing?.testUrl || (isInteractive ? await ask(' Test page URL path (e.g. /test): ') : '') || '/test';
137
+ const prodUrl = existing?.prodUrl || (isInteractive ? await ask(' Production URL path (e.g. /): ') : '') || '/';
138
+ const prodDomain = existing?.prodDomain || (isInteractive ? await ask(' Production domain (e.g. example.com): ') : '') || 'example.com';
139
+
140
+ if (existing) {
141
+ console.log(` Detected config: ${projectName} (${prodDomain})\n`);
142
+ }
143
+
144
+ const replacements = {
145
+ '\\{\\{PROJECT_NAME\\}\\}': projectName,
146
+ '\\{\\{TEST_URL\\}\\}': testUrl,
147
+ '\\{\\{PROD_URL\\}\\}': prodUrl,
148
+ '\\{\\{PROD_DOMAIN\\}\\}': prodDomain,
149
+ };
150
+
151
+ for (const tool of requestedTools) {
152
+ const [fullTpl, appendTpl] = TEMPLATE_MAP[tool];
153
+ const destPath = path.join(targetDir, TOOLS[tool].file);
154
+ writeOrAppend(fullTpl, appendTpl, destPath, replacements);
155
+ }
156
+
157
+ // If adding Claude, also copy agents
158
+ if (requestedTools.includes('claude')) {
159
+ const claudeDir = path.join(targetDir, '.claude');
160
+ const templateClaudeDir = path.join(TEMPLATES_DIR, '.claude');
161
+ if (fs.existsSync(templateClaudeDir)) {
162
+ const { copyDir } = require('./init');
163
+ if (typeof copyDir === 'function') {
164
+ copyDir(templateClaudeDir, claudeDir);
165
+ } else {
166
+ // Inline copy for agents
167
+ const commandsDir = path.join(templateClaudeDir, 'commands');
168
+ const destCommandsDir = path.join(claudeDir, 'commands');
169
+ if (fs.existsSync(commandsDir)) {
170
+ if (!fs.existsSync(destCommandsDir)) fs.mkdirSync(destCommandsDir, { recursive: true });
171
+ for (const file of fs.readdirSync(commandsDir)) {
172
+ const dest = path.join(destCommandsDir, file);
173
+ if (!fs.existsSync(dest)) {
174
+ fs.copyFileSync(path.join(commandsDir, file), dest);
175
+ console.log(` create ${path.relative(process.cwd(), dest)}`);
176
+ }
177
+ }
178
+ }
179
+ // Copy EVERGREEN_RULES.md
180
+ const egSrc = path.join(templateClaudeDir, 'EVERGREEN_RULES.md');
181
+ const egDest = path.join(claudeDir, 'EVERGREEN_RULES.md');
182
+ if (fs.existsSync(egSrc) && !fs.existsSync(egDest)) {
183
+ fs.copyFileSync(egSrc, egDest);
184
+ console.log(` create ${path.relative(process.cwd(), egDest)}`);
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ console.log(`
191
+ Done! Added: ${requestedTools.map(t => TOOLS[t].label).join(', ')}
192
+ `);
193
+ }
194
+
195
+ module.exports = run;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syntropic",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Philosophy-as-code development pipeline for Claude Code, Cursor, Windsurf, and GitHub Copilot.",
5
5
  "bin": {
6
6
  "syntropic": "./bin/syntropic.js"