specweave 1.0.350 → 1.0.352
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/bin/specweave.js +9 -0
- package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts +5 -0
- package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-client-v2.js +61 -23
- package/dist/plugins/specweave-ado/lib/ado-client-v2.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +3 -2
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-profile-resolver.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js +2 -1
- package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +25 -9
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/conflict-resolver.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
- package/dist/plugins/specweave-ado/lib/conflict-resolver.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +3 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/per-us-sync.js +14 -1
- package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +10 -7
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.js +7 -5
- package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js +13 -3
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts +24 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +36 -20
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +4 -2
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +38 -9
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts +1 -0
- package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-graphql-client.js +32 -22
- package/dist/plugins/specweave-github/lib/github-graphql-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +144 -8
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +8 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.js +94 -24
- package/dist/plugins/specweave-github/lib/github-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts +1 -0
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -1
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +3 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/per-us-sync.js +29 -9
- package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts +59 -0
- package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/content-format-adapter.js +159 -0
- package/dist/plugins/specweave-jira/lib/content-format-adapter.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts +45 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js +92 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +13 -28
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +2 -1
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +19 -7
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts +47 -0
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.js +110 -0
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts +26 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.js +77 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
- package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +17 -2
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +103 -33
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +4 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +19 -6
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts +29 -0
- package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/metadata-paths.js +73 -0
- package/dist/plugins/specweave-jira/lib/metadata-paths.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts +15 -2
- package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/reorganization-detector.js +121 -33
- package/dist/plugins/specweave-jira/lib/reorganization-detector.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +23 -18
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/sync-progress.d.ts +6 -0
- package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-progress.js +37 -0
- package/dist/src/cli/commands/sync-progress.js.map +1 -1
- package/dist/src/cli/commands/sync-task.d.ts +16 -0
- package/dist/src/cli/commands/sync-task.d.ts.map +1 -0
- package/dist/src/cli/commands/sync-task.js +42 -0
- package/dist/src/cli/commands/sync-task.js.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js +3 -3
- package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -1
- package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts +9 -1
- package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts.map +1 -1
- package/dist/src/core/hooks/LifecycleHookDispatcher.js +26 -8
- package/dist/src/core/hooks/LifecycleHookDispatcher.js.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts +13 -0
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +144 -17
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.js +2 -1
- package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
- package/dist/src/core/increment/status-commands.d.ts.map +1 -1
- package/dist/src/core/increment/status-commands.js +33 -11
- package/dist/src/core/increment/status-commands.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +2 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/locales/de/cli.json +252 -77
- package/dist/src/locales/en/cli.json +7 -0
- package/dist/src/locales/es/cli.json +245 -3
- package/dist/src/locales/fr/cli.json +259 -84
- package/dist/src/locales/ja/cli.json +253 -78
- package/dist/src/locales/ko/cli.json +253 -78
- package/dist/src/locales/pt/cli.json +252 -77
- package/dist/src/locales/ru/cli.json +17 -3
- package/dist/src/locales/zh/cli.json +258 -83
- package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
- package/dist/src/sync/ado-reconciler.js +5 -1
- package/dist/src/sync/ado-reconciler.js.map +1 -1
- package/dist/src/sync/base-reconciler.d.ts.map +1 -1
- package/dist/src/sync/base-reconciler.js +6 -1
- package/dist/src/sync/base-reconciler.js.map +1 -1
- package/dist/src/sync/config.d.ts +4 -0
- package/dist/src/sync/config.d.ts.map +1 -1
- package/dist/src/sync/config.js +6 -4
- package/dist/src/sync/config.js.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.d.ts +3 -0
- package/dist/src/sync/external-issue-auto-creator.d.ts.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.js +53 -17
- package/dist/src/sync/external-issue-auto-creator.js.map +1 -1
- package/dist/src/sync/external-item-sync-service.d.ts +9 -0
- package/dist/src/sync/external-item-sync-service.d.ts.map +1 -1
- package/dist/src/sync/external-item-sync-service.js +210 -9
- package/dist/src/sync/external-item-sync-service.js.map +1 -1
- package/dist/src/sync/github-reconciler.d.ts +30 -0
- package/dist/src/sync/github-reconciler.d.ts.map +1 -1
- package/dist/src/sync/github-reconciler.js +242 -3
- package/dist/src/sync/github-reconciler.js.map +1 -1
- package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
- package/dist/src/sync/jira-reconciler.js +5 -1
- package/dist/src/sync/jira-reconciler.js.map +1 -1
- package/dist/src/sync/provider-router.d.ts.map +1 -1
- package/dist/src/sync/provider-router.js +2 -1
- package/dist/src/sync/provider-router.js.map +1 -1
- package/dist/src/sync/providers/ado.d.ts +4 -0
- package/dist/src/sync/providers/ado.d.ts.map +1 -1
- package/dist/src/sync/providers/ado.js +36 -11
- package/dist/src/sync/providers/ado.js.map +1 -1
- package/dist/src/sync/providers/github.d.ts.map +1 -1
- package/dist/src/sync/providers/github.js +48 -35
- package/dist/src/sync/providers/github.js.map +1 -1
- package/dist/src/sync/providers/jira.d.ts.map +1 -1
- package/dist/src/sync/providers/jira.js +42 -26
- package/dist/src/sync/providers/jira.js.map +1 -1
- package/dist/src/sync/status-mapper.d.ts +3 -1
- package/dist/src/sync/status-mapper.d.ts.map +1 -1
- package/dist/src/sync/status-mapper.js +10 -2
- package/dist/src/sync/status-mapper.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +29 -19
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +31 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +13 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +144 -17
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +30 -0
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js +242 -3
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
- package/plugins/specweave/skills/architect/SKILL.md +2 -0
- package/plugins/specweave/skills/grill/SKILL.md +2 -0
- package/plugins/specweave/skills/team-lead/SKILL.md +43 -320
- package/plugins/specweave/skills/team-lead/agents/backend.md +60 -0
- package/plugins/specweave/skills/team-lead/agents/database.md +51 -0
- package/plugins/specweave/skills/team-lead/agents/frontend.md +61 -0
- package/plugins/specweave/skills/team-lead/agents/security.md +52 -0
- package/plugins/specweave/skills/team-lead/agents/testing.md +57 -0
- package/plugins/specweave/skills/test-aware-planner/SKILL.md +2 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh +2 -2
- package/plugins/specweave-ado/lib/ado-client-v2.js +51 -21
- package/plugins/specweave-ado/lib/ado-client-v2.ts +62 -23
- package/plugins/specweave-ado/lib/ado-duplicate-detector.js +4 -4
- package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +4 -3
- package/plugins/specweave-ado/lib/ado-hierarchical-sync.js +54 -12
- package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +88 -18
- package/plugins/specweave-ado/lib/ado-profile-resolver.js +1 -1
- package/plugins/specweave-ado/lib/ado-profile-resolver.ts +3 -1
- package/plugins/specweave-ado/lib/ado-spec-sync.js +22 -9
- package/plugins/specweave-ado/lib/ado-spec-sync.ts +27 -9
- package/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
- package/plugins/specweave-ado/lib/conflict-resolver.ts +17 -1
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +11 -1
- package/plugins/specweave-ado/lib/per-us-sync.js +8 -1
- package/plugins/specweave-ado/lib/per-us-sync.ts +17 -2
- package/plugins/specweave-github/hooks/github-auto-create-handler.sh +28 -2
- package/plugins/specweave-github/hooks/post-task-completion.sh +6 -3
- package/plugins/specweave-github/lib/enhanced-github-sync.js +35 -6
- package/plugins/specweave-github/lib/github-board-resolver.js +4 -4
- package/plugins/specweave-github/lib/github-board-resolver.ts +4 -4
- package/plugins/specweave-github/lib/github-client-v2.js +6 -6
- package/plugins/specweave-github/lib/github-client-v2.ts +11 -7
- package/plugins/specweave-github/lib/github-client.js +5 -4
- package/plugins/specweave-github/lib/github-client.ts +7 -5
- package/plugins/specweave-github/lib/github-cross-repo-sync.js +17 -3
- package/plugins/specweave-github/lib/github-cross-repo-sync.ts +16 -3
- package/plugins/specweave-github/lib/github-feature-sync-cli.js +20 -11
- package/plugins/specweave-github/lib/github-feature-sync-cli.ts +42 -20
- package/plugins/specweave-github/lib/github-feature-sync.js +32 -8
- package/plugins/specweave-github/lib/github-feature-sync.ts +41 -9
- package/plugins/specweave-github/lib/github-graphql-client.js +29 -20
- package/plugins/specweave-github/lib/github-graphql-client.ts +34 -22
- package/plugins/specweave-github/lib/github-hierarchical-sync.js +2 -2
- package/plugins/specweave-github/lib/github-hierarchical-sync.ts +2 -2
- package/plugins/specweave-github/lib/github-multi-project-sync.js +23 -7
- package/plugins/specweave-github/lib/github-multi-project-sync.ts +26 -8
- package/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +110 -5
- package/plugins/specweave-github/lib/github-spec-frontmatter-updater.ts +135 -9
- package/plugins/specweave-github/lib/github-spec-sync.js +85 -24
- package/plugins/specweave-github/lib/github-spec-sync.ts +100 -26
- package/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
- package/plugins/specweave-github/lib/github-sync-orchestrator.ts +3 -1
- package/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
- package/plugins/specweave-github/lib/github-us-auto-closer.ts +43 -0
- package/plugins/specweave-github/lib/per-us-sync.js +26 -11
- package/plugins/specweave-github/lib/per-us-sync.ts +29 -11
- package/plugins/specweave-jira/hooks/post-task-completion.sh +2 -1
- package/plugins/specweave-jira/lib/content-format-adapter.js +116 -0
- package/plugins/specweave-jira/lib/content-format-adapter.ts +189 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +21 -5
- package/plugins/specweave-jira/lib/jira-deployment-detector.js +63 -0
- package/plugins/specweave-jira/lib/jira-deployment-detector.ts +113 -0
- package/plugins/specweave-jira/lib/jira-duplicate-detector.js +12 -29
- package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +13 -27
- package/plugins/specweave-jira/lib/jira-epic-sync.js +15 -5
- package/plugins/specweave-jira/lib/jira-epic-sync.ts +22 -7
- package/plugins/specweave-jira/lib/jira-field-discovery.js +76 -0
- package/plugins/specweave-jira/lib/jira-field-discovery.ts +139 -0
- package/plugins/specweave-jira/lib/jira-hierarchical-sync.js +10 -0
- package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +11 -0
- package/plugins/specweave-jira/lib/jira-multi-project-sync.js +19 -9
- package/plugins/specweave-jira/lib/jira-multi-project-sync.ts +25 -14
- package/plugins/specweave-jira/lib/jira-paginated-search.js +55 -0
- package/plugins/specweave-jira/lib/jira-paginated-search.ts +108 -0
- package/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
- package/plugins/specweave-jira/lib/jira-spec-commit-sync.ts +5 -3
- package/plugins/specweave-jira/lib/jira-spec-sync.js +102 -31
- package/plugins/specweave-jira/lib/jira-spec-sync.ts +123 -45
- package/plugins/specweave-jira/lib/jira-status-sync.js +18 -5
- package/plugins/specweave-jira/lib/jira-status-sync.ts +21 -6
- package/plugins/specweave-jira/lib/metadata-paths.js +38 -0
- package/plugins/specweave-jira/lib/metadata-paths.ts +73 -0
- package/plugins/specweave-jira/lib/reorganization-detector.js +101 -23
- package/plugins/specweave-jira/lib/reorganization-detector.ts +125 -35
- package/plugins/specweave-jira/scripts/refresh-cache.js +1 -1
- package/plugins/specweave-jira/scripts/refresh-cache.ts +2 -2
- package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +3 -5
|
@@ -163,7 +163,8 @@ class AdoSpecSync {
|
|
|
163
163
|
value: this.mapPriorityToAdo(spec.metadata.priority)
|
|
164
164
|
}
|
|
165
165
|
];
|
|
166
|
-
const
|
|
166
|
+
const encodedType = encodeURIComponent(workItemType);
|
|
167
|
+
const response = await this.client.post(`/wit/workitems/$${encodedType}?api-version=7.0`, payload);
|
|
167
168
|
const featureData = response.data;
|
|
168
169
|
console.log(` \u2705 Created ADO Feature #${featureData.id}: ${featureData._links.html.href}`);
|
|
169
170
|
return {
|
|
@@ -173,29 +174,41 @@ class AdoSpecSync {
|
|
|
173
174
|
};
|
|
174
175
|
}
|
|
175
176
|
/**
|
|
176
|
-
* Update existing ADO Feature
|
|
177
|
+
* Update existing ADO Feature (conditional — only writes changed fields)
|
|
177
178
|
*/
|
|
178
179
|
async updateAdoFeature(featureId, spec) {
|
|
179
180
|
const featureTitle = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
|
|
180
181
|
const featureDescription = this.generateFeatureDescription(spec);
|
|
181
|
-
const
|
|
182
|
-
|
|
182
|
+
const current = await this.fetchAdoFeature(featureId);
|
|
183
|
+
const payload = [];
|
|
184
|
+
if (current.fields["System.Title"] !== featureTitle) {
|
|
185
|
+
payload.push({
|
|
183
186
|
op: "replace",
|
|
184
187
|
path: "/fields/System.Title",
|
|
185
188
|
value: featureTitle
|
|
186
|
-
}
|
|
187
|
-
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
console.log(` \u2139\uFE0F Title unchanged, skipping`);
|
|
192
|
+
}
|
|
193
|
+
if (current.fields["System.Description"] !== featureDescription) {
|
|
194
|
+
payload.push({
|
|
188
195
|
op: "replace",
|
|
189
196
|
path: "/fields/System.Description",
|
|
190
197
|
value: featureDescription
|
|
191
|
-
}
|
|
192
|
-
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
console.log(` \u2139\uFE0F Description unchanged, skipping`);
|
|
201
|
+
}
|
|
202
|
+
if (payload.length === 0) {
|
|
203
|
+
console.log(` \u2139\uFE0F No changes detected for ADO Feature #${featureId}`);
|
|
204
|
+
return current;
|
|
205
|
+
}
|
|
193
206
|
const response = await this.client.patch(
|
|
194
207
|
`/wit/workitems/${featureId}?api-version=7.0`,
|
|
195
208
|
payload
|
|
196
209
|
);
|
|
197
210
|
const featureData = response.data;
|
|
198
|
-
console.log(` \u2705 Updated ADO Feature #${featureId}`);
|
|
211
|
+
console.log(` \u2705 Updated ADO Feature #${featureId} (${payload.length} field(s) changed)`);
|
|
199
212
|
return {
|
|
200
213
|
id: featureData.id,
|
|
201
214
|
url: featureData._links.html.href,
|
|
@@ -255,7 +255,8 @@ export class AdoSpecSync {
|
|
|
255
255
|
}
|
|
256
256
|
];
|
|
257
257
|
|
|
258
|
-
const
|
|
258
|
+
const encodedType = encodeURIComponent(workItemType);
|
|
259
|
+
const response = await this.client.post(`/wit/workitems/$${encodedType}?api-version=7.0`, payload);
|
|
259
260
|
const featureData = response.data;
|
|
260
261
|
|
|
261
262
|
console.log(` ✅ Created ADO Feature #${featureData.id}: ${featureData._links.html.href}`);
|
|
@@ -268,24 +269,41 @@ export class AdoSpecSync {
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
/**
|
|
271
|
-
* Update existing ADO Feature
|
|
272
|
+
* Update existing ADO Feature (conditional — only writes changed fields)
|
|
272
273
|
*/
|
|
273
274
|
private async updateAdoFeature(featureId: number, spec: SpecContent): Promise<AdoFeature> {
|
|
274
275
|
const featureTitle = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
|
|
275
276
|
const featureDescription = this.generateFeatureDescription(spec);
|
|
276
277
|
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
// Fetch current values to avoid overwriting ADO-side edits
|
|
279
|
+
const current = await this.fetchAdoFeature(featureId);
|
|
280
|
+
|
|
281
|
+
const payload: any[] = [];
|
|
282
|
+
|
|
283
|
+
if (current.fields['System.Title'] !== featureTitle) {
|
|
284
|
+
payload.push({
|
|
279
285
|
op: 'replace',
|
|
280
286
|
path: '/fields/System.Title',
|
|
281
287
|
value: featureTitle
|
|
282
|
-
}
|
|
283
|
-
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
console.log(` ℹ️ Title unchanged, skipping`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (current.fields['System.Description'] !== featureDescription) {
|
|
294
|
+
payload.push({
|
|
284
295
|
op: 'replace',
|
|
285
296
|
path: '/fields/System.Description',
|
|
286
297
|
value: featureDescription
|
|
287
|
-
}
|
|
288
|
-
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
console.log(` ℹ️ Description unchanged, skipping`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (payload.length === 0) {
|
|
304
|
+
console.log(` ℹ️ No changes detected for ADO Feature #${featureId}`);
|
|
305
|
+
return current;
|
|
306
|
+
}
|
|
289
307
|
|
|
290
308
|
const response = await this.client.patch(
|
|
291
309
|
`/wit/workitems/${featureId}?api-version=7.0`,
|
|
@@ -293,7 +311,7 @@ export class AdoSpecSync {
|
|
|
293
311
|
);
|
|
294
312
|
const featureData = response.data;
|
|
295
313
|
|
|
296
|
-
console.log(` ✅ Updated ADO Feature #${featureId}`);
|
|
314
|
+
console.log(` ✅ Updated ADO Feature #${featureId} (${payload.length} field(s) changed)`);
|
|
297
315
|
|
|
298
316
|
return {
|
|
299
317
|
id: featureData.id,
|
|
@@ -3,6 +3,7 @@ import * as path from "path";
|
|
|
3
3
|
import * as yaml from "yaml";
|
|
4
4
|
const STATUS_MAPPING = {
|
|
5
5
|
ado: {
|
|
6
|
+
// Agile process template
|
|
6
7
|
"New": "draft",
|
|
7
8
|
"Active": "in-progress",
|
|
8
9
|
"Resolved": "implemented",
|
|
@@ -10,7 +11,20 @@ const STATUS_MAPPING = {
|
|
|
10
11
|
"In Review": "in-qa",
|
|
11
12
|
"In QA": "in-qa",
|
|
12
13
|
"Blocked": "blocked",
|
|
13
|
-
"Removed": "cancelled"
|
|
14
|
+
"Removed": "cancelled",
|
|
15
|
+
// Scrum process template
|
|
16
|
+
"Approved": "draft",
|
|
17
|
+
"Committed": "in-progress",
|
|
18
|
+
"Done": "complete",
|
|
19
|
+
// CMMI process template
|
|
20
|
+
"Proposed": "draft",
|
|
21
|
+
// 'Active' already mapped above (shared with CMMI)
|
|
22
|
+
// 'Resolved' already mapped above (shared with CMMI)
|
|
23
|
+
// 'Closed' already mapped above (shared with CMMI)
|
|
24
|
+
// Basic process template
|
|
25
|
+
"To Do": "draft",
|
|
26
|
+
"Doing": "in-progress"
|
|
27
|
+
// 'Done' already mapped above (shared with Basic)
|
|
14
28
|
},
|
|
15
29
|
jira: {
|
|
16
30
|
"To Do": "draft",
|
|
@@ -30,6 +44,8 @@ const STATUS_MAPPING = {
|
|
|
30
44
|
}
|
|
31
45
|
};
|
|
32
46
|
const REVERSE_STATUS_MAPPING = {
|
|
47
|
+
// Default reverse mapping uses Agile states (most common)
|
|
48
|
+
// Callers should use process-template-aware mapping when template is known
|
|
33
49
|
ado: {
|
|
34
50
|
"draft": "New",
|
|
35
51
|
"in-progress": "Active",
|
|
@@ -87,6 +87,7 @@ export interface ExternalStatus {
|
|
|
87
87
|
|
|
88
88
|
const STATUS_MAPPING = {
|
|
89
89
|
ado: {
|
|
90
|
+
// Agile process template
|
|
90
91
|
'New': 'draft' as SpecStatus,
|
|
91
92
|
'Active': 'in-progress' as SpecStatus,
|
|
92
93
|
'Resolved': 'implemented' as SpecStatus,
|
|
@@ -94,7 +95,20 @@ const STATUS_MAPPING = {
|
|
|
94
95
|
'In Review': 'in-qa' as SpecStatus,
|
|
95
96
|
'In QA': 'in-qa' as SpecStatus,
|
|
96
97
|
'Blocked': 'blocked' as SpecStatus,
|
|
97
|
-
'Removed': 'cancelled' as SpecStatus
|
|
98
|
+
'Removed': 'cancelled' as SpecStatus,
|
|
99
|
+
// Scrum process template
|
|
100
|
+
'Approved': 'draft' as SpecStatus,
|
|
101
|
+
'Committed': 'in-progress' as SpecStatus,
|
|
102
|
+
'Done': 'complete' as SpecStatus,
|
|
103
|
+
// CMMI process template
|
|
104
|
+
'Proposed': 'draft' as SpecStatus,
|
|
105
|
+
// 'Active' already mapped above (shared with CMMI)
|
|
106
|
+
// 'Resolved' already mapped above (shared with CMMI)
|
|
107
|
+
// 'Closed' already mapped above (shared with CMMI)
|
|
108
|
+
// Basic process template
|
|
109
|
+
'To Do': 'draft' as SpecStatus,
|
|
110
|
+
'Doing': 'in-progress' as SpecStatus,
|
|
111
|
+
// 'Done' already mapped above (shared with Basic)
|
|
98
112
|
},
|
|
99
113
|
jira: {
|
|
100
114
|
'To Do': 'draft' as SpecStatus,
|
|
@@ -115,6 +129,8 @@ const STATUS_MAPPING = {
|
|
|
115
129
|
};
|
|
116
130
|
|
|
117
131
|
const REVERSE_STATUS_MAPPING = {
|
|
132
|
+
// Default reverse mapping uses Agile states (most common)
|
|
133
|
+
// Callers should use process-template-aware mapping when template is known
|
|
118
134
|
ado: {
|
|
119
135
|
'draft': 'New',
|
|
120
136
|
'in-progress': 'Active',
|
|
@@ -130,10 +130,20 @@ function buildTaskMapping(increments, organization, project) {
|
|
|
130
130
|
title: task.title,
|
|
131
131
|
userStories: task.userStories
|
|
132
132
|
}));
|
|
133
|
+
// Derive repository name from git remote or fall back to project name
|
|
134
|
+
let repoName = project;
|
|
135
|
+
try {
|
|
136
|
+
const { execSync } = require("child_process");
|
|
137
|
+
const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
138
|
+
const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
|
|
139
|
+
if (match) repoName = match[1];
|
|
140
|
+
} catch {
|
|
141
|
+
// Fallback to project name if git is unavailable
|
|
142
|
+
}
|
|
133
143
|
return {
|
|
134
144
|
incrementId: firstIncrement.id,
|
|
135
145
|
tasks,
|
|
136
|
-
tasksUrl: `https://dev.azure.com/${organization}/${project}/_git
|
|
146
|
+
tasksUrl: `https://dev.azure.com/${organization}/${project}/_git/${repoName}?path=/.specweave/increments/${firstIncrement.id}/tasks.md`
|
|
137
147
|
};
|
|
138
148
|
}
|
|
139
149
|
async function findArchitectureDocs(rootDir, specId) {
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { consoleLogger } from "../../../src/utils/logger.js";
|
|
2
|
+
const WORK_ITEM_TYPE_BY_TEMPLATE = {
|
|
3
|
+
agile: "User Story",
|
|
4
|
+
scrum: "Product Backlog Item",
|
|
5
|
+
cmmi: "Requirement",
|
|
6
|
+
basic: "Issue"
|
|
7
|
+
};
|
|
2
8
|
class PerUSAdoSync {
|
|
3
9
|
constructor(adoClient, projectMappings, options = {}) {
|
|
4
10
|
this.adoClient = adoClient;
|
|
5
11
|
this.projectMappings = projectMappings;
|
|
6
12
|
this.logger = options.logger ?? consoleLogger;
|
|
13
|
+
this.workItemType = options.workItemType ?? WORK_ITEM_TYPE_BY_TEMPLATE[options.processTemplate?.toLowerCase() ?? ""] ?? "User Story";
|
|
7
14
|
}
|
|
8
15
|
/**
|
|
9
16
|
* Sync all user stories to their respective ADO projects
|
|
@@ -133,7 +140,7 @@ class PerUSAdoSync {
|
|
|
133
140
|
} else {
|
|
134
141
|
const newItem = await this.adoClient.createWorkItem(
|
|
135
142
|
mapping.project,
|
|
136
|
-
|
|
143
|
+
this.workItemType,
|
|
137
144
|
title,
|
|
138
145
|
description,
|
|
139
146
|
areaPath
|
|
@@ -81,6 +81,16 @@ export interface AdoClient {
|
|
|
81
81
|
getWorkItemUrl(project: string, workItemId: number): string;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Default work item type by process template
|
|
86
|
+
*/
|
|
87
|
+
const WORK_ITEM_TYPE_BY_TEMPLATE: Record<string, string> = {
|
|
88
|
+
agile: 'User Story',
|
|
89
|
+
scrum: 'Product Backlog Item',
|
|
90
|
+
cmmi: 'Requirement',
|
|
91
|
+
basic: 'Issue',
|
|
92
|
+
};
|
|
93
|
+
|
|
84
94
|
/**
|
|
85
95
|
* Per-US ADO Sync
|
|
86
96
|
*
|
|
@@ -91,15 +101,20 @@ export class PerUSAdoSync {
|
|
|
91
101
|
private projectMappings: ProjectMappings;
|
|
92
102
|
private adoClient: AdoClient;
|
|
93
103
|
private logger: Logger;
|
|
104
|
+
private workItemType: string;
|
|
94
105
|
|
|
95
106
|
constructor(
|
|
96
107
|
adoClient: AdoClient,
|
|
97
108
|
projectMappings: ProjectMappings,
|
|
98
|
-
options: { logger?: Logger } = {}
|
|
109
|
+
options: { logger?: Logger; workItemType?: string; processTemplate?: string } = {}
|
|
99
110
|
) {
|
|
100
111
|
this.adoClient = adoClient;
|
|
101
112
|
this.projectMappings = projectMappings;
|
|
102
113
|
this.logger = options.logger ?? consoleLogger;
|
|
114
|
+
// Explicit workItemType takes priority, then process template lookup, then default
|
|
115
|
+
this.workItemType = options.workItemType
|
|
116
|
+
?? WORK_ITEM_TYPE_BY_TEMPLATE[options.processTemplate?.toLowerCase() ?? '']
|
|
117
|
+
?? 'User Story';
|
|
103
118
|
}
|
|
104
119
|
|
|
105
120
|
/**
|
|
@@ -268,7 +283,7 @@ export class PerUSAdoSync {
|
|
|
268
283
|
// Create new work item
|
|
269
284
|
const newItem = await this.adoClient.createWorkItem(
|
|
270
285
|
mapping.project,
|
|
271
|
-
|
|
286
|
+
this.workItemType,
|
|
272
287
|
title,
|
|
273
288
|
description,
|
|
274
289
|
areaPath
|
|
@@ -74,14 +74,38 @@ else
|
|
|
74
74
|
[[ "$GH_ENABLED" != "true" ]] && { log "GitHub sync not enabled"; exit 0; }
|
|
75
75
|
fi
|
|
76
76
|
|
|
77
|
-
# Check auto-create enabled
|
|
77
|
+
# Check auto-create enabled
|
|
78
|
+
# Two flags control auto-creation behavior:
|
|
79
|
+
# - sync.autoSync (boolean): Global auto-sync toggle. When true, enables ALL auto-sync
|
|
80
|
+
# operations including issue creation, progress sync, and status updates.
|
|
81
|
+
# - hooks.post_increment_planning.auto_create_github_issue (boolean): Fine-grained toggle
|
|
82
|
+
# for ONLY the auto-create-issue behavior in post-increment-planning hooks.
|
|
83
|
+
#
|
|
84
|
+
# Precedence: auto_create_github_issue (specific) > autoSync (global)
|
|
85
|
+
# If BOTH are set, auto_create_github_issue takes precedence for issue creation.
|
|
86
|
+
# If ONLY autoSync is true, issue creation is enabled (as part of the global auto-sync).
|
|
78
87
|
AUTO_SYNC=$(jq -r '.sync.autoSync // false' "$CONFIG_PATH" 2>/dev/null)
|
|
79
88
|
AUTO_CREATE=$(jq -r '.hooks.post_increment_planning.auto_create_github_issue // false' "$CONFIG_PATH" 2>/dev/null)
|
|
89
|
+
|
|
90
|
+
# Warn if only one flag is set (likely misconfiguration)
|
|
91
|
+
if [[ "$AUTO_SYNC" == "true" ]] && [[ "$AUTO_CREATE" == "false" ]]; then
|
|
92
|
+
log "Warning: sync.autoSync=true but auto_create_github_issue=false. Issue creation enabled via autoSync. Set auto_create_github_issue=true to be explicit."
|
|
93
|
+
elif [[ "$AUTO_SYNC" == "false" ]] && [[ "$AUTO_CREATE" == "true" ]]; then
|
|
94
|
+
log "Warning: auto_create_github_issue=true but sync.autoSync=false. Only issue creation is enabled; other sync operations are disabled."
|
|
95
|
+
fi
|
|
96
|
+
|
|
80
97
|
if [[ "$AUTO_SYNC" != "true" ]] && [[ "$AUTO_CREATE" != "true" ]]; then
|
|
81
98
|
log "Auto-sync disabled (autoSync=$AUTO_SYNC, auto_create=$AUTO_CREATE)"
|
|
82
99
|
exit 0
|
|
83
100
|
fi
|
|
84
101
|
|
|
102
|
+
# Log which flag triggered activation
|
|
103
|
+
if [[ "$AUTO_CREATE" == "true" ]]; then
|
|
104
|
+
log "Auto-create enabled via hooks.post_increment_planning.auto_create_github_issue"
|
|
105
|
+
else
|
|
106
|
+
log "Auto-create enabled via sync.autoSync (global toggle)"
|
|
107
|
+
fi
|
|
108
|
+
|
|
85
109
|
# Get owner/repo
|
|
86
110
|
OWNER=$(jq -r '.sync.github.owner // ""' "$CONFIG_PATH" 2>/dev/null)
|
|
87
111
|
REPO=$(jq -r '.sync.github.repo // ""' "$CONFIG_PATH" 2>/dev/null)
|
|
@@ -115,7 +139,9 @@ fi
|
|
|
115
139
|
if [[ "${SPECWEAVE_SKIP_DEBOUNCE:-0}" != "1" ]]; then
|
|
116
140
|
DEBOUNCE_FILE="$STATE_DIR/.github-auto-create-pending-$INC_ID"
|
|
117
141
|
if [[ -f "$DEBOUNCE_FILE" ]]; then
|
|
118
|
-
|
|
142
|
+
# POSIX-portable: use perl for mtime (works on macOS and Linux)
|
|
143
|
+
FILE_MTIME=$(perl -e 'print((stat($ARGV[0]))[9])' "$DEBOUNCE_FILE" 2>/dev/null || echo 0)
|
|
144
|
+
SIGNAL_AGE=$(($(date +%s) - FILE_MTIME))
|
|
119
145
|
if (( SIGNAL_AGE < 30 )); then
|
|
120
146
|
log "Debounce: signal age ${SIGNAL_AGE}s < 30s. Deferring."
|
|
121
147
|
exit 0
|
|
@@ -80,10 +80,13 @@ for i in {1..15}; do
|
|
|
80
80
|
break
|
|
81
81
|
fi
|
|
82
82
|
|
|
83
|
-
# Check for stale lock
|
|
83
|
+
# Check for stale lock (POSIX-portable: works on macOS and Linux)
|
|
84
84
|
if [[ -d "$LOCK_FILE" ]]; then
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
# Use find -mmin which works on both macOS and Linux
|
|
86
|
+
LOCK_TIMEOUT_MIN=$(( (LOCK_TIMEOUT + 59) / 60 )) # Convert seconds to minutes (ceil)
|
|
87
|
+
[[ $LOCK_TIMEOUT_MIN -lt 1 ]] && LOCK_TIMEOUT_MIN=1
|
|
88
|
+
STALE=$(find "$LOCK_FILE" -maxdepth 0 -mmin +${LOCK_TIMEOUT_MIN} 2>/dev/null)
|
|
89
|
+
if [[ -n "$STALE" ]]; then
|
|
87
90
|
rmdir "$LOCK_FILE" 2>/dev/null || true
|
|
88
91
|
continue
|
|
89
92
|
fi
|
|
@@ -27,9 +27,10 @@ async function syncSpecWithEnhancedContent(options) {
|
|
|
27
27
|
console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
|
|
28
28
|
console.log(`\u{1F4CB} Mapped ${Object.keys(mapping.userStoryMappings).length} user stories to tasks`);
|
|
29
29
|
}
|
|
30
|
-
const
|
|
30
|
+
const defaultBranch = (owner && repo) ? await getDefaultBranch(owner, repo) : null;
|
|
31
|
+
const taskMapping = buildTaskMapping(mapping.increments, owner, repo, defaultBranch);
|
|
31
32
|
const architectureDocs = await findArchitectureDocs(rootDir, specId);
|
|
32
|
-
const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner, repo);
|
|
33
|
+
const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner, repo, defaultBranch);
|
|
33
34
|
const enhancedSpec = {
|
|
34
35
|
...baseSpec,
|
|
35
36
|
summary: baseSpec.description,
|
|
@@ -153,7 +154,33 @@ async function findSpecWeaveRoot(specPath) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
+
// Cache for default branch per repo per session
|
|
158
|
+
const defaultBranchCache = new Map();
|
|
159
|
+
|
|
160
|
+
async function getDefaultBranch(owner, repo) {
|
|
161
|
+
const cacheKey = `${owner}/${repo}`;
|
|
162
|
+
if (defaultBranchCache.has(cacheKey)) {
|
|
163
|
+
return defaultBranchCache.get(cacheKey);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const { execFileSync } = await import("child_process");
|
|
167
|
+
const result = execFileSync("gh", [
|
|
168
|
+
"repo", "view", `${owner}/${repo}`,
|
|
169
|
+
"--json", "defaultBranchRef",
|
|
170
|
+
"--jq", ".defaultBranchRef.name"
|
|
171
|
+
], { encoding: "utf-8", timeout: 10000 }).trim();
|
|
172
|
+
if (result) {
|
|
173
|
+
defaultBranchCache.set(cacheKey, result);
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
// On failure, return null — callers omit branch segment
|
|
178
|
+
}
|
|
179
|
+
defaultBranchCache.set(cacheKey, null);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function buildTaskMapping(increments, owner, repo, defaultBranch) {
|
|
157
184
|
if (increments.length === 0) return void 0;
|
|
158
185
|
const firstIncrement = increments[0];
|
|
159
186
|
const tasks = firstIncrement.tasks.map((task) => ({
|
|
@@ -162,10 +189,11 @@ function buildTaskMapping(increments, owner, repo) {
|
|
|
162
189
|
userStories: task.userStories,
|
|
163
190
|
githubIssue: task.githubIssue
|
|
164
191
|
}));
|
|
192
|
+
const branchSegment = defaultBranch ? `/blob/${defaultBranch}` : "";
|
|
165
193
|
return {
|
|
166
194
|
incrementId: firstIncrement.id,
|
|
167
195
|
tasks,
|
|
168
|
-
tasksUrl: `https://github.com/${owner}/${repo}
|
|
196
|
+
tasksUrl: `https://github.com/${owner}/${repo}${branchSegment}/.specweave/increments/${firstIncrement.id}/tasks.md`
|
|
169
197
|
};
|
|
170
198
|
}
|
|
171
199
|
async function findArchitectureDocs(rootDir, specId) {
|
|
@@ -198,9 +226,10 @@ async function findArchitectureDocs(rootDir, specId) {
|
|
|
198
226
|
}
|
|
199
227
|
return docs;
|
|
200
228
|
}
|
|
201
|
-
function buildSourceLinks(incrementId, owner, repo) {
|
|
229
|
+
function buildSourceLinks(incrementId, owner, repo, defaultBranch) {
|
|
202
230
|
if (!incrementId) return void 0;
|
|
203
|
-
const
|
|
231
|
+
const branchSegment = defaultBranch ? `/blob/${defaultBranch}` : "";
|
|
232
|
+
const baseUrl = `https://github.com/${owner}/${repo}${branchSegment}/.specweave`;
|
|
204
233
|
return {
|
|
205
234
|
spec: `${baseUrl}/docs/internal/specs/default/spec-${incrementId.replace(/^\d+-/, "")}.md`,
|
|
206
235
|
plan: `${baseUrl}/increments/${incrementId}/plan.md`,
|
|
@@ -15,9 +15,9 @@ async function fetchBoardsForRepo(owner, repo) {
|
|
|
15
15
|
"-H",
|
|
16
16
|
"Accept: application/vnd.github+json"
|
|
17
17
|
], { env: getGhEnv() });
|
|
18
|
-
if (result.
|
|
18
|
+
if (result.exitCode !== 0) {
|
|
19
19
|
console.error(`\u274C Failed to fetch boards for ${owner}/${repo}:`, result.stderr);
|
|
20
|
-
throw new Error(`GitHub API error
|
|
20
|
+
throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
|
|
21
21
|
}
|
|
22
22
|
const boards = result.stdout.trim().split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
23
23
|
console.log(`\u2705 Found ${boards.length} board(s) for repo ${owner}/${repo}`);
|
|
@@ -38,9 +38,9 @@ async function fetchBoardsForOrg(org) {
|
|
|
38
38
|
"-H",
|
|
39
39
|
"Accept: application/vnd.github+json"
|
|
40
40
|
], { env: getGhEnv() });
|
|
41
|
-
if (result.
|
|
41
|
+
if (result.exitCode !== 0) {
|
|
42
42
|
console.error(`\u274C Failed to fetch boards for org ${org}:`, result.stderr);
|
|
43
|
-
throw new Error(`GitHub API error
|
|
43
|
+
throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
|
|
44
44
|
}
|
|
45
45
|
const boards = result.stdout.trim().split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
46
46
|
console.log(`\u2705 Found ${boards.length} board(s) for org ${org}`);
|
|
@@ -54,9 +54,9 @@ export async function fetchBoardsForRepo(
|
|
|
54
54
|
'Accept: application/vnd.github+json',
|
|
55
55
|
], { env: getGhEnv() });
|
|
56
56
|
|
|
57
|
-
if (result.
|
|
57
|
+
if (result.exitCode !== 0) {
|
|
58
58
|
console.error(`❌ Failed to fetch boards for ${owner}/${repo}:`, result.stderr);
|
|
59
|
-
throw new Error(`GitHub API error
|
|
59
|
+
throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Parse JSONL (one JSON object per line)
|
|
@@ -98,9 +98,9 @@ export async function fetchBoardsForOrg(
|
|
|
98
98
|
'Accept: application/vnd.github+json',
|
|
99
99
|
], { env: getGhEnv() });
|
|
100
100
|
|
|
101
|
-
if (result.
|
|
101
|
+
if (result.exitCode !== 0) {
|
|
102
102
|
console.error(`❌ Failed to fetch boards for org ${org}:`, result.stderr);
|
|
103
|
-
throw new Error(`GitHub API error
|
|
103
|
+
throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// Parse JSONL
|
|
@@ -103,13 +103,14 @@ class GitHubClientV2 {
|
|
|
103
103
|
/**
|
|
104
104
|
* Create or get existing milestone
|
|
105
105
|
*/
|
|
106
|
-
async createOrGetMilestone(title, description, daysFromNow
|
|
106
|
+
async createOrGetMilestone(title, description, daysFromNow) {
|
|
107
|
+
const dueDays = daysFromNow ?? 2;
|
|
107
108
|
const existing = await this.getMilestoneByTitle(title);
|
|
108
109
|
if (existing) {
|
|
109
110
|
return existing;
|
|
110
111
|
}
|
|
111
112
|
const dueDate = /* @__PURE__ */ new Date();
|
|
112
|
-
dueDate.setDate(dueDate.getDate() +
|
|
113
|
+
dueDate.setDate(dueDate.getDate() + dueDays);
|
|
113
114
|
const dueDateISO = dueDate.toISOString();
|
|
114
115
|
const args = [
|
|
115
116
|
"api",
|
|
@@ -136,7 +137,7 @@ class GitHubClientV2 {
|
|
|
136
137
|
async getMilestoneByTitle(title) {
|
|
137
138
|
const result = await execFileNoThrow("gh", [
|
|
138
139
|
"api",
|
|
139
|
-
`repos/${this.fullRepo}/milestones`,
|
|
140
|
+
`repos/${this.fullRepo}/milestones?per_page=100&state=all`,
|
|
140
141
|
"--jq",
|
|
141
142
|
`.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}`
|
|
142
143
|
], { env: this.getGhEnv() });
|
|
@@ -477,10 +478,9 @@ ${body}`;
|
|
|
477
478
|
async getLastComment(issueNumber) {
|
|
478
479
|
const result = await execFileNoThrow("gh", [
|
|
479
480
|
"api",
|
|
480
|
-
`repos/${this.fullRepo}/issues/${issueNumber}/comments`,
|
|
481
|
+
`repos/${this.fullRepo}/issues/${issueNumber}/comments?sort=created&direction=desc&per_page=1`,
|
|
481
482
|
"--jq",
|
|
482
|
-
".[
|
|
483
|
-
// Get last comment only
|
|
483
|
+
".[0] | {body: .body, author: .user.login}"
|
|
484
484
|
], { env: this.getGhEnv() });
|
|
485
485
|
if (result.exitCode !== 0) {
|
|
486
486
|
return null;
|
|
@@ -148,17 +148,20 @@ export class GitHubClientV2 {
|
|
|
148
148
|
async createOrGetMilestone(
|
|
149
149
|
title: string,
|
|
150
150
|
description?: string,
|
|
151
|
-
daysFromNow
|
|
151
|
+
daysFromNow?: number
|
|
152
152
|
): Promise<GitHubMilestone> {
|
|
153
|
+
// Use configured value or default to 2 days
|
|
154
|
+
const dueDays = daysFromNow ?? 2;
|
|
155
|
+
|
|
153
156
|
// Check if milestone already exists
|
|
154
157
|
const existing = await this.getMilestoneByTitle(title);
|
|
155
158
|
if (existing) {
|
|
156
159
|
return existing;
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
// Calculate due date
|
|
162
|
+
// Calculate due date from creation timestamp
|
|
160
163
|
const dueDate = new Date();
|
|
161
|
-
dueDate.setDate(dueDate.getDate() +
|
|
164
|
+
dueDate.setDate(dueDate.getDate() + dueDays);
|
|
162
165
|
const dueDateISO = dueDate.toISOString();
|
|
163
166
|
|
|
164
167
|
// Build API request
|
|
@@ -194,7 +197,7 @@ export class GitHubClientV2 {
|
|
|
194
197
|
): Promise<GitHubMilestone | null> {
|
|
195
198
|
const result = await execFileNoThrow('gh', [
|
|
196
199
|
'api',
|
|
197
|
-
`repos/${this.fullRepo}/milestones`,
|
|
200
|
+
`repos/${this.fullRepo}/milestones?per_page=100&state=all`,
|
|
198
201
|
'--jq',
|
|
199
202
|
`.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}`,
|
|
200
203
|
], { env: this.getGhEnv() });
|
|
@@ -607,12 +610,13 @@ export class GitHubClientV2 {
|
|
|
607
610
|
* Returns the most recent comment body, or null if no comments exist
|
|
608
611
|
*/
|
|
609
612
|
async getLastComment(issueNumber: number): Promise<{body: string; author: string} | null> {
|
|
610
|
-
//
|
|
613
|
+
// Query the last comment directly using per_page=1 + page from last page
|
|
614
|
+
// sort=created&direction=desc gives newest first, per_page=1 returns just one
|
|
611
615
|
const result = await execFileNoThrow('gh', [
|
|
612
616
|
'api',
|
|
613
|
-
`repos/${this.fullRepo}/issues/${issueNumber}/comments`,
|
|
617
|
+
`repos/${this.fullRepo}/issues/${issueNumber}/comments?sort=created&direction=desc&per_page=1`,
|
|
614
618
|
'--jq',
|
|
615
|
-
'.[
|
|
619
|
+
'.[0] | {body: .body, author: .user.login}',
|
|
616
620
|
], { env: this.getGhEnv() });
|
|
617
621
|
|
|
618
622
|
if (result.exitCode !== 0) {
|
|
@@ -40,15 +40,16 @@ class GitHubClient {
|
|
|
40
40
|
*
|
|
41
41
|
* @param title Milestone title
|
|
42
42
|
* @param description Milestone description
|
|
43
|
-
* @param daysFromNow Days until milestone due (default: 2
|
|
43
|
+
* @param daysFromNow Days until milestone due (configurable via github.milestoneDueDays, default: 2)
|
|
44
44
|
*/
|
|
45
|
-
async createOrGetMilestone(title, description, daysFromNow
|
|
45
|
+
async createOrGetMilestone(title, description, daysFromNow) {
|
|
46
|
+
const dueDays = daysFromNow ?? 2;
|
|
46
47
|
const existing = await this.getMilestoneByTitle(title);
|
|
47
48
|
if (existing) {
|
|
48
49
|
return existing;
|
|
49
50
|
}
|
|
50
51
|
const dueDate = /* @__PURE__ */ new Date();
|
|
51
|
-
dueDate.setDate(dueDate.getDate() +
|
|
52
|
+
dueDate.setDate(dueDate.getDate() + dueDays);
|
|
52
53
|
const dueDateISO = dueDate.toISOString();
|
|
53
54
|
const cmd = `gh api repos/${this.repo}/milestones -f title="${title}" ${description ? `-f description="${description}"` : ""} -f due_on="${dueDateISO}" --jq '{number: .number, title: .title, description: .description, state: .state, due_on: .due_on}'`;
|
|
54
55
|
try {
|
|
@@ -63,7 +64,7 @@ class GitHubClient {
|
|
|
63
64
|
*/
|
|
64
65
|
async getMilestoneByTitle(title) {
|
|
65
66
|
try {
|
|
66
|
-
const cmd = `gh api repos/${this.repo}/milestones --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
|
|
67
|
+
const cmd = `gh api "repos/${this.repo}/milestones?per_page=100&state=all" --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
|
|
67
68
|
const output = execSync(cmd, { encoding: "utf-8" }).trim();
|
|
68
69
|
return output ? JSON.parse(output) : null;
|
|
69
70
|
} catch {
|
|
@@ -54,22 +54,24 @@ export class GitHubClient {
|
|
|
54
54
|
*
|
|
55
55
|
* @param title Milestone title
|
|
56
56
|
* @param description Milestone description
|
|
57
|
-
* @param daysFromNow Days until milestone due (default: 2
|
|
57
|
+
* @param daysFromNow Days until milestone due (configurable via github.milestoneDueDays, default: 2)
|
|
58
58
|
*/
|
|
59
59
|
async createOrGetMilestone(
|
|
60
60
|
title: string,
|
|
61
61
|
description?: string,
|
|
62
|
-
daysFromNow
|
|
62
|
+
daysFromNow?: number
|
|
63
63
|
): Promise<GitHubMilestone> {
|
|
64
|
+
// Use configured value or default to 2 days
|
|
65
|
+
const dueDays = daysFromNow ?? 2;
|
|
64
66
|
// Check if milestone already exists
|
|
65
67
|
const existing = await this.getMilestoneByTitle(title);
|
|
66
68
|
if (existing) {
|
|
67
69
|
return existing;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
// Calculate due date
|
|
72
|
+
// Calculate due date from creation timestamp
|
|
71
73
|
const dueDate = new Date();
|
|
72
|
-
dueDate.setDate(dueDate.getDate() +
|
|
74
|
+
dueDate.setDate(dueDate.getDate() + dueDays);
|
|
73
75
|
const dueDateISO = dueDate.toISOString();
|
|
74
76
|
|
|
75
77
|
// Create new milestone with due date
|
|
@@ -88,7 +90,7 @@ export class GitHubClient {
|
|
|
88
90
|
*/
|
|
89
91
|
private async getMilestoneByTitle(title: string): Promise<GitHubMilestone | null> {
|
|
90
92
|
try {
|
|
91
|
-
const cmd = `gh api repos/${this.repo}/milestones --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
|
|
93
|
+
const cmd = `gh api "repos/${this.repo}/milestones?per_page=100&state=all" --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
|
|
92
94
|
const output = execSync(cmd, { encoding: 'utf-8' }).trim();
|
|
93
95
|
return output ? JSON.parse(output) : null;
|
|
94
96
|
} catch {
|