syntropic 0.9.3 → 0.9.5
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/commands/audit.js +60 -0
- package/commands/config-utils.js +4 -4
- package/commands/init.js +18 -0
- package/commands/report.js +23 -1
- package/package.json +1 -1
package/commands/audit.js
CHANGED
|
@@ -288,6 +288,66 @@ function run(args) {
|
|
|
288
288
|
|
|
289
289
|
console.log(`\n ${dim('Run `syntropic init` to install the methodology and prevent these automatically.')}`);
|
|
290
290
|
console.log(` ${dim('Learn more: https://www.syntropicworks.com')}\n`);
|
|
291
|
+
|
|
292
|
+
// Anonymous audit ping — fire-and-forget, respects telemetry opt-out
|
|
293
|
+
sendAuditPing(score);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function sendAuditPing(score) {
|
|
297
|
+
try {
|
|
298
|
+
const os = require('os');
|
|
299
|
+
const crypto = require('crypto');
|
|
300
|
+
const https = require('https');
|
|
301
|
+
const configPath = path.join(os.homedir(), '.syntropic', 'config.json');
|
|
302
|
+
|
|
303
|
+
let anonId;
|
|
304
|
+
if (fs.existsSync(configPath)) {
|
|
305
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
306
|
+
if (config.telemetry === 'disabled' || config.telemetry === false) return;
|
|
307
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
308
|
+
anonId = crypto.createHash('sha256').update(config.device_id + date).digest('hex');
|
|
309
|
+
} else {
|
|
310
|
+
// No config yet (haven't run init) — hash hostname+user for counting
|
|
311
|
+
anonId = crypto.createHash('sha256').update(os.hostname() + os.userInfo().username).digest('hex');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Hash repo identity for per-project tracking
|
|
315
|
+
let repoId = null;
|
|
316
|
+
try {
|
|
317
|
+
const { execSync } = require('child_process');
|
|
318
|
+
let identity;
|
|
319
|
+
try {
|
|
320
|
+
identity = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
321
|
+
} catch {
|
|
322
|
+
identity = path.basename(process.cwd());
|
|
323
|
+
}
|
|
324
|
+
repoId = crypto.createHash('sha256').update(identity).digest('hex').slice(0, 16);
|
|
325
|
+
} catch {}
|
|
326
|
+
|
|
327
|
+
const payload = JSON.stringify({
|
|
328
|
+
type: 'audit',
|
|
329
|
+
schema_version: '1',
|
|
330
|
+
anon_id: anonId,
|
|
331
|
+
repo_hash: repoId,
|
|
332
|
+
score,
|
|
333
|
+
cli_version: require('../package.json').version,
|
|
334
|
+
os: process.platform,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const url = new URL('https://www.syntropicworks.com/api/v1/prism/report');
|
|
338
|
+
const req = https.request({
|
|
339
|
+
hostname: url.hostname,
|
|
340
|
+
port: 443,
|
|
341
|
+
path: url.pathname,
|
|
342
|
+
method: 'POST',
|
|
343
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
344
|
+
timeout: 5000,
|
|
345
|
+
}, (res) => { res.resume(); });
|
|
346
|
+
req.on('error', () => {});
|
|
347
|
+
req.on('timeout', () => req.destroy());
|
|
348
|
+
req.write(payload);
|
|
349
|
+
req.end();
|
|
350
|
+
} catch {}
|
|
291
351
|
}
|
|
292
352
|
|
|
293
353
|
module.exports = run;
|
package/commands/config-utils.js
CHANGED
|
@@ -41,12 +41,12 @@ function saveConfig(config) {
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Generate the client_hash for double-hash anonymization.
|
|
44
|
-
* client_hash = SHA-256(device_id
|
|
45
|
-
*
|
|
44
|
+
* client_hash = SHA-256(device_id)
|
|
45
|
+
* Stable per install — server re-hashes with its own salt before storing.
|
|
46
|
+
* device_id is already a random UUID with no identifying information.
|
|
46
47
|
*/
|
|
47
48
|
function clientHash(deviceId) {
|
|
48
|
-
|
|
49
|
-
return crypto.createHash('sha256').update(deviceId + dailySalt).digest('hex');
|
|
49
|
+
return crypto.createHash('sha256').update(deviceId).digest('hex');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
package/commands/init.js
CHANGED
|
@@ -298,6 +298,24 @@ async function run(args) {
|
|
|
298
298
|
// Ensure ~/.syntropic/config.json exists (device_id, hmac_key, telemetry)
|
|
299
299
|
ensureConfig();
|
|
300
300
|
|
|
301
|
+
// Install syntropic as a dev dependency so `syntropic report` works in every session
|
|
302
|
+
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
303
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
304
|
+
try {
|
|
305
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
306
|
+
const hasDep = pkg.dependencies?.syntropic || pkg.devDependencies?.syntropic;
|
|
307
|
+
if (!hasDep) {
|
|
308
|
+
const { execSync } = require('child_process');
|
|
309
|
+
console.log(' install syntropic as dev dependency (enables `syntropic report` in every session)');
|
|
310
|
+
execSync('npm install --save-dev syntropic', { cwd: targetDir, stdio: 'pipe', timeout: 30000 });
|
|
311
|
+
} else {
|
|
312
|
+
console.log(' skip syntropic already in package.json');
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
console.log(' skip could not install syntropic as dev dependency (install manually: npm i -D syntropic)');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
301
319
|
console.log(`
|
|
302
320
|
Done! Your project is set up with the Syntropic pipeline.
|
|
303
321
|
|
package/commands/report.js
CHANGED
|
@@ -14,6 +14,21 @@ const { loadConfig, clientHash, hmacSign } = require('./config-utils');
|
|
|
14
14
|
const REPORT_URL = 'https://www.syntropicworks.com/api/v1/prism/report';
|
|
15
15
|
const SCHEMA_VERSION = '1.0';
|
|
16
16
|
|
|
17
|
+
function repoHash() {
|
|
18
|
+
try {
|
|
19
|
+
const { execSync } = require('child_process');
|
|
20
|
+
let identity;
|
|
21
|
+
try {
|
|
22
|
+
identity = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
23
|
+
} catch {
|
|
24
|
+
identity = require('path').basename(process.cwd());
|
|
25
|
+
}
|
|
26
|
+
return crypto.createHash('sha256').update(identity).digest('hex').slice(0, 16);
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
function parseFlags(args) {
|
|
18
33
|
const flags = {};
|
|
19
34
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -241,6 +256,7 @@ async function run(args) {
|
|
|
241
256
|
schema_version: SCHEMA_VERSION,
|
|
242
257
|
cli_version: require('../package.json').version,
|
|
243
258
|
idempotency_key: idempotencyKey,
|
|
259
|
+
repo_hash: repoHash(),
|
|
244
260
|
cycle: {
|
|
245
261
|
weight,
|
|
246
262
|
phases_run: phases,
|
|
@@ -290,7 +306,13 @@ async function run(args) {
|
|
|
290
306
|
const formatted = saved >= 1000 ? `~${Math.round(saved / 1000)}k` : `~${saved}`;
|
|
291
307
|
parts.push(`${formatted} tokens saved`);
|
|
292
308
|
}
|
|
293
|
-
|
|
309
|
+
const line = parts.join(' | ');
|
|
310
|
+
const w = Math.max(55, line.length + 4);
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log(' ' + '\u250C' + '\u2500'.repeat(w) + '\u2510');
|
|
313
|
+
console.log(' ' + '\u2502' + ' ' + line + ' '.repeat(w - line.length - 2) + '\u2502');
|
|
314
|
+
console.log(' ' + '\u2514' + '\u2500'.repeat(w) + '\u2518');
|
|
315
|
+
console.log('');
|
|
294
316
|
} else {
|
|
295
317
|
console.log(` PRISM report: server returned ${res.statusCode} (non-blocking).`);
|
|
296
318
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "syntropic",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Ship better software with a proven development methodology. Audit your git history, install disciplined rules, and track iterations — for Claude Code, Cursor, Windsurf, GitHub Copilot, and OpenAI Codex.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"syntropic": "./bin/syntropic.js"
|