specsmd 0.0.34 → 0.1.1

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.
Files changed (33) hide show
  1. package/README.md +21 -1
  2. package/flows/aidlc/agents/inception-agent.md +4 -2
  3. package/flows/aidlc/context-config.yaml +28 -2
  4. package/flows/aidlc/memory-bank.yaml +7 -7
  5. package/flows/aidlc/skills/construction/bolt-list.md +14 -14
  6. package/flows/aidlc/skills/construction/bolt-replan.md +37 -35
  7. package/flows/aidlc/skills/construction/bolt-start.md +133 -8
  8. package/flows/aidlc/skills/construction/navigator.md +7 -7
  9. package/flows/aidlc/skills/inception/bolt-plan.md +50 -30
  10. package/flows/aidlc/skills/master/analyze-context.md +107 -0
  11. package/flows/aidlc/skills/master/explain-flow.md +13 -1
  12. package/flows/aidlc/templates/construction/bolt-template.md +13 -1
  13. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +1 -1
  14. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +1 -1
  15. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +1 -1
  16. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +1 -1
  17. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt.md +3 -3
  18. package/flows/aidlc/templates/construction/bolt-types/simple-construction-bolt.md +92 -23
  19. package/flows/aidlc/templates/construction/bolt-types/spike-bolt.md +2 -2
  20. package/flows/aidlc/templates/construction/construction-log-template.md +2 -2
  21. package/flows/aidlc/templates/inception/inception-log-template.md +2 -2
  22. package/flows/aidlc/templates/inception/requirements-template.md +2 -2
  23. package/flows/aidlc/templates/inception/stories-template.md +1 -1
  24. package/flows/aidlc/templates/inception/story-template.md +2 -2
  25. package/flows/aidlc/templates/inception/system-context-template.md +1 -1
  26. package/flows/aidlc/templates/inception/unit-brief-template.md +2 -2
  27. package/flows/aidlc/templates/inception/units-template.md +1 -1
  28. package/lib/analytics/env-detector.js +92 -0
  29. package/lib/analytics/index.js +22 -0
  30. package/lib/analytics/machine-id.js +33 -0
  31. package/lib/analytics/tracker.js +205 -0
  32. package/lib/installer.js +75 -1
  33. package/package.json +4 -3
@@ -13,7 +13,7 @@ unit: {unit-name}
13
13
  intent: {intent-name}
14
14
  status: draft
15
15
  priority: must|should|could
16
- created: {YYYY-MM-DD}
16
+ created: {YYYY-MM-DDTHH:MM:SSZ}
17
17
  assigned_bolt: null
18
18
  implemented: false
19
19
  ---
@@ -96,7 +96,7 @@ unit: auth-service
96
96
  intent: user-authentication
97
97
  status: ready
98
98
  priority: must
99
- created: 2024-12-05
99
+ created: 2024-12-05T10:00:00Z
100
100
  assigned_bolt: bolt-auth-service-1
101
101
  implemented: false
102
102
  ---
@@ -2,7 +2,7 @@
2
2
  intent: {intent-name}
3
3
  phase: inception
4
4
  status: context-defined
5
- updated: {date}
5
+ updated: {YYYY-MM-DDTHH:MM:SSZ}
6
6
  ---
7
7
 
8
8
  # {Intent Name} - System Context
@@ -12,8 +12,8 @@ unit: {unit-name}
12
12
  intent: {intent-name}
13
13
  phase: inception
14
14
  status: draft|ready
15
- created: {YYYY-MM-DD}
16
- updated: {YYYY-MM-DD}
15
+ created: {YYYY-MM-DDTHH:MM:SSZ}
16
+ updated: {YYYY-MM-DDTHH:MM:SSZ}
17
17
  ---
18
18
  ```
19
19
 
@@ -2,7 +2,7 @@
2
2
  intent: {intent-name}
3
3
  phase: inception
4
4
  status: units-decomposed
5
- updated: {date}
5
+ updated: {YYYY-MM-DDTHH:MM:SSZ}
6
6
  ---
7
7
 
8
8
  # {Intent Name} - Unit Decomposition
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Environment Detection
3
+ *
4
+ * Detects shell environment and telemetry opt-out settings.
5
+ * Used to enrich analytics events and respect user privacy preferences.
6
+ */
7
+
8
+ /**
9
+ * Detect the user's shell/terminal environment
10
+ *
11
+ * @returns {string} Shell name (zsh, bash, powershell, cmd, fish, etc.) or 'unknown'
12
+ */
13
+ function detectShell() {
14
+ if (process.platform === 'win32') {
15
+ const comspec = (process.env.ComSpec || '').toLowerCase();
16
+ if (comspec.includes('powershell') || comspec.includes('pwsh')) {
17
+ return 'powershell';
18
+ }
19
+ if (comspec.includes('cmd')) {
20
+ return 'cmd';
21
+ }
22
+ return 'unknown';
23
+ }
24
+
25
+ // Unix-like systems (macOS, Linux)
26
+ const shell = process.env.SHELL || '';
27
+ const basename = shell.split('/').pop() || 'unknown';
28
+ return basename;
29
+ }
30
+
31
+ /**
32
+ * Check if telemetry is disabled via environment variables or CLI flag
33
+ *
34
+ * Respects:
35
+ * - SPECSMD_TELEMETRY_DISABLED=1
36
+ * - DO_NOT_TRACK=1
37
+ * - CI environments (CI, GITHUB_ACTIONS, GITLAB_CI, CIRCLECI, JENKINS_URL)
38
+ * - --no-telemetry CLI flag
39
+ *
40
+ * @param {Object} options - Optional overrides
41
+ * @param {boolean} options.noTelemetryFlag - CLI flag state
42
+ * @returns {boolean} True if telemetry should be disabled
43
+ */
44
+ function isTelemetryDisabled(options = {}) {
45
+ // Check CLI flag first (highest priority)
46
+ if (options.noTelemetryFlag === true) {
47
+ return true;
48
+ }
49
+
50
+ // Check process.argv for --no-telemetry flag
51
+ if (process.argv.includes('--no-telemetry')) {
52
+ return true;
53
+ }
54
+
55
+ // Check explicit opt-out environment variables
56
+ if (process.env.SPECSMD_TELEMETRY_DISABLED === '1') {
57
+ return true;
58
+ }
59
+
60
+ // Respect DO_NOT_TRACK standard (https://consoledonottrack.com/)
61
+ if (process.env.DO_NOT_TRACK === '1') {
62
+ return true;
63
+ }
64
+
65
+ // Auto-disable in CI environments
66
+ if (process.env.CI === 'true') {
67
+ return true;
68
+ }
69
+
70
+ if (process.env.GITHUB_ACTIONS === 'true') {
71
+ return true;
72
+ }
73
+
74
+ if (process.env.GITLAB_CI === 'true') {
75
+ return true;
76
+ }
77
+
78
+ if (process.env.CIRCLECI === 'true') {
79
+ return true;
80
+ }
81
+
82
+ if (process.env.JENKINS_URL !== undefined) {
83
+ return true;
84
+ }
85
+
86
+ return false;
87
+ }
88
+
89
+ module.exports = {
90
+ detectShell,
91
+ isTelemetryDisabled
92
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Analytics Module
3
+ *
4
+ * Exports the analytics tracker singleton for use throughout the installer.
5
+ *
6
+ * Usage:
7
+ * const analytics = require('./analytics');
8
+ *
9
+ * // Initialize at startup
10
+ * analytics.init();
11
+ *
12
+ * // Track events
13
+ * analytics.trackInstallerStarted();
14
+ * analytics.trackIdesConfirmed(['claude-code', 'cursor']);
15
+ * analytics.trackFlowSelected('aidlc');
16
+ * analytics.trackInstallationCompleted('claude-code', 'aidlc', 1500, 12);
17
+ * analytics.trackInstallationFailed('cursor', 'file_permission', 'aidlc');
18
+ */
19
+
20
+ const tracker = require('./tracker');
21
+
22
+ module.exports = tracker;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Machine ID Generation
3
+ *
4
+ * Generates a stable, anonymous machine identifier using a salted SHA-256 hash
5
+ * of the hostname. This ensures:
6
+ * - Same machine always produces same ID
7
+ * - Cannot reverse-lookup the hostname from the hash
8
+ * - No PII is stored or transmitted
9
+ */
10
+
11
+ const crypto = require('crypto');
12
+ const os = require('os');
13
+
14
+ // Constant salt prevents rainbow table attacks
15
+ // Do not change this value - it would break ID consistency
16
+ const SALT = 'specsmd-analytics-v1';
17
+
18
+ /**
19
+ * Generate a stable machine identifier
20
+ *
21
+ * @returns {string} SHA-256 hash of salted hostname (64 hex characters)
22
+ */
23
+ function getMachineId() {
24
+ const hostname = os.hostname();
25
+ return crypto
26
+ .createHash('sha256')
27
+ .update(SALT + hostname)
28
+ .digest('hex');
29
+ }
30
+
31
+ module.exports = {
32
+ getMachineId
33
+ };
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Analytics Tracker
3
+ *
4
+ * Mixpanel-based analytics for the specsmd installer.
5
+ * Tracks anonymous usage patterns while respecting privacy.
6
+ *
7
+ * Features:
8
+ * - Fire-and-forget event delivery (non-blocking)
9
+ * - Silent failures (never breaks installation)
10
+ * - Privacy-first (no PII, respects opt-out)
11
+ */
12
+
13
+ const crypto = require('crypto');
14
+ const { getMachineId } = require('./machine-id');
15
+ const { detectShell, isTelemetryDisabled } = require('./env-detector');
16
+
17
+ // Mixpanel project token
18
+ // This is safe to embed - analytics tokens are public by design
19
+ const MIXPANEL_TOKEN = 'f405d1fa631f91137f9bb8e0a0277653';
20
+
21
+ /**
22
+ * AnalyticsTracker - Singleton class for event tracking
23
+ */
24
+ class AnalyticsTracker {
25
+ constructor() {
26
+ this.mixpanel = null;
27
+ this.enabled = false;
28
+ this.machineId = null;
29
+ this.sessionId = null;
30
+ this.baseProperties = null;
31
+ this.initialized = false;
32
+ }
33
+
34
+ /**
35
+ * Initialize the analytics tracker
36
+ *
37
+ * @param {Object} options - Initialization options
38
+ * @param {boolean} options.noTelemetry - CLI flag to disable telemetry
39
+ * @returns {boolean} True if analytics is enabled
40
+ */
41
+ init(options = {}) {
42
+ if (this.initialized) {
43
+ return this.enabled;
44
+ }
45
+
46
+ this.initialized = true;
47
+
48
+ // Check if telemetry is disabled
49
+ if (isTelemetryDisabled({ noTelemetryFlag: options.noTelemetry })) {
50
+ this.enabled = false;
51
+ return false;
52
+ }
53
+
54
+ try {
55
+ // Lazy-load Mixpanel to avoid blocking if not needed
56
+ const Mixpanel = require('mixpanel');
57
+ this.mixpanel = Mixpanel.init(MIXPANEL_TOKEN, {
58
+ protocol: 'https',
59
+ host: 'api-eu.mixpanel.com' // EU endpoint for GDPR compliance
60
+ });
61
+
62
+ // Generate IDs
63
+ this.machineId = getMachineId();
64
+ this.sessionId = crypto.randomUUID();
65
+
66
+ // Build base properties included with every event
67
+ this.baseProperties = {
68
+ distinct_id: this.machineId,
69
+ session_id: this.sessionId,
70
+ $os: process.platform,
71
+ shell: detectShell(),
72
+ node_version: process.version,
73
+ specsmd_version: this._getVersion()
74
+ };
75
+
76
+ this.enabled = true;
77
+ return true;
78
+ } catch (error) {
79
+ // Silent failure - analytics should never break installation
80
+ this.enabled = false;
81
+ return false;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Get specsmd version from package.json
87
+ * @private
88
+ */
89
+ _getVersion() {
90
+ try {
91
+ const pkg = require('../../package.json');
92
+ return pkg.version || 'unknown';
93
+ } catch {
94
+ return 'unknown';
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Track an event (fire-and-forget)
100
+ * @private
101
+ *
102
+ * @param {string} eventName - Name of the event
103
+ * @param {Object} properties - Additional event properties
104
+ */
105
+ track(eventName, properties = {}) {
106
+ if (!this.enabled || !this.mixpanel) {
107
+ return;
108
+ }
109
+
110
+ try {
111
+ this.mixpanel.track(eventName, {
112
+ ...this.baseProperties,
113
+ ...properties
114
+ });
115
+ // No await - fire and forget
116
+ } catch {
117
+ // Silent failure
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Track installer_started event
123
+ * Called when the installer begins
124
+ */
125
+ trackInstallerStarted() {
126
+ this.track('installer_started');
127
+ }
128
+
129
+ /**
130
+ * Track ides_confirmed event
131
+ * Called after user confirms IDE/tool selection
132
+ *
133
+ * @param {string[]} ides - Array of selected IDE keys (e.g., ['claude-code', 'cursor'])
134
+ */
135
+ trackIdesConfirmed(ides) {
136
+ this.track('ides_confirmed', {
137
+ ide_count: ides.length,
138
+ ides: ides
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Track flow_selected event
144
+ * Called after user selects an SDLC flow
145
+ *
146
+ * @param {string} flow - Flow key (e.g., 'aidlc', 'agile')
147
+ */
148
+ trackFlowSelected(flow) {
149
+ this.track('flow_selected', {
150
+ flow: flow
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Track installation_completed event
156
+ * Called after successful installation for an IDE
157
+ *
158
+ * @param {string} ide - IDE key (e.g., 'claude-code')
159
+ * @param {string} flow - Flow key (e.g., 'aidlc')
160
+ * @param {number} durationMs - Installation duration in milliseconds
161
+ * @param {number} filesCreated - Number of files created
162
+ */
163
+ trackInstallationCompleted(ide, flow, durationMs, filesCreated) {
164
+ this.track('installation_completed', {
165
+ ide: ide,
166
+ flow: flow,
167
+ duration_ms: durationMs,
168
+ files_created: filesCreated
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Track installation_failed event
174
+ * Called after failed installation for an IDE
175
+ *
176
+ * @param {string} ide - IDE key (e.g., 'claude-code')
177
+ * @param {string} errorCategory - Error category (e.g., 'file_permission', 'network', 'unknown')
178
+ * @param {string} [flow] - Flow key (optional, may not be selected yet)
179
+ */
180
+ trackInstallationFailed(ide, errorCategory, flow) {
181
+ const properties = {
182
+ ide: ide,
183
+ error_category: errorCategory
184
+ };
185
+
186
+ if (flow) {
187
+ properties.flow = flow;
188
+ }
189
+
190
+ this.track('installation_failed', properties);
191
+ }
192
+
193
+ /**
194
+ * Check if analytics is enabled
195
+ * @returns {boolean}
196
+ */
197
+ isEnabled() {
198
+ return this.enabled;
199
+ }
200
+ }
201
+
202
+ // Export singleton instance
203
+ const tracker = new AnalyticsTracker();
204
+
205
+ module.exports = tracker;
package/lib/installer.js CHANGED
@@ -5,10 +5,56 @@ const yaml = require('js-yaml');
5
5
  const CLIUtils = require('./cli-utils');
6
6
  const InstallerFactory = require('./InstallerFactory');
7
7
  const { FLOWS } = require('./constants');
8
+ const analytics = require('./analytics');
8
9
 
9
10
  // Use theme from CLIUtils for consistent styling
10
11
  const { theme } = CLIUtils;
11
12
 
13
+ /**
14
+ * Categorize an error for analytics tracking
15
+ * @param {Error} error - The error to categorize
16
+ * @returns {string} Error category
17
+ */
18
+ function categorizeError(error) {
19
+ const message = (error.message || '').toLowerCase();
20
+
21
+ if (message.includes('permission') || message.includes('eacces')) {
22
+ return 'file_permission';
23
+ }
24
+ if (message.includes('enoent') || message.includes('not found')) {
25
+ return 'file_not_found';
26
+ }
27
+ if (message.includes('network') || message.includes('enotfound') || message.includes('timeout')) {
28
+ return 'network';
29
+ }
30
+ if (message.includes('enospc') || message.includes('disk')) {
31
+ return 'disk_space';
32
+ }
33
+ return 'unknown';
34
+ }
35
+
36
+ /**
37
+ * Count files in a directory recursively
38
+ * @param {string} dir - Directory path
39
+ * @returns {Promise<number>} File count
40
+ */
41
+ async function countFiles(dir) {
42
+ let count = 0;
43
+ try {
44
+ const entries = await fs.readdir(dir, { withFileTypes: true });
45
+ for (const entry of entries) {
46
+ if (entry.isDirectory()) {
47
+ count += await countFiles(path.join(dir, entry.name));
48
+ } else {
49
+ count++;
50
+ }
51
+ }
52
+ } catch {
53
+ // Ignore errors (directory might not exist)
54
+ }
55
+ return count;
56
+ }
57
+
12
58
  async function detectTools() {
13
59
  const detected = [];
14
60
  const installers = InstallerFactory.getInstallers();
@@ -22,6 +68,12 @@ async function detectTools() {
22
68
  }
23
69
 
24
70
  async function install() {
71
+ // Initialize analytics (respects opt-out env vars)
72
+ analytics.init();
73
+ analytics.trackInstallerStarted();
74
+
75
+ const installStartTime = Date.now();
76
+
25
77
  await CLIUtils.displayLogo();
26
78
  CLIUtils.displayHeader('Installation', '');
27
79
 
@@ -67,6 +119,9 @@ async function install() {
67
119
  process.exit(1);
68
120
  }
69
121
 
122
+ // Track IDE selection
123
+ analytics.trackIdesConfirmed(selectedToolKeys);
124
+
70
125
  // Step 3: Select Flow
71
126
  console.log('');
72
127
  CLIUtils.displayStep(3, 4, 'Select SDLC flow');
@@ -88,12 +143,21 @@ async function install() {
88
143
  process.exit(1);
89
144
  }
90
145
 
146
+ // Track flow selection
147
+ analytics.trackFlowSelected(selectedFlow);
148
+
91
149
  // Step 4: Install flow files
92
150
  console.log('');
93
151
  CLIUtils.displayStep(4, 4, `Installing ${FLOWS[selectedFlow].name} flow...`);
94
152
 
95
153
  try {
96
- await installFlow(selectedFlow, selectedToolKeys);
154
+ const filesCreated = await installFlow(selectedFlow, selectedToolKeys);
155
+
156
+ // Track successful installation for each tool
157
+ const durationMs = Date.now() - installStartTime;
158
+ for (const toolKey of selectedToolKeys) {
159
+ analytics.trackInstallationCompleted(toolKey, selectedFlow, durationMs, filesCreated);
160
+ }
97
161
 
98
162
  CLIUtils.displaySuccess(`${FLOWS[selectedFlow].name} flow installed successfully!`, 'Installation Complete');
99
163
 
@@ -108,6 +172,12 @@ async function install() {
108
172
  ];
109
173
  CLIUtils.displayNextSteps(nextSteps);
110
174
  } catch (error) {
175
+ // Track installation failure
176
+ const errorCategory = categorizeError(error);
177
+ for (const toolKey of selectedToolKeys) {
178
+ analytics.trackInstallationFailed(toolKey, errorCategory, selectedFlow);
179
+ }
180
+
111
181
  CLIUtils.displayError(`Installation failed: ${error.message}`);
112
182
  console.log(theme.dim('\nRolling back changes...'));
113
183
  await rollback(selectedFlow, selectedToolKeys);
@@ -199,6 +269,10 @@ async function installFlow(flowKey, toolKeys) {
199
269
  );
200
270
 
201
271
  CLIUtils.displayStatus('', 'Created installation manifest', 'success');
272
+
273
+ // Count files created for analytics
274
+ const filesCreated = await countFiles(specsmdDir);
275
+ return filesCreated;
202
276
  }
203
277
 
204
278
  async function rollback(flowKey, toolKeys) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.0.34",
3
+ "version": "0.1.1",
4
4
  "description": "Multi-agent orchestration system for AI-native software development. Delivers AI-DLC, Agile, and custom SDLC flows as markdown-based agent systems.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {
@@ -10,8 +10,8 @@
10
10
  "test": "vitest run",
11
11
  "test:watch": "vitest",
12
12
  "test:schema": "vitest run __tests__/unit/schema-validation/",
13
- "lint:md": "markdownlint '../memory-bank/**/*.md' 'flows/**/*.md' --config ../.markdownlint.yaml",
14
- "lint:md:fix": "markdownlint '../memory-bank/**/*.md' 'flows/**/*.md' --config ../.markdownlint.yaml --fix",
13
+ "lint:md": "markdownlint 'flows/**/*.md' --config ../.markdownlint.yaml",
14
+ "lint:md:fix": "markdownlint 'flows/**/*.md' --config ../.markdownlint.yaml --fix",
15
15
  "validate:all": "npm run test && npm run lint:md"
16
16
  },
17
17
  "keywords": [
@@ -45,6 +45,7 @@
45
45
  "fs-extra": "^11.1.1",
46
46
  "gradient-string": "^2.0.2",
47
47
  "js-yaml": "^4.1.0",
48
+ "mixpanel": "^0.18.0",
48
49
  "oh-my-logo": "^0.4.0",
49
50
  "prompts": "^2.4.2"
50
51
  },