specweave 1.0.351 → 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-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/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/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.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +26 -0
- package/dist/src/core/increment/metadata-manager.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/locales/de/cli.json +245 -77
- package/dist/src/locales/es/cli.json +238 -3
- package/dist/src/locales/fr/cli.json +254 -86
- package/dist/src/locales/ja/cli.json +246 -78
- package/dist/src/locales/ko/cli.json +246 -78
- package/dist/src/locales/pt/cli.json +245 -77
- package/dist/src/locales/zh/cli.json +251 -83
- 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 +235 -1
- package/dist/src/sync/github-reconciler.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.js +26 -0
- 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 +235 -1
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
- package/plugins/specweave/skills/team-lead/agents/security.md +0 -1
- 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-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
|
@@ -32,17 +32,17 @@ export class GitHubGraphQLClient {
|
|
|
32
32
|
* Tries user first, falls back to organization.
|
|
33
33
|
*/
|
|
34
34
|
async getOwnerNodeId(login: string): Promise<string> {
|
|
35
|
-
// Try user query first
|
|
36
|
-
const userQuery = `query { user(login:
|
|
37
|
-
const userResult = await this.executeGraphQL(userQuery);
|
|
35
|
+
// Try user query first (parameterized)
|
|
36
|
+
const userQuery = `query($login: String!) { user(login: $login) { id } }`;
|
|
37
|
+
const userResult = await this.executeGraphQL(userQuery, { login });
|
|
38
38
|
|
|
39
39
|
if (userResult.data?.user && (userResult.data.user as { id?: string }).id) {
|
|
40
40
|
return (userResult.data.user as { id: string }).id;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Fallback to organization
|
|
44
|
-
const orgQuery = `query { organization(login:
|
|
45
|
-
const orgResult = await this.executeGraphQL(orgQuery);
|
|
43
|
+
// Fallback to organization (parameterized)
|
|
44
|
+
const orgQuery = `query($login: String!) { organization(login: $login) { id } }`;
|
|
45
|
+
const orgResult = await this.executeGraphQL(orgQuery, { login });
|
|
46
46
|
|
|
47
47
|
if (orgResult.data?.organization && (orgResult.data.organization as { id?: string }).id) {
|
|
48
48
|
return (orgResult.data.organization as { id: string }).id;
|
|
@@ -55,13 +55,13 @@ export class GitHubGraphQLClient {
|
|
|
55
55
|
* Create a new GitHub Projects V2 board.
|
|
56
56
|
*/
|
|
57
57
|
async createProjectV2(ownerId: string, title: string): Promise<{ id: string; number: number }> {
|
|
58
|
-
const query = `mutation {
|
|
59
|
-
createProjectV2(input: { ownerId:
|
|
58
|
+
const query = `mutation($ownerId: ID!, $title: String!) {
|
|
59
|
+
createProjectV2(input: { ownerId: $ownerId, title: $title }) {
|
|
60
60
|
projectV2 { id number }
|
|
61
61
|
}
|
|
62
62
|
}`;
|
|
63
63
|
|
|
64
|
-
const result = await this.executeGraphQL(query);
|
|
64
|
+
const result = await this.executeGraphQL(query, { ownerId, title });
|
|
65
65
|
const project = (result.data?.createProjectV2 as { projectV2: { id: string; number: number } })?.projectV2;
|
|
66
66
|
return { id: project.id, number: project.number };
|
|
67
67
|
}
|
|
@@ -71,13 +71,13 @@ export class GitHubGraphQLClient {
|
|
|
71
71
|
* Returns the project item ID. Idempotent.
|
|
72
72
|
*/
|
|
73
73
|
async addProjectV2Item(projectId: string, contentId: string): Promise<string> {
|
|
74
|
-
const query = `mutation {
|
|
75
|
-
addProjectV2ItemById(input: { projectId:
|
|
74
|
+
const query = `mutation($projectId: ID!, $contentId: ID!) {
|
|
75
|
+
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
|
|
76
76
|
item { id }
|
|
77
77
|
}
|
|
78
78
|
}`;
|
|
79
79
|
|
|
80
|
-
const result = await this.executeGraphQL(query);
|
|
80
|
+
const result = await this.executeGraphQL(query, { projectId, contentId });
|
|
81
81
|
const item = (result.data?.addProjectV2ItemById as { item: { id: string } })?.item;
|
|
82
82
|
return item.id;
|
|
83
83
|
}
|
|
@@ -91,26 +91,31 @@ export class GitHubGraphQLClient {
|
|
|
91
91
|
fieldId: string,
|
|
92
92
|
value: { singleSelectOptionId: string },
|
|
93
93
|
): Promise<void> {
|
|
94
|
-
const query = `mutation {
|
|
94
|
+
const query = `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
95
95
|
updateProjectV2ItemFieldValue(input: {
|
|
96
|
-
projectId:
|
|
97
|
-
itemId:
|
|
98
|
-
fieldId:
|
|
99
|
-
value: { singleSelectOptionId:
|
|
96
|
+
projectId: $projectId,
|
|
97
|
+
itemId: $itemId,
|
|
98
|
+
fieldId: $fieldId,
|
|
99
|
+
value: { singleSelectOptionId: $optionId }
|
|
100
100
|
}) {
|
|
101
101
|
projectV2Item { id }
|
|
102
102
|
}
|
|
103
103
|
}`;
|
|
104
104
|
|
|
105
|
-
await this.executeGraphQL(query
|
|
105
|
+
await this.executeGraphQL(query, {
|
|
106
|
+
projectId,
|
|
107
|
+
itemId,
|
|
108
|
+
fieldId,
|
|
109
|
+
optionId: value.singleSelectOptionId,
|
|
110
|
+
});
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
/**
|
|
109
114
|
* Get field definitions for a Projects V2 board.
|
|
110
115
|
*/
|
|
111
116
|
async getProjectFields(projectId: string): Promise<ProjectField[]> {
|
|
112
|
-
const query = `query {
|
|
113
|
-
node(id:
|
|
117
|
+
const query = `query($nodeId: ID!) {
|
|
118
|
+
node(id: $nodeId) {
|
|
114
119
|
... on ProjectV2 {
|
|
115
120
|
fields(first: 50) {
|
|
116
121
|
nodes {
|
|
@@ -125,7 +130,7 @@ export class GitHubGraphQLClient {
|
|
|
125
130
|
}
|
|
126
131
|
}`;
|
|
127
132
|
|
|
128
|
-
const result = await this.executeGraphQL(query);
|
|
133
|
+
const result = await this.executeGraphQL(query, { nodeId: projectId });
|
|
129
134
|
const fields = (result.data?.node as { fields?: { nodes: Array<{
|
|
130
135
|
id: string;
|
|
131
136
|
name: string;
|
|
@@ -144,10 +149,17 @@ export class GitHubGraphQLClient {
|
|
|
144
149
|
|
|
145
150
|
/**
|
|
146
151
|
* Execute a GraphQL query via `gh api graphql`.
|
|
152
|
+
* All variables are passed via `-F` flags to prevent injection.
|
|
147
153
|
*/
|
|
148
|
-
private executeGraphQL(query: string): Promise<GraphQLResponse> {
|
|
154
|
+
private executeGraphQL(query: string, variables: Record<string, string> = {}): Promise<GraphQLResponse> {
|
|
149
155
|
return new Promise((resolve, reject) => {
|
|
150
156
|
const args = ['api', 'graphql', '-f', `query=${query}`];
|
|
157
|
+
|
|
158
|
+
// Pass all variables as structured parameters via -F
|
|
159
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
160
|
+
args.push('-F', `${key}=${value}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
151
163
|
const opts: { env?: NodeJS.ProcessEnv } = {};
|
|
152
164
|
|
|
153
165
|
if (this.token) {
|
|
@@ -156,8 +156,8 @@ async function executeSearch(query) {
|
|
|
156
156
|
"1000"
|
|
157
157
|
// Max results
|
|
158
158
|
], { env: getGhEnv() });
|
|
159
|
-
if (result.
|
|
160
|
-
throw new Error(`Failed to search issues: ${result.stderr || result.stdout}`);
|
|
159
|
+
if (result.exitCode !== 0) {
|
|
160
|
+
throw new Error(`Failed to search issues (exit code ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
161
161
|
}
|
|
162
162
|
if (!result.stdout.trim()) {
|
|
163
163
|
return [];
|
|
@@ -317,8 +317,8 @@ async function executeSearch(query: string): Promise<GitHubIssue[]> {
|
|
|
317
317
|
'1000', // Max results
|
|
318
318
|
], { env: getGhEnv() });
|
|
319
319
|
|
|
320
|
-
if (result.
|
|
321
|
-
throw new Error(`Failed to search issues: ${result.stderr || result.stdout}`);
|
|
320
|
+
if (result.exitCode !== 0) {
|
|
321
|
+
throw new Error(`Failed to search issues (exit code ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
if (!result.stdout.trim()) {
|
|
@@ -251,13 +251,29 @@ ${userStory.technicalContext}
|
|
|
251
251
|
|
|
252
252
|
\u{1F916} Auto-generated by SpecWeave
|
|
253
253
|
`;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
let existing;
|
|
255
|
+
const seenNumbers = /* @__PURE__ */ new Set();
|
|
256
|
+
let page = 1;
|
|
257
|
+
while (!existing) {
|
|
258
|
+
const existingIssues = await this.octokit.issues.listForRepo({
|
|
259
|
+
owner: this.config.owner,
|
|
260
|
+
repo,
|
|
261
|
+
labels: "specweave",
|
|
262
|
+
state: "all",
|
|
263
|
+
per_page: 100,
|
|
264
|
+
page
|
|
265
|
+
});
|
|
266
|
+
for (const issue of existingIssues.data) {
|
|
267
|
+
if (seenNumbers.has(issue.number)) continue;
|
|
268
|
+
seenNumbers.add(issue.number);
|
|
269
|
+
if (issue.title === title) {
|
|
270
|
+
existing = { number: issue.number, title: issue.title };
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (existingIssues.data.length < 100) break;
|
|
275
|
+
page++;
|
|
276
|
+
}
|
|
261
277
|
if (existing) {
|
|
262
278
|
const response = await this.octokit.issues.update({
|
|
263
279
|
owner: this.config.owner,
|
|
@@ -356,15 +356,33 @@ ${userStory.technicalContext ? `\n## Technical Context\n\n${userStory.technicalC
|
|
|
356
356
|
🤖 Auto-generated by SpecWeave
|
|
357
357
|
`;
|
|
358
358
|
|
|
359
|
-
// Check if issue already exists (search by title)
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
359
|
+
// Check if issue already exists (search by title, paginated)
|
|
360
|
+
let existing: { number: number; title: string } | undefined;
|
|
361
|
+
const seenNumbers = new Set<number>();
|
|
362
|
+
let page = 1;
|
|
363
|
+
|
|
364
|
+
while (!existing) {
|
|
365
|
+
const existingIssues = await this.octokit.issues.listForRepo({
|
|
366
|
+
owner: this.config.owner,
|
|
367
|
+
repo,
|
|
368
|
+
labels: 'specweave',
|
|
369
|
+
state: 'all',
|
|
370
|
+
per_page: 100,
|
|
371
|
+
page,
|
|
372
|
+
});
|
|
366
373
|
|
|
367
|
-
|
|
374
|
+
for (const issue of existingIssues.data) {
|
|
375
|
+
if (seenNumbers.has(issue.number)) continue;
|
|
376
|
+
seenNumbers.add(issue.number);
|
|
377
|
+
if (issue.title === title) {
|
|
378
|
+
existing = { number: issue.number, title: issue.title };
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (existingIssues.data.length < 100) break;
|
|
384
|
+
page++;
|
|
385
|
+
}
|
|
368
386
|
|
|
369
387
|
if (existing) {
|
|
370
388
|
// Update existing issue
|
|
@@ -60,10 +60,38 @@ function parseYamlSimple(yaml) {
|
|
|
60
60
|
const stack = [
|
|
61
61
|
{ obj: result, indent: -1 }
|
|
62
62
|
];
|
|
63
|
-
for (
|
|
63
|
+
for (let i = 0; i < lines.length; i++) {
|
|
64
|
+
const line = lines[i];
|
|
64
65
|
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
65
66
|
const indent = line.search(/\S/);
|
|
66
67
|
const trimmed = line.trim();
|
|
68
|
+
if (trimmed.startsWith("- ")) {
|
|
69
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
70
|
+
stack.pop();
|
|
71
|
+
}
|
|
72
|
+
const parent2 = stack[stack.length - 1];
|
|
73
|
+
const key2 = parent2.currentKey;
|
|
74
|
+
if (key2 && parent2.obj[key2] !== void 0) {
|
|
75
|
+
const arr = parent2.obj[key2];
|
|
76
|
+
if (Array.isArray(arr)) {
|
|
77
|
+
const itemText = trimmed.slice(2).trim();
|
|
78
|
+
const nestedColonIdx = itemText.indexOf(":");
|
|
79
|
+
if (nestedColonIdx > 0 && !itemText.startsWith('"') && !itemText.startsWith("'")) {
|
|
80
|
+
const nestedKey = itemText.slice(0, nestedColonIdx).trim();
|
|
81
|
+
const nestedVal = itemText.slice(nestedColonIdx + 1).trim();
|
|
82
|
+
if (nestedVal) {
|
|
83
|
+
const obj = { [nestedKey]: parseYamlValue(nestedVal) };
|
|
84
|
+
arr.push(obj);
|
|
85
|
+
} else {
|
|
86
|
+
arr.push(parseYamlValue(itemText));
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
arr.push(parseYamlValue(itemText));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
67
95
|
const colonIdx = trimmed.indexOf(":");
|
|
68
96
|
if (colonIdx === -1) continue;
|
|
69
97
|
const key = trimmed.slice(0, colonIdx).trim();
|
|
@@ -73,15 +101,60 @@ function parseYamlSimple(yaml) {
|
|
|
73
101
|
}
|
|
74
102
|
const parent = stack[stack.length - 1].obj;
|
|
75
103
|
if (rawValue === "" || rawValue === void 0) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
const nextIdx = findNextNonEmptyLine(lines, i + 1);
|
|
105
|
+
if (nextIdx !== -1 && lines[nextIdx].trim().startsWith("- ")) {
|
|
106
|
+
parent[key] = [];
|
|
107
|
+
stack.push({ obj: parent, indent, currentKey: key });
|
|
108
|
+
} else {
|
|
109
|
+
const child = {};
|
|
110
|
+
parent[key] = child;
|
|
111
|
+
stack.push({ obj: child, indent });
|
|
112
|
+
}
|
|
113
|
+
} else if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
|
|
114
|
+
parent[key] = parseFlowArray(rawValue);
|
|
79
115
|
} else {
|
|
80
116
|
parent[key] = parseYamlValue(rawValue);
|
|
81
117
|
}
|
|
82
118
|
}
|
|
83
119
|
return result;
|
|
84
120
|
}
|
|
121
|
+
function findNextNonEmptyLine(lines, start) {
|
|
122
|
+
for (let i = start; i < lines.length; i++) {
|
|
123
|
+
const trimmed = lines[i].trim();
|
|
124
|
+
if (trimmed && !trimmed.startsWith("#")) return i;
|
|
125
|
+
}
|
|
126
|
+
return -1;
|
|
127
|
+
}
|
|
128
|
+
function parseFlowArray(raw) {
|
|
129
|
+
const inner = raw.slice(1, -1).trim();
|
|
130
|
+
if (!inner) return [];
|
|
131
|
+
const items = [];
|
|
132
|
+
let current = "";
|
|
133
|
+
let inQuote = false;
|
|
134
|
+
let quoteChar = "";
|
|
135
|
+
for (let i = 0; i < inner.length; i++) {
|
|
136
|
+
const ch = inner[i];
|
|
137
|
+
if (inQuote) {
|
|
138
|
+
if (ch === quoteChar) {
|
|
139
|
+
inQuote = false;
|
|
140
|
+
}
|
|
141
|
+
current += ch;
|
|
142
|
+
} else if (ch === '"' || ch === "'") {
|
|
143
|
+
inQuote = true;
|
|
144
|
+
quoteChar = ch;
|
|
145
|
+
current += ch;
|
|
146
|
+
} else if (ch === ",") {
|
|
147
|
+
items.push(parseYamlValue(current.trim()));
|
|
148
|
+
current = "";
|
|
149
|
+
} else {
|
|
150
|
+
current += ch;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (current.trim()) {
|
|
154
|
+
items.push(parseYamlValue(current.trim()));
|
|
155
|
+
}
|
|
156
|
+
return items;
|
|
157
|
+
}
|
|
85
158
|
function parseYamlValue(raw) {
|
|
86
159
|
if (raw === "null") return null;
|
|
87
160
|
if (raw === "true") return true;
|
|
@@ -99,7 +172,39 @@ function stringifyYaml(obj, indent = 0) {
|
|
|
99
172
|
for (const [key, value] of Object.entries(obj)) {
|
|
100
173
|
if (value === null || value === void 0) {
|
|
101
174
|
parts.push(`${prefix}${key}: null`);
|
|
102
|
-
} else if (
|
|
175
|
+
} else if (Array.isArray(value)) {
|
|
176
|
+
if (value.length === 0) {
|
|
177
|
+
parts.push(`${prefix}${key}: []`);
|
|
178
|
+
} else if (value.every((v) => typeof v !== "object" || v === null)) {
|
|
179
|
+
parts.push(`${prefix}${key}:`);
|
|
180
|
+
for (const item of value) {
|
|
181
|
+
if (typeof item === "string") {
|
|
182
|
+
parts.push(`${prefix} - "${item}"`);
|
|
183
|
+
} else {
|
|
184
|
+
parts.push(`${prefix} - ${item}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
parts.push(`${prefix}${key}:`);
|
|
189
|
+
for (const item of value) {
|
|
190
|
+
if (typeof item === "object" && item !== null) {
|
|
191
|
+
const entries = Object.entries(item);
|
|
192
|
+
if (entries.length > 0) {
|
|
193
|
+
const [firstKey, firstVal] = entries[0];
|
|
194
|
+
const formattedVal = typeof firstVal === "string" ? `"${firstVal}"` : firstVal;
|
|
195
|
+
parts.push(`${prefix} - ${firstKey}: ${formattedVal}`);
|
|
196
|
+
for (let j = 1; j < entries.length; j++) {
|
|
197
|
+
const [k, v] = entries[j];
|
|
198
|
+
const fv = typeof v === "string" ? `"${v}"` : v;
|
|
199
|
+
parts.push(`${prefix} ${k}: ${fv}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
parts.push(`${prefix} - ${item}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} else if (typeof value === "object") {
|
|
103
208
|
parts.push(`${prefix}${key}:`);
|
|
104
209
|
parts.push(stringifyYaml(value, indent + 1));
|
|
105
210
|
} else if (typeof value === "string") {
|
|
@@ -101,20 +101,55 @@ export async function updateSpecFrontmatter(
|
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* Simple YAML parser for spec frontmatter.
|
|
104
|
-
* Handles nested objects, strings, numbers, booleans, null.
|
|
104
|
+
* Handles nested objects, strings, numbers, booleans, null, and arrays.
|
|
105
|
+
* Supports block arrays (`- item`) and flow arrays (`[a, b, c]`).
|
|
105
106
|
*/
|
|
106
107
|
function parseYamlSimple(yaml: string): Record<string, unknown> {
|
|
107
108
|
const result: Record<string, unknown> = {};
|
|
108
109
|
const lines = yaml.split('\n');
|
|
109
|
-
const stack: Array<{ obj: Record<string, unknown>; indent: number }> = [
|
|
110
|
+
const stack: Array<{ obj: Record<string, unknown>; indent: number; currentKey?: string }> = [
|
|
110
111
|
{ obj: result, indent: -1 },
|
|
111
112
|
];
|
|
112
113
|
|
|
113
|
-
for (
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const line = lines[i];
|
|
114
116
|
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
115
117
|
|
|
116
118
|
const indent = line.search(/\S/);
|
|
117
119
|
const trimmed = line.trim();
|
|
120
|
+
|
|
121
|
+
// Block array item: `- value` or `- key: value` (nested object in array)
|
|
122
|
+
if (trimmed.startsWith('- ')) {
|
|
123
|
+
// Find the parent that owns this array
|
|
124
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
125
|
+
stack.pop();
|
|
126
|
+
}
|
|
127
|
+
const parent = stack[stack.length - 1];
|
|
128
|
+
const key = parent.currentKey;
|
|
129
|
+
if (key && parent.obj[key] !== undefined) {
|
|
130
|
+
const arr = parent.obj[key];
|
|
131
|
+
if (Array.isArray(arr)) {
|
|
132
|
+
const itemText = trimmed.slice(2).trim();
|
|
133
|
+
// Check if it's a nested object item (- key: value)
|
|
134
|
+
const nestedColonIdx = itemText.indexOf(':');
|
|
135
|
+
if (nestedColonIdx > 0 && !itemText.startsWith('"') && !itemText.startsWith("'")) {
|
|
136
|
+
const nestedKey = itemText.slice(0, nestedColonIdx).trim();
|
|
137
|
+
const nestedVal = itemText.slice(nestedColonIdx + 1).trim();
|
|
138
|
+
if (nestedVal) {
|
|
139
|
+
// Simple key:value object item
|
|
140
|
+
const obj: Record<string, unknown> = { [nestedKey]: parseYamlValue(nestedVal) };
|
|
141
|
+
arr.push(obj);
|
|
142
|
+
} else {
|
|
143
|
+
arr.push(parseYamlValue(itemText));
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
arr.push(parseYamlValue(itemText));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
118
153
|
const colonIdx = trimmed.indexOf(':');
|
|
119
154
|
if (colonIdx === -1) continue;
|
|
120
155
|
|
|
@@ -129,10 +164,21 @@ function parseYamlSimple(yaml: string): Record<string, unknown> {
|
|
|
129
164
|
const parent = stack[stack.length - 1].obj;
|
|
130
165
|
|
|
131
166
|
if (rawValue === '' || rawValue === undefined) {
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
167
|
+
// Check if next non-empty line is a block array item
|
|
168
|
+
const nextIdx = findNextNonEmptyLine(lines, i + 1);
|
|
169
|
+
if (nextIdx !== -1 && lines[nextIdx].trim().startsWith('- ')) {
|
|
170
|
+
// This key holds an array
|
|
171
|
+
parent[key] = [] as unknown[];
|
|
172
|
+
stack.push({ obj: parent, indent, currentKey: key });
|
|
173
|
+
} else {
|
|
174
|
+
// Nested object
|
|
175
|
+
const child: Record<string, unknown> = {};
|
|
176
|
+
parent[key] = child;
|
|
177
|
+
stack.push({ obj: child, indent });
|
|
178
|
+
}
|
|
179
|
+
} else if (rawValue.startsWith('[') && rawValue.endsWith(']')) {
|
|
180
|
+
// Flow array: [a, b, c]
|
|
181
|
+
parent[key] = parseFlowArray(rawValue);
|
|
136
182
|
} else {
|
|
137
183
|
parent[key] = parseYamlValue(rawValue);
|
|
138
184
|
}
|
|
@@ -141,6 +187,52 @@ function parseYamlSimple(yaml: string): Record<string, unknown> {
|
|
|
141
187
|
return result;
|
|
142
188
|
}
|
|
143
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Find the next non-empty, non-comment line index.
|
|
192
|
+
*/
|
|
193
|
+
function findNextNonEmptyLine(lines: string[], start: number): number {
|
|
194
|
+
for (let i = start; i < lines.length; i++) {
|
|
195
|
+
const trimmed = lines[i].trim();
|
|
196
|
+
if (trimmed && !trimmed.startsWith('#')) return i;
|
|
197
|
+
}
|
|
198
|
+
return -1;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Parse a YAML flow array like [a, b, c] or ["a", "b"]
|
|
203
|
+
*/
|
|
204
|
+
function parseFlowArray(raw: string): unknown[] {
|
|
205
|
+
const inner = raw.slice(1, -1).trim();
|
|
206
|
+
if (!inner) return [];
|
|
207
|
+
// Split on commas, respecting quoted strings
|
|
208
|
+
const items: unknown[] = [];
|
|
209
|
+
let current = '';
|
|
210
|
+
let inQuote = false;
|
|
211
|
+
let quoteChar = '';
|
|
212
|
+
for (let i = 0; i < inner.length; i++) {
|
|
213
|
+
const ch = inner[i];
|
|
214
|
+
if (inQuote) {
|
|
215
|
+
if (ch === quoteChar) {
|
|
216
|
+
inQuote = false;
|
|
217
|
+
}
|
|
218
|
+
current += ch;
|
|
219
|
+
} else if (ch === '"' || ch === "'") {
|
|
220
|
+
inQuote = true;
|
|
221
|
+
quoteChar = ch;
|
|
222
|
+
current += ch;
|
|
223
|
+
} else if (ch === ',') {
|
|
224
|
+
items.push(parseYamlValue(current.trim()));
|
|
225
|
+
current = '';
|
|
226
|
+
} else {
|
|
227
|
+
current += ch;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (current.trim()) {
|
|
231
|
+
items.push(parseYamlValue(current.trim()));
|
|
232
|
+
}
|
|
233
|
+
return items;
|
|
234
|
+
}
|
|
235
|
+
|
|
144
236
|
function parseYamlValue(raw: string): unknown {
|
|
145
237
|
if (raw === 'null') return null;
|
|
146
238
|
if (raw === 'true') return true;
|
|
@@ -155,7 +247,7 @@ function parseYamlValue(raw: string): unknown {
|
|
|
155
247
|
}
|
|
156
248
|
|
|
157
249
|
/**
|
|
158
|
-
* Simple YAML stringifier.
|
|
250
|
+
* Simple YAML stringifier with array support.
|
|
159
251
|
*/
|
|
160
252
|
function stringifyYaml(obj: Record<string, unknown>, indent = 0): string {
|
|
161
253
|
const prefix = ' '.repeat(indent);
|
|
@@ -164,7 +256,41 @@ function stringifyYaml(obj: Record<string, unknown>, indent = 0): string {
|
|
|
164
256
|
for (const [key, value] of Object.entries(obj)) {
|
|
165
257
|
if (value === null || value === undefined) {
|
|
166
258
|
parts.push(`${prefix}${key}: null`);
|
|
167
|
-
} else if (
|
|
259
|
+
} else if (Array.isArray(value)) {
|
|
260
|
+
if (value.length === 0) {
|
|
261
|
+
parts.push(`${prefix}${key}: []`);
|
|
262
|
+
} else if (value.every(v => typeof v !== 'object' || v === null)) {
|
|
263
|
+
// Simple array — use block style
|
|
264
|
+
parts.push(`${prefix}${key}:`);
|
|
265
|
+
for (const item of value) {
|
|
266
|
+
if (typeof item === 'string') {
|
|
267
|
+
parts.push(`${prefix} - "${item}"`);
|
|
268
|
+
} else {
|
|
269
|
+
parts.push(`${prefix} - ${item}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
// Array of objects — use block style with nested keys
|
|
274
|
+
parts.push(`${prefix}${key}:`);
|
|
275
|
+
for (const item of value) {
|
|
276
|
+
if (typeof item === 'object' && item !== null) {
|
|
277
|
+
const entries = Object.entries(item as Record<string, unknown>);
|
|
278
|
+
if (entries.length > 0) {
|
|
279
|
+
const [firstKey, firstVal] = entries[0];
|
|
280
|
+
const formattedVal = typeof firstVal === 'string' ? `"${firstVal}"` : firstVal;
|
|
281
|
+
parts.push(`${prefix} - ${firstKey}: ${formattedVal}`);
|
|
282
|
+
for (let j = 1; j < entries.length; j++) {
|
|
283
|
+
const [k, v] = entries[j];
|
|
284
|
+
const fv = typeof v === 'string' ? `"${v}"` : v;
|
|
285
|
+
parts.push(`${prefix} ${k}: ${fv}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
parts.push(`${prefix} - ${item}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} else if (typeof value === 'object') {
|
|
168
294
|
parts.push(`${prefix}${key}:`);
|
|
169
295
|
parts.push(stringifyYaml(value as Record<string, unknown>, indent + 1));
|
|
170
296
|
} else if (typeof value === 'string') {
|