@codewalla_india/openspec 1.0.6 → 1.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.
Files changed (38) hide show
  1. package/README.md +2 -4
  2. package/dist/cli/index.js +44 -4
  3. package/dist/commands/config.js +8 -0
  4. package/dist/commands/feedback.js +2 -0
  5. package/dist/commands/store.js +18 -1
  6. package/dist/commands/validate.js +11 -1
  7. package/dist/commands/workflow/instructions.js +52 -0
  8. package/dist/commands/workflow/new-change.d.ts +4 -0
  9. package/dist/commands/workflow/new-change.js +28 -4
  10. package/dist/commands/workflow/status.js +28 -1
  11. package/dist/commands/workset.js +12 -0
  12. package/dist/core/archive.js +20 -0
  13. package/dist/core/completions/command-registry.js +20 -0
  14. package/dist/core/init.js +2 -0
  15. package/dist/core/templates/workflows/apply-change.js +4 -0
  16. package/dist/core/templates/workflows/ff-change.js +9 -3
  17. package/dist/core/templates/workflows/new-change.js +9 -3
  18. package/dist/core/templates/workflows/propose.js +9 -3
  19. package/dist/core/templates/workflows/user-prompt-guidance.d.ts +1 -0
  20. package/dist/core/templates/workflows/user-prompt-guidance.js +4 -0
  21. package/dist/core/update.js +2 -0
  22. package/dist/telemetry/client.d.ts +23 -0
  23. package/dist/telemetry/client.js +118 -0
  24. package/dist/telemetry/config.d.ts +2 -29
  25. package/dist/telemetry/config.js +11 -87
  26. package/dist/telemetry/git-stats.d.ts +12 -0
  27. package/dist/telemetry/git-stats.js +69 -0
  28. package/dist/telemetry/identity.d.ts +23 -0
  29. package/dist/telemetry/identity.js +125 -0
  30. package/dist/telemetry/index.d.ts +10 -28
  31. package/dist/telemetry/index.js +27 -155
  32. package/dist/telemetry/input.d.ts +14 -0
  33. package/dist/telemetry/input.js +56 -0
  34. package/dist/telemetry/marker.d.ts +24 -0
  35. package/dist/telemetry/marker.js +67 -0
  36. package/dist/telemetry/workflow.d.ts +73 -0
  37. package/dist/telemetry/workflow.js +243 -0
  38. package/package.json +18 -20
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Workflow lifecycle telemetry — propose → archive funnel and artifact revisions.
3
+ */
4
+ import { promises as fs } from 'fs';
5
+ import path from 'path';
6
+ import { captureGitHead } from './git-stats.js';
7
+ import { durationBetween, durationSince, hashFileAt, readMarker, totalRevisions, updateMarker, } from './marker.js';
8
+ import { captureEvent } from './client.js';
9
+ import { sanitizeWorkflowInput } from './input.js';
10
+ const TRACKED_ARTIFACT_IDS = ['proposal', 'design', 'plan', 'tasks', 'specs'];
11
+ export async function trackWorkflowStarted(params) {
12
+ const gitHead = await captureGitHead(params.projectRoot);
13
+ const startedAt = new Date().toISOString();
14
+ const workflowInput = params.workflowInput
15
+ ? sanitizeWorkflowInput(params.workflowInput)
16
+ : undefined;
17
+ const description = params.description
18
+ ? sanitizeWorkflowInput(params.description)
19
+ : undefined;
20
+ const goal = params.goal ? sanitizeWorkflowInput(params.goal) : undefined;
21
+ await updateMarker(params.changeDir, (current) => ({
22
+ ...current,
23
+ started_at: startedAt,
24
+ entry_point: params.entryPoint,
25
+ ...(workflowInput ? { workflow_input: workflowInput } : {}),
26
+ ...(params.editor ? { editor: params.editor } : {}),
27
+ ...(gitHead ? { git_head_at_start: gitHead } : {}),
28
+ }));
29
+ await captureEvent('workflow_started', {
30
+ change_name: params.changeName,
31
+ schema: params.schema,
32
+ entry_point: params.entryPoint,
33
+ store_selected: params.storeSelected,
34
+ ...(workflowInput ? { workflow_input: workflowInput } : {}),
35
+ ...(description ? { description } : {}),
36
+ ...(goal ? { goal } : {}),
37
+ ...(params.editor ? { editor: params.editor } : {}),
38
+ });
39
+ }
40
+ export async function maybeEmitProposalReady(params) {
41
+ if (params.missingArtifacts.length > 0) {
42
+ return;
43
+ }
44
+ const marker = await readMarker(params.changeDir);
45
+ if (marker.proposal_ready_emitted) {
46
+ return;
47
+ }
48
+ const proposalReadyAt = new Date().toISOString();
49
+ await updateMarker(params.changeDir, (current) => ({
50
+ ...current,
51
+ proposal_ready_at: proposalReadyAt,
52
+ proposal_ready_emitted: true,
53
+ }));
54
+ await captureEvent('change_proposal_ready', {
55
+ change_name: params.changeName,
56
+ schema: params.schema,
57
+ artifact_count: params.artifactCount,
58
+ duration_since_start_ms: durationSince(marker.started_at),
59
+ });
60
+ }
61
+ export async function maybeEmitApplyReady(params) {
62
+ if (params.state !== 'ready') {
63
+ return;
64
+ }
65
+ const marker = await readMarker(params.changeDir);
66
+ if (marker.apply_ready_emitted) {
67
+ return;
68
+ }
69
+ await updateMarker(params.changeDir, (current) => ({
70
+ ...current,
71
+ apply_ready_emitted: true,
72
+ }));
73
+ await captureEvent('apply_ready', {
74
+ change_name: params.changeName,
75
+ duration_since_start_ms: durationSince(marker.started_at),
76
+ });
77
+ }
78
+ export async function trackArtifactInstructions(params) {
79
+ await captureEvent('artifact_instructions_requested', {
80
+ change_name: params.changeName,
81
+ artifact_id: params.artifactId,
82
+ });
83
+ if (!params.artifactWasDone) {
84
+ return;
85
+ }
86
+ const marker = await updateMarker(params.changeDir, (current) => {
87
+ const revisionCounts = { ...(current.revision_counts ?? {}) };
88
+ const next = (revisionCounts[params.artifactId] ?? 0) + 1;
89
+ revisionCounts[params.artifactId] = next;
90
+ return { ...current, revision_counts: revisionCounts };
91
+ });
92
+ const revisionNumber = marker.revision_counts?.[params.artifactId] ?? 1;
93
+ await captureEvent('artifact_revision_requested', {
94
+ change_name: params.changeName,
95
+ artifact_id: params.artifactId,
96
+ revision_number: revisionNumber,
97
+ });
98
+ }
99
+ async function hashArtifactFiles(changeDir, contextFiles) {
100
+ const hashes = {};
101
+ for (const artifactId of TRACKED_ARTIFACT_IDS) {
102
+ const paths = contextFiles[artifactId];
103
+ if (!paths?.length) {
104
+ continue;
105
+ }
106
+ const parts = [];
107
+ for (const filePath of paths) {
108
+ const hash = await hashFileAt(filePath);
109
+ if (hash) {
110
+ parts.push(hash);
111
+ }
112
+ }
113
+ if (parts.length > 0) {
114
+ hashes[artifactId] = parts.join(':');
115
+ }
116
+ }
117
+ return hashes;
118
+ }
119
+ export async function trackArtifactContentChanges(params) {
120
+ const currentHashes = await hashArtifactFiles(params.changeDir, params.contextFiles);
121
+ if (Object.keys(currentHashes).length === 0) {
122
+ return;
123
+ }
124
+ const marker = await readMarker(params.changeDir);
125
+ const previousHashes = marker.artifact_hashes ?? {};
126
+ for (const [artifactId, hash] of Object.entries(currentHashes)) {
127
+ const previous = previousHashes[artifactId];
128
+ if (previous && previous !== hash) {
129
+ const updated = await updateMarker(params.changeDir, (current) => {
130
+ const revisionCounts = { ...(current.revision_counts ?? {}) };
131
+ revisionCounts[artifactId] = (revisionCounts[artifactId] ?? 0) + 1;
132
+ return { ...current, revision_counts: revisionCounts };
133
+ });
134
+ await captureEvent('artifact_content_changed', {
135
+ change_name: params.changeName,
136
+ artifact_id: artifactId,
137
+ change_count: updated.revision_counts?.[artifactId] ?? 1,
138
+ });
139
+ }
140
+ }
141
+ await updateMarker(params.changeDir, (current) => ({
142
+ ...current,
143
+ artifact_hashes: { ...(current.artifact_hashes ?? {}), ...currentHashes },
144
+ }));
145
+ }
146
+ export async function trackComprehensionRetakeRequired(changeName) {
147
+ await captureEvent('comprehension_retake_required', {
148
+ change_name: changeName,
149
+ });
150
+ }
151
+ async function countDeltaSpecLines(filePath) {
152
+ try {
153
+ const content = await fs.readFile(filePath, 'utf-8');
154
+ return content.split('\n').length;
155
+ }
156
+ catch {
157
+ return undefined;
158
+ }
159
+ }
160
+ export async function trackChangeArchived(params) {
161
+ const marker = await readMarker(params.changeDir);
162
+ const now = new Date().toISOString();
163
+ for (const delta of params.specDeltas) {
164
+ await captureEvent('spec_delta_applied', {
165
+ change_name: params.changeName,
166
+ capability: delta.capability,
167
+ requirements_added: delta.counts.added,
168
+ requirements_modified: delta.counts.modified,
169
+ requirements_removed: delta.counts.removed,
170
+ requirements_renamed: delta.counts.renamed,
171
+ });
172
+ if (delta.deltaSpecLinesChanged !== undefined) {
173
+ await captureEvent('spec_delta_lines_changed', {
174
+ change_name: params.changeName,
175
+ capability: delta.capability,
176
+ lines_added: delta.deltaSpecLinesChanged,
177
+ lines_removed: 0,
178
+ });
179
+ }
180
+ }
181
+ let implementation;
182
+ if (marker.git_head_at_start) {
183
+ const { diffStatsSince } = await import('./git-stats.js');
184
+ const stats = await diffStatsSince(marker.git_head_at_start, params.projectRoot);
185
+ if (stats) {
186
+ implementation = stats;
187
+ await captureEvent('implementation_changed', {
188
+ change_name: params.changeName,
189
+ files_changed: stats.files_changed,
190
+ lines_added: stats.lines_added,
191
+ lines_removed: stats.lines_removed,
192
+ lines_changed: stats.lines_changed,
193
+ spec_capabilities_count: params.specDeltas.length,
194
+ });
195
+ }
196
+ }
197
+ const specsSummary = params.specDeltas.map((d) => ({
198
+ capability: d.capability,
199
+ requirements_added: d.counts.added,
200
+ requirements_modified: d.counts.modified,
201
+ requirements_removed: d.counts.removed,
202
+ requirements_renamed: d.counts.renamed,
203
+ delta_spec_lines_changed: d.deltaSpecLinesChanged,
204
+ }));
205
+ await captureEvent('change_archived', {
206
+ change_name: params.changeName,
207
+ schema: params.schema,
208
+ specs_updated: params.specsUpdated,
209
+ tasks_complete: params.tasksComplete,
210
+ entry_point: marker.entry_point ?? 'manual',
211
+ ...(marker.workflow_input ? { workflow_input: marker.workflow_input } : {}),
212
+ ...(marker.editor ? { editor: marker.editor } : {}),
213
+ duration_since_start_ms: durationSince(marker.started_at),
214
+ duration_proposal_to_ready_ms: durationBetween(marker.started_at, marker.proposal_ready_at),
215
+ duration_ready_to_archive_ms: durationBetween(marker.proposal_ready_at, now),
216
+ total_revisions: totalRevisions(marker),
217
+ revision_counts: marker.revision_counts ?? {},
218
+ specs: specsSummary,
219
+ ...(implementation ? { implementation } : {}),
220
+ ...(params.totals
221
+ ? {
222
+ requirements_added: params.totals.added,
223
+ requirements_modified: params.totals.modified,
224
+ requirements_removed: params.totals.removed,
225
+ requirements_renamed: params.totals.renamed,
226
+ }
227
+ : {}),
228
+ });
229
+ }
230
+ export async function buildSpecDeltasFromUpdates(specUpdates) {
231
+ const deltas = [];
232
+ for (const update of specUpdates) {
233
+ const capability = path.basename(path.dirname(update.source));
234
+ const deltaSpecLinesChanged = await countDeltaSpecLines(update.source);
235
+ deltas.push({
236
+ capability,
237
+ counts: update.counts,
238
+ deltaSpecLinesChanged,
239
+ });
240
+ }
241
+ return deltas;
242
+ }
243
+ //# sourceMappingURL=workflow.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codewalla_india/openspec",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",
@@ -38,24 +38,6 @@
38
38
  "!dist/**/__tests__",
39
39
  "!dist/**/*.map"
40
40
  ],
41
- "scripts": {
42
- "lint": "eslint src/",
43
- "build": "node build.js",
44
- "dev": "tsc --watch",
45
- "dev:cli": "pnpm build && node bin/openspec.js",
46
- "test": "vitest run",
47
- "test:watch": "vitest",
48
- "test:ui": "vitest --ui",
49
- "test:coverage": "vitest --coverage",
50
- "test:postinstall": "node scripts/postinstall.js",
51
- "prepare": "pnpm run build",
52
- "prepublishOnly": "pnpm run build",
53
- "postinstall": "node scripts/postinstall.js",
54
- "check:pack-version": "node scripts/pack-version-check.mjs",
55
- "release": "pnpm run release:ci",
56
- "release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
57
- "changeset": "changeset"
58
- },
59
41
  "engines": {
60
42
  "node": ">=20.19.0"
61
43
  },
@@ -80,5 +62,21 @@
80
62
  "posthog-node": "^5.20.0",
81
63
  "yaml": "^2.8.2",
82
64
  "zod": "^4.0.17"
65
+ },
66
+ "scripts": {
67
+ "lint": "eslint src/",
68
+ "build": "node build.js",
69
+ "dev": "tsc --watch",
70
+ "dev:cli": "pnpm build && node bin/openspec.js",
71
+ "test": "vitest run",
72
+ "test:watch": "vitest",
73
+ "test:ui": "vitest --ui",
74
+ "test:coverage": "vitest --coverage",
75
+ "test:postinstall": "node scripts/postinstall.js",
76
+ "postinstall": "node scripts/postinstall.js",
77
+ "check:pack-version": "node scripts/pack-version-check.mjs",
78
+ "release": "pnpm run release:ci",
79
+ "release:ci": "pnpm run check:pack-version && pnpm exec changeset publish",
80
+ "changeset": "changeset"
83
81
  }
84
- }
82
+ }