syntropic 0.9.3 → 0.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.
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;
@@ -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 + YYYY-MM-DD)
45
- * This rotates daily — server re-hashes with its own salt.
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
- const dailySalt = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
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
 
@@ -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
- console.log(` ${parts.join(' | ')}`);
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",
3
+ "version": "0.9.4",
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"