@codewalla_india/openspec 1.0.5 → 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 (42) 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/mcp-guidance.d.ts +1 -1
  18. package/dist/core/templates/workflows/mcp-guidance.js +15 -0
  19. package/dist/core/templates/workflows/new-change.js +9 -3
  20. package/dist/core/templates/workflows/propose.js +9 -3
  21. package/dist/core/templates/workflows/user-prompt-guidance.d.ts +1 -0
  22. package/dist/core/templates/workflows/user-prompt-guidance.js +4 -0
  23. package/dist/core/update.js +2 -0
  24. package/dist/telemetry/client.d.ts +23 -0
  25. package/dist/telemetry/client.js +118 -0
  26. package/dist/telemetry/config.d.ts +2 -29
  27. package/dist/telemetry/config.js +11 -87
  28. package/dist/telemetry/git-stats.d.ts +12 -0
  29. package/dist/telemetry/git-stats.js +69 -0
  30. package/dist/telemetry/identity.d.ts +23 -0
  31. package/dist/telemetry/identity.js +125 -0
  32. package/dist/telemetry/index.d.ts +10 -28
  33. package/dist/telemetry/index.js +27 -155
  34. package/dist/telemetry/input.d.ts +14 -0
  35. package/dist/telemetry/input.js +56 -0
  36. package/dist/telemetry/marker.d.ts +24 -0
  37. package/dist/telemetry/marker.js +67 -0
  38. package/dist/telemetry/workflow.d.ts +73 -0
  39. package/dist/telemetry/workflow.js +243 -0
  40. package/package.json +18 -20
  41. package/schemas/spec-driven/schema.yaml +9 -1
  42. package/schemas/spec-driven/templates/proposal.md +1 -0
@@ -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.05",
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
+ }
@@ -15,7 +15,12 @@ artifacts:
15
15
  - **Capabilities**: Identify which specs will be created or modified:
16
16
  - **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<name>/spec.md`. Use kebab-case names (e.g., `user-auth`, `data-export`).
17
17
  - **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Check `openspec/specs/` for existing spec names. Leave empty if no requirement changes.
18
- - **Impact**: Affected code, APIs, dependencies, or systems.
18
+ - **Impact**: Affected code, APIs, dependencies, or systems. If importing from
19
+ Jira, record ticket key(s) here (e.g., `Jira: CW-1234`) for traceability.
20
+
21
+ When importing from Jira: name capabilities by behavioral domain (e.g., `ui`,
22
+ `user-auth`), not by ticket key. Reference the ticket in Impact, not in
23
+ capability names.
19
24
 
20
25
  IMPORTANT: The Capabilities section is critical. It creates the contract between
21
26
  proposal and specs phases. Research existing specs before filling this in.
@@ -38,6 +43,9 @@ artifacts:
38
43
  - New capabilities: use the exact kebab-case name from the proposal (specs/<capability>/spec.md).
39
44
  - Modified capabilities: use the existing spec folder name from openspec/specs/<capability>/ when creating the delta spec at specs/<capability>/spec.md.
40
45
 
46
+ When Jira acceptance criteria exist: translate them into requirements under the
47
+ capability folders listed in the proposal. Never create specs/<ticket-key>/.
48
+
41
49
  Delta operations (use ## headers):
42
50
  - **ADDED Requirements**: New capabilities
43
51
  - **MODIFIED Requirements**: Changed behavior - MUST include full updated content
@@ -21,3 +21,4 @@
21
21
  ## Impact
22
22
 
23
23
  <!-- Affected code, APIs, dependencies, systems -->
24
+ <!-- If this change maps to a Jira ticket, note it here (e.g., Jira: CW-1234). Do NOT use the ticket key as a capability name. -->