loki-mode 5.58.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v5.58.2
6
+ # Loki Mode v6.0.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -263,4 +263,4 @@ The following features are documented in skill modules but not yet fully automat
263
263
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
264
264
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
265
265
 
266
- **v5.58.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
266
+ **v6.0.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.58.2
1
+ 6.0.0
@@ -259,7 +259,14 @@ council_vote() {
259
259
  verdict=$(council_member_review "$member" "$role" "$evidence_file" "$vote_dir")
260
260
 
261
261
  local vote_result
262
- vote_result=$(echo "$verdict" | grep -oE "VOTE:\s*(APPROVE|REJECT)" | grep -oE "APPROVE|REJECT" | head -1)
262
+ vote_result=$(echo "$verdict" | grep -oE "VOTE:\s*(APPROVE|REJECT|CANNOT_VALIDATE)" | grep -oE "APPROVE|REJECT|CANNOT_VALIDATE" | head -1)
263
+
264
+ # v6.0.0: Handle CANNOT_VALIDATE - validator lacks enough context to decide
265
+ if [ "$vote_result" = "CANNOT_VALIDATE" ]; then
266
+ log_warn " Member $member ($role): CANNOT_VALIDATE - insufficient evidence"
267
+ # CANNOT_VALIDATE counts as REJECT (conservative default)
268
+ vote_result="REJECT"
269
+ fi
263
270
 
264
271
  # Extract severity-categorized issues (v5.49.0 error budget)
265
272
  local member_issues=""
@@ -320,9 +327,9 @@ council_vote() {
320
327
  local contrarian_verdict
321
328
  contrarian_verdict=$(council_devils_advocate "$evidence_file" "$vote_dir")
322
329
  local contrarian_vote
323
- contrarian_vote=$(echo "$contrarian_verdict" | grep -oE "VOTE:\s*(APPROVE|REJECT)" | grep -oE "APPROVE|REJECT" | head -1)
330
+ contrarian_vote=$(echo "$contrarian_verdict" | grep -oE "VOTE:\s*(APPROVE|REJECT|CANNOT_VALIDATE)" | grep -oE "APPROVE|REJECT|CANNOT_VALIDATE" | head -1)
324
331
 
325
- if [ "$contrarian_vote" = "REJECT" ]; then
332
+ if [ "$contrarian_vote" = "REJECT" ] || [ "$contrarian_vote" = "CANNOT_VALIDATE" ]; then
326
333
  log_warn "Anti-sycophancy: Devil's advocate REJECTED unanimous approval"
327
334
  log_warn "Overriding to require one more iteration for verification"
328
335
  approve_count=$((approve_count - 1))
@@ -661,6 +668,21 @@ council_member_review() {
661
668
  local evidence
662
669
  evidence=$(cat "$evidence_file" 2>/dev/null || echo "No evidence available")
663
670
 
671
+ # v6.0.0: Blind validation (default ON) - strip worker iteration context
672
+ # Validators see only: PRD, git state, test results, build artifacts
673
+ # They do NOT see: iteration count, convergence signals, agent done signals
674
+ local blind_mode="${LOKI_BLIND_VALIDATION:-true}"
675
+ if [ "$blind_mode" = "true" ]; then
676
+ # Strip convergence/iteration context that could bias validators
677
+ # Uses awk for macOS/BSD compatibility (sed range syntax differs between GNU/BSD)
678
+ evidence=$(echo "$evidence" | awk '
679
+ /^## Convergence Data/ { skip=1; next }
680
+ /^## / && skip { skip=0 }
681
+ !skip { print }
682
+ ')
683
+ log_debug "Blind validation: stripped convergence context for member $member_id"
684
+ fi
685
+
664
686
  local verdict=""
665
687
  local role_instruction=""
666
688
  case "$role" in
@@ -700,13 +722,14 @@ ${severity_instruction}
700
722
  INSTRUCTIONS:
701
723
  1. Review the evidence carefully
702
724
  2. Determine if the project meets completion criteria
703
- 3. Output EXACTLY one line starting with VOTE:APPROVE or VOTE:REJECT
725
+ 3. Output EXACTLY one line starting with VOTE:APPROVE, VOTE:REJECT, or VOTE:CANNOT_VALIDATE
704
726
  4. Output EXACTLY one line starting with REASON: explaining your decision
705
727
  5. If issues found, output lines starting with ISSUES: SEVERITY:description
706
728
  6. Be honest - do not approve incomplete work
729
+ 7. If you lack sufficient evidence to make a determination, vote CANNOT_VALIDATE
707
730
 
708
731
  Output format:
709
- VOTE:APPROVE or VOTE:REJECT
732
+ VOTE:APPROVE or VOTE:REJECT or VOTE:CANNOT_VALIDATE
710
733
  REASON: your reasoning here
711
734
  ISSUES: CRITICAL:description (optional, one per line per issue)"
712
735
 
@@ -716,12 +739,13 @@ ISSUES: CRITICAL:description (optional, one per line per issue)"
716
739
  case "${PROVIDER_NAME:-claude}" in
717
740
  claude)
718
741
  if command -v claude &>/dev/null; then
719
- verdict=$(echo "$prompt" | claude --model haiku -p 2>/dev/null | tail -5)
742
+ local council_model="${PROVIDER_MODEL_FAST:-haiku}"
743
+ verdict=$(echo "$prompt" | claude --model "$council_model" -p 2>/dev/null | tail -5)
720
744
  fi
721
745
  ;;
722
746
  codex)
723
747
  if command -v codex &>/dev/null; then
724
- verdict=$(codex exec -q "$prompt" 2>/dev/null | tail -5)
748
+ verdict=$(codex exec --full-auto "$prompt" 2>/dev/null | tail -5)
725
749
  fi
726
750
  ;;
727
751
  gemini)
@@ -792,12 +816,13 @@ REASON: your reasoning"
792
816
  case "${PROVIDER_NAME:-claude}" in
793
817
  claude)
794
818
  if command -v claude &>/dev/null; then
795
- verdict=$(echo "$prompt" | claude --model haiku -p 2>/dev/null | tail -5)
819
+ local council_model="${PROVIDER_MODEL_FAST:-haiku}"
820
+ verdict=$(echo "$prompt" | claude --model "$council_model" -p 2>/dev/null | tail -5)
796
821
  fi
797
822
  ;;
798
823
  codex)
799
824
  if command -v codex &>/dev/null; then
800
- verdict=$(codex exec -q "$prompt" 2>/dev/null | tail -5)
825
+ verdict=$(codex exec --full-auto "$prompt" 2>/dev/null | tail -5)
801
826
  fi
802
827
  ;;
803
828
  gemini)
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env bash
2
+ #===============================================================================
3
+ # Loki Mode - Issue Provider Abstraction (v6.0.0)
4
+ # Multi-provider issue fetching: GitHub, GitLab, Jira, Azure DevOps
5
+ #
6
+ # Usage:
7
+ # source autonomy/issue-providers.sh
8
+ # detect_issue_provider "https://github.com/org/repo/issues/123"
9
+ # fetch_issue "https://github.com/org/repo/issues/123"
10
+ # fetch_issue "https://gitlab.com/org/repo/-/issues/42"
11
+ # fetch_issue "https://org.atlassian.net/browse/PROJ-123"
12
+ # fetch_issue "https://dev.azure.com/org/project/_workitems/edit/456"
13
+ #
14
+ # Output:
15
+ # JSON with normalized fields: provider, number, title, body, labels, author, url, created_at
16
+ #===============================================================================
17
+
18
+ # Colors (safe to re-source)
19
+ RED='\033[0;31m'
20
+ GREEN='\033[0;32m'
21
+ YELLOW='\033[1;33m'
22
+ CYAN='\033[0;36m'
23
+ NC='\033[0m'
24
+
25
+ # Supported issue providers
26
+ ISSUE_PROVIDERS=("github" "gitlab" "jira" "azure_devops")
27
+
28
+ # Detect issue provider from a URL or reference
29
+ # Returns: github, gitlab, jira, azure_devops, or "unknown"
30
+ detect_issue_provider() {
31
+ local ref="$1"
32
+
33
+ if [[ "$ref" =~ github\.com ]]; then
34
+ echo "github"
35
+ elif [[ "$ref" =~ gitlab\.com ]] || [[ "$ref" =~ gitlab\. ]]; then
36
+ echo "gitlab"
37
+ elif [[ "$ref" =~ \.atlassian\.net ]] || [[ "$ref" =~ jira\. ]]; then
38
+ echo "jira"
39
+ elif [[ "$ref" =~ dev\.azure\.com ]] || [[ "$ref" =~ visualstudio\.com ]]; then
40
+ echo "azure_devops"
41
+ elif [[ "$ref" =~ ^[0-9]+$ ]] || [[ "$ref" =~ ^#[0-9]+$ ]]; then
42
+ # Bare number - default to GitHub (most common)
43
+ echo "github"
44
+ elif [[ "$ref" =~ ^[^/]+/[^#]+#[0-9]+$ ]]; then
45
+ # owner/repo#N format - GitHub
46
+ echo "github"
47
+ elif [[ "$ref" =~ ^[A-Z]+-[0-9]+$ ]]; then
48
+ # PROJ-123 format - Jira
49
+ echo "jira"
50
+ else
51
+ echo "unknown"
52
+ fi
53
+ }
54
+
55
+ # Check if required CLI tools are available for a provider
56
+ check_issue_provider_cli() {
57
+ local provider="$1"
58
+
59
+ case "$provider" in
60
+ github)
61
+ if ! command -v gh &>/dev/null; then
62
+ echo -e "${RED}Error: gh CLI not found. Install with: brew install gh${NC}" >&2
63
+ return 1
64
+ fi
65
+ if ! gh auth status &>/dev/null 2>&1; then
66
+ echo -e "${RED}Error: gh CLI not authenticated. Run: gh auth login${NC}" >&2
67
+ return 1
68
+ fi
69
+ ;;
70
+ gitlab)
71
+ if ! command -v glab &>/dev/null; then
72
+ echo -e "${RED}Error: glab CLI not found. Install with: brew install glab${NC}" >&2
73
+ return 1
74
+ fi
75
+ ;;
76
+ jira)
77
+ # Jira uses REST API via curl - check for JIRA_API_TOKEN
78
+ if [ -z "${JIRA_API_TOKEN:-}" ] && [ -z "${JIRA_TOKEN:-}" ]; then
79
+ echo -e "${RED}Error: JIRA_API_TOKEN or JIRA_TOKEN not set${NC}" >&2
80
+ echo "Set with: export JIRA_API_TOKEN=your-token" >&2
81
+ return 1
82
+ fi
83
+ if [ -z "${JIRA_URL:-}" ] && [ -z "${JIRA_BASE_URL:-}" ]; then
84
+ echo -e "${RED}Error: JIRA_URL or JIRA_BASE_URL not set${NC}" >&2
85
+ echo "Set with: export JIRA_URL=https://your-org.atlassian.net" >&2
86
+ return 1
87
+ fi
88
+ ;;
89
+ azure_devops)
90
+ if ! command -v az &>/dev/null; then
91
+ echo -e "${RED}Error: az CLI not found. Install with: brew install azure-cli${NC}" >&2
92
+ return 1
93
+ fi
94
+ ;;
95
+ *)
96
+ echo -e "${RED}Error: Unknown issue provider: $provider${NC}" >&2
97
+ return 1
98
+ ;;
99
+ esac
100
+ return 0
101
+ }
102
+
103
+ # Parse issue reference and extract provider-specific identifiers
104
+ # Sets: ISSUE_PROVIDER, ISSUE_OWNER, ISSUE_REPO, ISSUE_NUMBER, ISSUE_PROJECT, ISSUE_ORG
105
+ parse_issue_reference() {
106
+ local ref="$1"
107
+
108
+ ISSUE_PROVIDER=$(detect_issue_provider "$ref")
109
+ ISSUE_OWNER=""
110
+ ISSUE_REPO=""
111
+ ISSUE_NUMBER=""
112
+ ISSUE_PROJECT=""
113
+ ISSUE_ORG=""
114
+
115
+ case "$ISSUE_PROVIDER" in
116
+ github)
117
+ if [[ "$ref" =~ ^https?://github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
118
+ ISSUE_OWNER="${BASH_REMATCH[1]}"
119
+ ISSUE_REPO="${BASH_REMATCH[2]}"
120
+ ISSUE_NUMBER="${BASH_REMATCH[3]}"
121
+ elif [[ "$ref" =~ ^([^/]+)/([^#]+)#([0-9]+)$ ]]; then
122
+ ISSUE_OWNER="${BASH_REMATCH[1]}"
123
+ ISSUE_REPO="${BASH_REMATCH[2]}"
124
+ ISSUE_NUMBER="${BASH_REMATCH[3]}"
125
+ elif [[ "$ref" =~ ^#?([0-9]+)$ ]]; then
126
+ ISSUE_NUMBER="${BASH_REMATCH[1]}"
127
+ # Auto-detect repo from git remote
128
+ local remote_repo
129
+ remote_repo=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null) || true
130
+ if [ -n "$remote_repo" ]; then
131
+ ISSUE_OWNER="${remote_repo%%/*}"
132
+ ISSUE_REPO="${remote_repo##*/}"
133
+ fi
134
+ fi
135
+ ;;
136
+ gitlab)
137
+ if [[ "$ref" =~ ^https?://[^/]+/(.+)/([^/]+)/-/issues/([0-9]+) ]]; then
138
+ ISSUE_OWNER="${BASH_REMATCH[1]}"
139
+ ISSUE_REPO="${BASH_REMATCH[2]}"
140
+ ISSUE_NUMBER="${BASH_REMATCH[3]}"
141
+ elif [[ "$ref" =~ ^#?([0-9]+)$ ]]; then
142
+ ISSUE_NUMBER="${BASH_REMATCH[1]}"
143
+ fi
144
+ ;;
145
+ jira)
146
+ if [[ "$ref" =~ /browse/([A-Z]+-[0-9]+) ]]; then
147
+ ISSUE_NUMBER="${BASH_REMATCH[1]}"
148
+ elif [[ "$ref" =~ ^([A-Z]+-[0-9]+)$ ]]; then
149
+ ISSUE_NUMBER="$ref"
150
+ fi
151
+ ISSUE_PROJECT="${ISSUE_NUMBER%%-*}"
152
+ ;;
153
+ azure_devops)
154
+ if [[ "$ref" =~ dev\.azure\.com/([^/]+)/([^/]+)/_workitems/edit/([0-9]+) ]]; then
155
+ ISSUE_ORG="${BASH_REMATCH[1]}"
156
+ ISSUE_PROJECT="${BASH_REMATCH[2]}"
157
+ ISSUE_NUMBER="${BASH_REMATCH[3]}"
158
+ fi
159
+ ;;
160
+ esac
161
+ }
162
+
163
+ # Fetch issue from GitHub using gh CLI
164
+ # Output: normalized JSON
165
+ fetch_github_issue() {
166
+ local owner="$ISSUE_OWNER"
167
+ local repo="$ISSUE_REPO"
168
+ local number="$ISSUE_NUMBER"
169
+
170
+ local repo_ref=""
171
+ if [ -n "$owner" ] && [ -n "$repo" ]; then
172
+ repo_ref="$owner/$repo"
173
+ fi
174
+
175
+ local issue_json
176
+ if [ -n "$repo_ref" ]; then
177
+ issue_json=$(gh issue view "$number" --repo "$repo_ref" --json number,title,body,labels,author,createdAt,url 2>&1) || {
178
+ echo -e "${RED}Error fetching GitHub issue: $issue_json${NC}" >&2
179
+ return 1
180
+ }
181
+ else
182
+ issue_json=$(gh issue view "$number" --json number,title,body,labels,author,createdAt,url 2>&1) || {
183
+ echo -e "${RED}Error fetching GitHub issue: $issue_json${NC}" >&2
184
+ return 1
185
+ }
186
+ fi
187
+
188
+ # Normalize to common format (pass repo via env to prevent injection)
189
+ _LOKI_REPO_REF="$repo_ref" python3 -c "
190
+ import json, sys, os
191
+ data = json.loads(sys.stdin.read())
192
+ print(json.dumps({
193
+ 'provider': 'github',
194
+ 'number': data.get('number', 0),
195
+ 'title': data.get('title', ''),
196
+ 'body': data.get('body', '') or '',
197
+ 'labels': [l.get('name','') for l in data.get('labels', [])],
198
+ 'author': data.get('author', {}).get('login', ''),
199
+ 'url': data.get('url', ''),
200
+ 'created_at': data.get('createdAt', ''),
201
+ 'repo': os.environ.get('_LOKI_REPO_REF', '')
202
+ }))
203
+ " <<< "$issue_json"
204
+ }
205
+
206
+ # Fetch issue from GitLab using glab CLI
207
+ fetch_gitlab_issue() {
208
+ local number="$ISSUE_NUMBER"
209
+ local repo_ref=""
210
+ if [ -n "$ISSUE_OWNER" ] && [ -n "$ISSUE_REPO" ]; then
211
+ repo_ref="$ISSUE_OWNER/$ISSUE_REPO"
212
+ fi
213
+
214
+ local issue_json
215
+ if [ -n "$repo_ref" ]; then
216
+ issue_json=$(glab issue view "$number" --repo "$repo_ref" --output json 2>&1) || {
217
+ echo -e "${RED}Error fetching GitLab issue: $issue_json${NC}" >&2
218
+ return 1
219
+ }
220
+ else
221
+ issue_json=$(glab issue view "$number" --output json 2>&1) || {
222
+ echo -e "${RED}Error fetching GitLab issue: $issue_json${NC}" >&2
223
+ return 1
224
+ }
225
+ fi
226
+
227
+ _LOKI_REPO_REF="$repo_ref" python3 -c "
228
+ import json, sys, os
229
+ data = json.loads(sys.stdin.read())
230
+ print(json.dumps({
231
+ 'provider': 'gitlab',
232
+ 'number': data.get('iid', 0),
233
+ 'title': data.get('title', ''),
234
+ 'body': data.get('description', '') or '',
235
+ 'labels': data.get('labels', []),
236
+ 'author': (data.get('author') or {}).get('username', ''),
237
+ 'url': data.get('web_url', ''),
238
+ 'created_at': data.get('created_at', ''),
239
+ 'repo': os.environ.get('_LOKI_REPO_REF', '')
240
+ }))
241
+ " <<< "$issue_json"
242
+ }
243
+
244
+ # Fetch issue from Jira using REST API
245
+ fetch_jira_issue() {
246
+ local issue_key="$ISSUE_NUMBER"
247
+ local base_url="${JIRA_URL:-${JIRA_BASE_URL:-}}"
248
+ local token="${JIRA_API_TOKEN:-${JIRA_TOKEN:-}}"
249
+ local email="${JIRA_EMAIL:-${JIRA_USER:-}}"
250
+
251
+ local auth_header=""
252
+ if [ -n "$email" ]; then
253
+ # Jira Cloud: Basic auth with email:token
254
+ local encoded
255
+ encoded=$(printf '%s:%s' "$email" "$token" | base64 | tr -d '\n')
256
+ auth_header="Authorization: Basic $encoded"
257
+ else
258
+ # Bearer token
259
+ auth_header="Authorization: Bearer $token"
260
+ fi
261
+
262
+ local issue_json
263
+ issue_json=$(curl -sf -H "$auth_header" -H "Content-Type: application/json" \
264
+ "${base_url}/rest/api/2/issue/${issue_key}?fields=summary,description,labels,reporter,created" 2>&1) || {
265
+ echo -e "${RED}Error fetching Jira issue: $issue_json${NC}" >&2
266
+ return 1
267
+ }
268
+
269
+ _LOKI_BASE_URL="$base_url" _LOKI_ISSUE_KEY="$issue_key" _LOKI_PROJECT="$ISSUE_PROJECT" python3 -c "
270
+ import json, sys, os
271
+ data = json.loads(sys.stdin.read())
272
+ fields = data.get('fields', {})
273
+ base_url = os.environ.get('_LOKI_BASE_URL', '')
274
+ issue_key = os.environ.get('_LOKI_ISSUE_KEY', '')
275
+ project = os.environ.get('_LOKI_PROJECT', '')
276
+ reporter = fields.get('reporter') or {}
277
+ print(json.dumps({
278
+ 'provider': 'jira',
279
+ 'number': data.get('key', ''),
280
+ 'title': fields.get('summary', ''),
281
+ 'body': fields.get('description', '') or '',
282
+ 'labels': fields.get('labels', []),
283
+ 'author': reporter.get('displayName', '') if isinstance(reporter, dict) else '',
284
+ 'url': f'{base_url}/browse/{issue_key}',
285
+ 'created_at': fields.get('created', ''),
286
+ 'repo': project
287
+ }))
288
+ " <<< "$issue_json"
289
+ }
290
+
291
+ # Fetch issue from Azure DevOps using az CLI
292
+ fetch_azure_devops_issue() {
293
+ local org="$ISSUE_ORG"
294
+ local project="$ISSUE_PROJECT"
295
+ local id="$ISSUE_NUMBER"
296
+
297
+ local issue_json
298
+ issue_json=$(az boards work-item show --id "$id" --org "https://dev.azure.com/$org" --output json 2>&1) || {
299
+ echo -e "${RED}Error fetching Azure DevOps work item: $issue_json${NC}" >&2
300
+ return 1
301
+ }
302
+
303
+ _LOKI_PROJECT="$project" python3 -c "
304
+ import json, sys, os
305
+ data = json.loads(sys.stdin.read())
306
+ fields = data.get('fields', {})
307
+ project = os.environ.get('_LOKI_PROJECT', '')
308
+ created_by = fields.get('System.CreatedBy', '')
309
+ author = created_by.get('displayName', '') if isinstance(created_by, dict) else str(created_by)
310
+ tags = fields.get('System.Tags', '')
311
+ print(json.dumps({
312
+ 'provider': 'azure_devops',
313
+ 'number': data.get('id', 0),
314
+ 'title': fields.get('System.Title', ''),
315
+ 'body': fields.get('System.Description', '') or '',
316
+ 'labels': [t.strip() for t in tags.split(';') if t.strip()] if tags else [],
317
+ 'author': author,
318
+ 'url': data.get('_links', {}).get('html', {}).get('href', ''),
319
+ 'created_at': fields.get('System.CreatedDate', ''),
320
+ 'repo': project
321
+ }))
322
+ " <<< "$issue_json"
323
+ }
324
+
325
+ # Main entry point: fetch issue from any supported provider
326
+ # Usage: fetch_issue "reference-or-url"
327
+ # Output: normalized JSON on stdout
328
+ fetch_issue() {
329
+ local ref="$1"
330
+
331
+ parse_issue_reference "$ref"
332
+
333
+ if [ "$ISSUE_PROVIDER" = "unknown" ]; then
334
+ echo -e "${RED}Error: Could not detect issue provider from: $ref${NC}" >&2
335
+ echo "Supported formats:" >&2
336
+ echo " GitHub: https://github.com/owner/repo/issues/123 or owner/repo#123 or #123" >&2
337
+ echo " GitLab: https://gitlab.com/owner/repo/-/issues/42" >&2
338
+ echo " Jira: https://org.atlassian.net/browse/PROJ-123 or PROJ-123" >&2
339
+ echo " Azure DevOps: https://dev.azure.com/org/project/_workitems/edit/456" >&2
340
+ return 1
341
+ fi
342
+
343
+ if [ -z "$ISSUE_NUMBER" ]; then
344
+ echo -e "${RED}Error: Could not parse issue number from: $ref${NC}" >&2
345
+ return 1
346
+ fi
347
+
348
+ check_issue_provider_cli "$ISSUE_PROVIDER" || return 1
349
+
350
+ case "$ISSUE_PROVIDER" in
351
+ github) fetch_github_issue ;;
352
+ gitlab) fetch_gitlab_issue ;;
353
+ jira) fetch_jira_issue ;;
354
+ azure_devops) fetch_azure_devops_issue ;;
355
+ esac
356
+ }
357
+
358
+ # Generate PRD from normalized issue JSON
359
+ # Input: normalized issue JSON on stdin
360
+ # Output: PRD markdown on stdout
361
+ generate_prd_from_issue() {
362
+ python3 -c "
363
+ import json, sys
364
+
365
+ data = json.loads(sys.stdin.read())
366
+ provider = data.get('provider', 'unknown')
367
+ number = data.get('number', '')
368
+ title = data.get('title', '')
369
+ body = data.get('body', '')
370
+ labels = data.get('labels', [])
371
+ author = data.get('author', '')
372
+ url = data.get('url', '')
373
+ created_at = data.get('created_at', '')
374
+ repo = data.get('repo', '')
375
+
376
+ labels_str = ', '.join(labels) if labels else ''
377
+
378
+ prd = f'''# PRD: {title}
379
+
380
+ **Source:** {provider.replace('_', ' ').title()} Issue [{number}]({url})
381
+ **Author:** {author}
382
+ **Created:** {created_at}
383
+ '''
384
+ if labels_str:
385
+ prd += f'**Labels:** {labels_str}\n'
386
+ if repo:
387
+ prd += f'**Repository:** {repo}\n'
388
+
389
+ prd += f'''
390
+ ---
391
+
392
+ ## Overview
393
+
394
+ {body}
395
+
396
+ ---
397
+
398
+ ## Acceptance Criteria
399
+
400
+ Based on the issue description, implement the following:
401
+
402
+ 1. Address all requirements specified in the issue body above
403
+ 2. Ensure backward compatibility (unless explicitly breaking changes are requested)
404
+ 3. Add appropriate tests for new functionality
405
+ 4. Update documentation as needed
406
+
407
+ ---
408
+
409
+ ## Technical Notes
410
+
411
+ - Source: {provider.replace('_', ' ').title()} Issue {number}
412
+ - Repository: {repo}
413
+ - Generated by: Loki Mode CLI v6.0.0
414
+
415
+ ---
416
+
417
+ ## References
418
+
419
+ - Original Issue: {url}
420
+ '''
421
+ print(prd)
422
+ "
423
+ }