specweave 0.28.0 → 0.28.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 (56) hide show
  1. package/README.md +64 -72
  2. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +6 -2
  3. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  4. package/dist/plugins/specweave-github/lib/github-client-v2.js +28 -8
  5. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  6. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts +21 -0
  7. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts.map +1 -0
  8. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +166 -0
  9. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -0
  10. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +1 -1
  11. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  12. package/dist/src/core/repo-structure/repo-structure-manager.js +5 -11
  13. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  14. package/dist/src/core/sync/label-detector.d.ts.map +1 -1
  15. package/dist/src/core/sync/label-detector.js +22 -9
  16. package/dist/src/core/sync/label-detector.js.map +1 -1
  17. package/dist/src/metrics/calculators/deployment-frequency.d.ts +12 -8
  18. package/dist/src/metrics/calculators/deployment-frequency.d.ts.map +1 -1
  19. package/dist/src/metrics/calculators/deployment-frequency.js +16 -12
  20. package/dist/src/metrics/calculators/deployment-frequency.js.map +1 -1
  21. package/dist/src/metrics/dora-calculator.d.ts +2 -1
  22. package/dist/src/metrics/dora-calculator.d.ts.map +1 -1
  23. package/dist/src/metrics/dora-calculator.js +9 -4
  24. package/dist/src/metrics/dora-calculator.js.map +1 -1
  25. package/dist/src/metrics/github-client.d.ts +12 -0
  26. package/dist/src/metrics/github-client.d.ts.map +1 -1
  27. package/dist/src/metrics/github-client.js +30 -0
  28. package/dist/src/metrics/github-client.js.map +1 -1
  29. package/dist/src/sync/sync-coordinator.d.ts +33 -0
  30. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  31. package/dist/src/sync/sync-coordinator.js +203 -2
  32. package/dist/src/sync/sync-coordinator.js.map +1 -1
  33. package/dist/src/utils/env-file-generator.d.ts +8 -23
  34. package/dist/src/utils/env-file-generator.d.ts.map +1 -1
  35. package/dist/src/utils/env-file-generator.js +31 -71
  36. package/dist/src/utils/env-file-generator.js.map +1 -1
  37. package/package.json +7 -2
  38. package/plugins/specweave/agents/architect/AGENT.md +2 -2
  39. package/plugins/specweave/agents/docs-writer/AGENT.md +2 -2
  40. package/plugins/specweave/agents/pm/AGENT.md +2 -2
  41. package/plugins/specweave/agents/qa-lead/AGENT.md +2 -2
  42. package/plugins/specweave/agents/security/AGENT.md +2 -2
  43. package/plugins/specweave/agents/tdd-orchestrator/AGENT.md +2 -2
  44. package/plugins/specweave/agents/tech-lead/AGENT.md +2 -2
  45. package/plugins/specweave/agents/test-aware-planner/AGENT.md +2 -2
  46. package/plugins/specweave/hooks/post-increment-completion.sh +84 -0
  47. package/plugins/specweave/hooks/post-increment-planning.sh +114 -7
  48. package/plugins/specweave/lib/hooks/sync-increment-closure.js +66 -0
  49. package/plugins/specweave/lib/hooks/sync-increment-closure.ts +111 -0
  50. package/plugins/specweave-github/lib/github-client-v2.js +32 -8
  51. package/plugins/specweave-github/lib/github-client-v2.ts +31 -9
  52. package/plugins/specweave-github/lib/github-feature-sync-cli.js +135 -0
  53. package/plugins/specweave-github/lib/github-feature-sync-cli.ts +194 -0
  54. package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +43 -0
  55. package/plugins/specweave-infrastructure/agents/devops/AGENT.md +2 -2
  56. package/src/templates/.env.example +9 -26
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "fs";
3
+ import * as path from "path";
4
+ import { GitHubFeatureSync } from "./github-feature-sync.js";
5
+ import { GitHubClientV2 } from "./github-client-v2.js";
6
+ async function loadGitHubConfig() {
7
+ const projectRoot = process.cwd();
8
+ const configPath = path.join(projectRoot, ".specweave/config.json");
9
+ let owner = process.env.GITHUB_OWNER || "";
10
+ let repo = process.env.GITHUB_REPO || "";
11
+ const token = process.env.GITHUB_TOKEN || "";
12
+ if (existsSync(configPath)) {
13
+ try {
14
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
15
+ if (config.sync?.github?.owner && config.sync?.github?.repo) {
16
+ owner = config.sync.github.owner;
17
+ repo = config.sync.github.repo;
18
+ } else if (config.multiProject?.enabled && config.multiProject?.activeProject) {
19
+ const activeProject = config.multiProject.activeProject;
20
+ const projectConfig = config.multiProject.projects?.[activeProject];
21
+ if (projectConfig?.externalTools?.github?.repository) {
22
+ const parts = projectConfig.externalTools.github.repository.split("/");
23
+ if (parts.length === 2) {
24
+ owner = parts[0];
25
+ repo = parts[1];
26
+ }
27
+ }
28
+ } else if (config.sync?.activeProfile && config.sync?.profiles) {
29
+ const profile = config.sync.profiles[config.sync.activeProfile];
30
+ if (profile?.config?.owner && profile?.config?.repo) {
31
+ owner = profile.config.owner;
32
+ repo = profile.config.repo;
33
+ }
34
+ }
35
+ } catch (error) {
36
+ console.error("\u26A0\uFE0F Failed to parse config.json:", error);
37
+ }
38
+ }
39
+ if (!owner || !repo) {
40
+ try {
41
+ const { execSync } = await import("child_process");
42
+ const remoteUrl = execSync("git remote get-url origin 2>/dev/null", {
43
+ encoding: "utf-8",
44
+ cwd: projectRoot
45
+ }).trim();
46
+ const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
47
+ if (match) {
48
+ owner = owner || match[1];
49
+ repo = repo || match[2];
50
+ }
51
+ } catch {
52
+ }
53
+ }
54
+ if (!token) {
55
+ console.error("\u274C GITHUB_TOKEN not set");
56
+ console.error(" Set it in .env file or export GITHUB_TOKEN=ghp_xxx");
57
+ return null;
58
+ }
59
+ if (!owner || !repo) {
60
+ console.error("\u274C Could not detect GitHub owner/repo");
61
+ console.error(" Set sync.github.owner and sync.github.repo in .specweave/config.json");
62
+ return null;
63
+ }
64
+ return { owner, repo, token };
65
+ }
66
+ async function main() {
67
+ const args = process.argv.slice(2);
68
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
69
+ console.log("Usage: node github-feature-sync-cli.js <feature-id>");
70
+ console.log("");
71
+ console.log("Arguments:");
72
+ console.log(" feature-id Feature ID (e.g., FS-062)");
73
+ console.log("");
74
+ console.log("Environment:");
75
+ console.log(" GITHUB_TOKEN Required - GitHub personal access token");
76
+ console.log("");
77
+ console.log("Example:");
78
+ console.log(" GITHUB_TOKEN=ghp_xxx node github-feature-sync-cli.js FS-062");
79
+ process.exit(args.length === 0 ? 1 : 0);
80
+ }
81
+ const featureId = args[0];
82
+ if (!featureId.match(/^FS-\d+$/i)) {
83
+ console.error(`\u274C Invalid feature ID: ${featureId}`);
84
+ console.error(" Expected format: FS-XXX (e.g., FS-062)");
85
+ process.exit(1);
86
+ }
87
+ console.log(`
88
+ \u{1F419} GitHub Feature Sync CLI`);
89
+ console.log(` Feature: ${featureId}`);
90
+ const config = await loadGitHubConfig();
91
+ if (!config) {
92
+ process.exit(1);
93
+ }
94
+ console.log(` Repository: ${config.owner}/${config.repo}`);
95
+ const projectRoot = process.cwd();
96
+ const specsDir = path.join(projectRoot, ".specweave/docs/internal/specs");
97
+ const profile = {
98
+ provider: "github",
99
+ displayName: "GitHub",
100
+ config: {
101
+ owner: config.owner,
102
+ repo: config.repo,
103
+ token: config.token
104
+ },
105
+ timeRange: {
106
+ default: "1M",
107
+ max: "3M"
108
+ }
109
+ };
110
+ const client = new GitHubClientV2(profile);
111
+ const sync = new GitHubFeatureSync(client, specsDir, projectRoot);
112
+ try {
113
+ console.log(`
114
+ \u{1F504} Syncing ${featureId} to GitHub...`);
115
+ const result = await sync.syncFeatureToGitHub(featureId);
116
+ console.log(`
117
+ \u2705 Sync complete!`);
118
+ console.log(` \u{1F3AF} Milestone: #${result.milestoneNumber}`);
119
+ console.log(` \u{1F4DD} Issues created: ${result.issuesCreated}`);
120
+ console.log(` \u{1F504} Issues updated: ${result.issuesUpdated}`);
121
+ console.log(` \u{1F4DA} User stories processed: ${result.userStoriesProcessed}`);
122
+ if (result.milestoneUrl) {
123
+ console.log(` \u{1F517} ${result.milestoneUrl}`);
124
+ }
125
+ process.exit(0);
126
+ } catch (error) {
127
+ console.error(`
128
+ \u274C Sync failed:`, error);
129
+ process.exit(1);
130
+ }
131
+ }
132
+ main().catch((error) => {
133
+ console.error("Fatal error:", error);
134
+ process.exit(1);
135
+ });
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GitHub Feature Sync CLI
4
+ *
5
+ * CLI wrapper for GitHubFeatureSync.syncFeatureToGitHub()
6
+ * Called by post-increment-planning.sh hook to create GitHub issues
7
+ * after increment creation.
8
+ *
9
+ * Usage:
10
+ * node github-feature-sync-cli.js <feature-id>
11
+ * node github-feature-sync-cli.js FS-062
12
+ *
13
+ * Environment:
14
+ * GITHUB_TOKEN - Required
15
+ * GITHUB_OWNER - Optional (detected from config.json or git remote)
16
+ * GITHUB_REPO - Optional (detected from config.json or git remote)
17
+ *
18
+ * @see ADR-0139 (Unified Post-Increment Sync)
19
+ */
20
+
21
+ import { existsSync, readFileSync } from 'fs';
22
+ import * as path from 'path';
23
+ import { GitHubFeatureSync } from './github-feature-sync.js';
24
+ import { GitHubClientV2 } from './github-client-v2.js';
25
+
26
+ interface GitHubConfig {
27
+ owner: string;
28
+ repo: string;
29
+ token: string;
30
+ }
31
+
32
+ async function loadGitHubConfig(): Promise<GitHubConfig | null> {
33
+ const projectRoot = process.cwd();
34
+ const configPath = path.join(projectRoot, '.specweave/config.json');
35
+
36
+ let owner = process.env.GITHUB_OWNER || '';
37
+ let repo = process.env.GITHUB_REPO || '';
38
+ const token = process.env.GITHUB_TOKEN || '';
39
+
40
+ // Try to load from config.json
41
+ if (existsSync(configPath)) {
42
+ try {
43
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
44
+
45
+ // Method 1: sync.github
46
+ if (config.sync?.github?.owner && config.sync?.github?.repo) {
47
+ owner = config.sync.github.owner;
48
+ repo = config.sync.github.repo;
49
+ }
50
+ // Method 2: multiProject.projects[activeProject].externalTools.github
51
+ else if (config.multiProject?.enabled && config.multiProject?.activeProject) {
52
+ const activeProject = config.multiProject.activeProject;
53
+ const projectConfig = config.multiProject.projects?.[activeProject];
54
+ if (projectConfig?.externalTools?.github?.repository) {
55
+ const parts = projectConfig.externalTools.github.repository.split('/');
56
+ if (parts.length === 2) {
57
+ owner = parts[0];
58
+ repo = parts[1];
59
+ }
60
+ }
61
+ }
62
+ // Method 3: sync.profiles[activeProfile]
63
+ else if (config.sync?.activeProfile && config.sync?.profiles) {
64
+ const profile = config.sync.profiles[config.sync.activeProfile];
65
+ if (profile?.config?.owner && profile?.config?.repo) {
66
+ owner = profile.config.owner;
67
+ repo = profile.config.repo;
68
+ }
69
+ }
70
+ } catch (error) {
71
+ console.error('āš ļø Failed to parse config.json:', error);
72
+ }
73
+ }
74
+
75
+ // Fallback: detect from git remote
76
+ if (!owner || !repo) {
77
+ try {
78
+ const { execSync } = await import('child_process');
79
+ const remoteUrl = execSync('git remote get-url origin 2>/dev/null', {
80
+ encoding: 'utf-8',
81
+ cwd: projectRoot
82
+ }).trim();
83
+
84
+ // Parse GitHub URL (HTTPS or SSH)
85
+ const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
86
+ if (match) {
87
+ owner = owner || match[1];
88
+ repo = repo || match[2];
89
+ }
90
+ } catch {
91
+ // Git detection failed, continue with what we have
92
+ }
93
+ }
94
+
95
+ if (!token) {
96
+ console.error('āŒ GITHUB_TOKEN not set');
97
+ console.error(' Set it in .env file or export GITHUB_TOKEN=ghp_xxx');
98
+ return null;
99
+ }
100
+
101
+ if (!owner || !repo) {
102
+ console.error('āŒ Could not detect GitHub owner/repo');
103
+ console.error(' Set sync.github.owner and sync.github.repo in .specweave/config.json');
104
+ return null;
105
+ }
106
+
107
+ return { owner, repo, token };
108
+ }
109
+
110
+ async function main() {
111
+ const args = process.argv.slice(2);
112
+
113
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
114
+ console.log('Usage: node github-feature-sync-cli.js <feature-id>');
115
+ console.log('');
116
+ console.log('Arguments:');
117
+ console.log(' feature-id Feature ID (e.g., FS-062)');
118
+ console.log('');
119
+ console.log('Environment:');
120
+ console.log(' GITHUB_TOKEN Required - GitHub personal access token');
121
+ console.log('');
122
+ console.log('Example:');
123
+ console.log(' GITHUB_TOKEN=ghp_xxx node github-feature-sync-cli.js FS-062');
124
+ process.exit(args.length === 0 ? 1 : 0);
125
+ }
126
+
127
+ const featureId = args[0];
128
+
129
+ // Validate feature ID format
130
+ if (!featureId.match(/^FS-\d+$/i)) {
131
+ console.error(`āŒ Invalid feature ID: ${featureId}`);
132
+ console.error(' Expected format: FS-XXX (e.g., FS-062)');
133
+ process.exit(1);
134
+ }
135
+
136
+ console.log(`\nšŸ™ GitHub Feature Sync CLI`);
137
+ console.log(` Feature: ${featureId}`);
138
+
139
+ // Load config
140
+ const config = await loadGitHubConfig();
141
+ if (!config) {
142
+ process.exit(1);
143
+ }
144
+
145
+ console.log(` Repository: ${config.owner}/${config.repo}`);
146
+
147
+ // Create client and sync
148
+ const projectRoot = process.cwd();
149
+ const specsDir = path.join(projectRoot, '.specweave/docs/internal/specs');
150
+
151
+ const profile = {
152
+ provider: 'github' as const,
153
+ displayName: 'GitHub',
154
+ config: {
155
+ owner: config.owner,
156
+ repo: config.repo,
157
+ token: config.token
158
+ },
159
+ timeRange: {
160
+ default: '1M' as const,
161
+ max: '3M' as const
162
+ }
163
+ };
164
+
165
+ const client = new GitHubClientV2(profile);
166
+ const sync = new GitHubFeatureSync(client, specsDir, projectRoot);
167
+
168
+ try {
169
+ console.log(`\nšŸ”„ Syncing ${featureId} to GitHub...`);
170
+
171
+ const result = await sync.syncFeatureToGitHub(featureId);
172
+
173
+ console.log(`\nāœ… Sync complete!`);
174
+ console.log(` šŸŽÆ Milestone: #${result.milestoneNumber}`);
175
+ console.log(` šŸ“ Issues created: ${result.issuesCreated}`);
176
+ console.log(` šŸ”„ Issues updated: ${result.issuesUpdated}`);
177
+ console.log(` šŸ“š User stories processed: ${result.userStoriesProcessed}`);
178
+
179
+ if (result.milestoneUrl) {
180
+ console.log(` šŸ”— ${result.milestoneUrl}`);
181
+ }
182
+
183
+ process.exit(0);
184
+ } catch (error) {
185
+ console.error(`\nāŒ Sync failed:`, error);
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ // Run CLI
191
+ main().catch(error => {
192
+ console.error('Fatal error:', error);
193
+ process.exit(1);
194
+ });
@@ -11,6 +11,49 @@ description: Standard format for ALL GitHub issues created by SpecWeave. Ensures
11
11
  - Increments (0001-* folders)
12
12
  - Specs (spec-*.md files)
13
13
 
14
+ ## Issue Title Format (MANDATORY)
15
+
16
+ ### āœ… ONLY Allowed Title Formats
17
+
18
+ ```
19
+ [FS-XXX][US-YYY] User Story Title ← STANDARD (User Stories)
20
+ [FS-XXX] Feature Title ← Rare (Feature-level only)
21
+ ```
22
+
23
+ **Examples**:
24
+ - āœ… `[FS-059][US-003] Hook Optimization (P0)`
25
+ - āœ… `[FS-054][US-001] Fix Reopen Desync Bug (P0)`
26
+ - āœ… `[FS-048] Smart Pagination Feature`
27
+
28
+ ### āŒ PROHIBITED Title Formats (NEVER USE)
29
+
30
+ ```
31
+ [BUG] Title ← WRONG! Bug is a LABEL, not title prefix
32
+ [HOTFIX] Title ← WRONG! Hotfix is a LABEL
33
+ [FEATURE] Title ← WRONG! Feature is a LABEL
34
+ [DOCS] Title ← WRONG! Docs is a LABEL
35
+ [Increment XXXX] Title ← DEPRECATED! Old format
36
+ ```
37
+
38
+ **Why?** Type-based prefixes like `[BUG]` break traceability:
39
+ - Cannot link to Feature Spec (FS-XXX)
40
+ - Cannot link to User Story (US-YYY)
41
+ - Violates SpecWeave's data flow: `Increment → Living Docs → GitHub`
42
+
43
+ **What to do instead?**
44
+ 1. Link work to a Feature (FS-XXX) in living docs
45
+ 2. Create User Story (US-YYY) under that feature
46
+ 3. Use GitHub **labels** for categorization: `bug`, `enhancement`, `hotfix`
47
+
48
+ ### Validation
49
+
50
+ The GitHub client (`github-client-v2.ts`) enforces this:
51
+ - Rejects titles starting with `[BUG]`, `[HOTFIX]`, `[FEATURE]`, etc.
52
+ - Rejects deprecated `[Increment XXXX]` format
53
+ - Only allows `[FS-XXX][US-YYY]` or `[FS-XXX]` formats
54
+
55
+ ---
56
+
14
57
  ## The Standard Format
15
58
 
16
59
  ### āœ… Required Elements
@@ -2,8 +2,8 @@
2
2
  name: devops
3
3
  description: DevOps and infrastructure expert that generates IaC ONE COMPONENT AT A TIME (VPC → Compute → Database → Monitoring) to prevent crashes. Handles Terraform, Kubernetes, Docker, CI/CD. **CRITICAL CHUNKING RULE - Large deployments (EKS + RDS + monitoring = 20+ files) done incrementally.** Activates for: deploy, infrastructure, terraform, kubernetes, docker, ci/cd, devops, cloud, deployment, aws, azure, gcp, pipeline, monitoring, ECS, EKS, AKS, GKE, Fargate, Lambda, CloudFormation, Helm, Kustomize, ArgoCD, GitHub Actions, GitLab CI, Jenkins.
4
4
  tools: Read, Write, Edit, Bash
5
- model: claude-sonnet-4-5-20250929
6
- model_preference: haiku
5
+ model: claude-opus-4-5-20251101
6
+ model_preference: opus
7
7
  cost_profile: execution
8
8
  fallback_behavior: flexible
9
9
  max_response_tokens: 2000
@@ -53,35 +53,18 @@ VERCEL_TOKEN=your-vercel-token-here
53
53
  # ============================================================================
54
54
 
55
55
  # ----------------------------------------------------------------------------
56
- # GitHub - 3 Strategies for Team Organization
56
+ # GitHub
57
57
  # ----------------------------------------------------------------------------
58
+ # RECOMMENDED: Use gh CLI instead of tokens
59
+ # gh auth login
60
+ #
61
+ # Only use GITHUB_TOKEN if gh CLI is not available (e.g., CI/CD)
58
62
  # Guide: https://github.com/settings/tokens
59
- # Scopes: repo, read:org, workflow
63
+ # Scopes: repo, read:org
60
64
 
61
- # Required for all strategies:
62
- GH_TOKEN=ghp_your-github-token-40-chars
63
-
64
- # Strategy 1: Repository-per-team (MOST COMMON)
65
- # Use when: Each team has separate repositories (microservices, multi-repo)
66
- # Example: Frontend team → frontend-app, Backend team → backend-api
67
- GITHUB_STRATEGY=repository-per-team
68
- GITHUB_OWNER=myorg
69
- GITHUB_REPOS=frontend-app,backend-api,mobile-app,qa-tools
70
-
71
- # Strategy 2: Team-based (MONOREPO)
72
- # Use when: Single repository with team-based filtering
73
- # Example: Monorepo with teams working on different parts
74
- # GITHUB_STRATEGY=team-based
75
- # GITHUB_OWNER=myorg
76
- # GITHUB_REPO=main-product
77
- # GITHUB_TEAMS=frontend-team,backend-team,mobile-team,qa-team
78
-
79
- # Strategy 3: Team-multi-repo (COMPLEX)
80
- # Use when: Teams own multiple repositories (platform teams)
81
- # Example: Platform team owns api-gateway + auth-service
82
- # GITHUB_STRATEGY=team-multi-repo
83
- # GITHUB_OWNER=myorg
84
- # GITHUB_TEAM_REPO_MAPPING='{"platform-team":["api-gateway","auth-service"],"frontend-team":["web-app","mobile-app"]}'
65
+ GITHUB_TOKEN=ghp_your-github-token-40-chars
66
+
67
+ # All other GitHub configuration is in .specweave/config.json
85
68
 
86
69
  # ----------------------------------------------------------------------------
87
70
  # Jira - 3 Strategies for Team Organization